/** * Dependencies API: WP_Scripts class * * @since 2.6.0 * * @package WordPress * @subpackage Dependencies */ /** * Core class used to register scripts. * * @since 2.1.0 * * @see WP_Dependencies */ class WP_Scripts extends WP_Dependencies { /** * Base URL for scripts. * * Full URL with trailing slash. * * @since 2.6.0 * @var string */ public $base_url; /** * URL of the content directory. * * @since 2.8.0 * @var string */ public $content_url; /** * Default version string for stylesheets. * * @since 2.6.0 * @var string */ public $default_version; /** * Holds handles of scripts which are enqueued in footer. * * @since 2.8.0 * @var array */ public $in_footer = array(); /** * Holds a list of script handles which will be concatenated. * * @since 2.8.0 * @var string */ public $concat = ''; /** * Holds a string which contains script handles and their version. * * @since 2.8.0 * @deprecated 3.4.0 * @var string */ public $concat_version = ''; /** * Whether to perform concatenation. * * @since 2.8.0 * @var bool */ public $do_concat = false; /** * Holds HTML markup of scripts and additional data if concatenation * is enabled. * * @since 2.8.0 * @var string */ public $print_html = ''; /** * Holds inline code if concatenation is enabled. * * @since 2.8.0 * @var string */ public $print_code = ''; /** * Holds a list of script handles which are not in the default directory * if concatenation is enabled. * * Unused in core. * * @since 2.8.0 * @var string */ public $ext_handles = ''; /** * Holds a string which contains handles and versions of scripts which * are not in the default directory if concatenation is enabled. * * Unused in core. * * @since 2.8.0 * @var string */ public $ext_version = ''; /** * List of default directories. * * @since 2.8.0 * @var array */ public $default_dirs; /** * Constructor. * * @since 2.6.0 */ public function __construct() { $this->init(); add_action( 'init', array( $this, 'init' ), 0 ); } /** * Initialize the class. * * @since 3.4.0 */ public function init() { /** * Fires when the WP_Scripts instance is initialized. * * @since 2.6.0 * * @param WP_Scripts $this WP_Scripts instance (passed by reference). */ do_action_ref_array( 'wp_default_scripts', array(&$this) ); } /** * Prints scripts. * * Prints the scripts passed to it or the print queue. Also prints all necessary dependencies. * * @since 2.1.0 * @since 2.8.0 Added the `$group` parameter. * * @param mixed $handles Optional. Scripts to be printed. (void) prints queue, (string) prints * that script, (array of strings) prints those scripts. Default false. * @param int $group Optional. If scripts were queued in groups prints this group number. * Default false. * @return array Scripts that have been printed. */ public function print_scripts( $handles = false, $group = false ) { return $this->do_items( $handles, $group ); } /** * Prints extra scripts of a registered script. * * @since 2.1.0 * @since 2.8.0 Added the `$echo` parameter. * @deprecated 3.3.0 * * @see print_extra_script() * * @param string $handle The script's registered handle. * @param bool $echo Optional. Whether to echo the extra script instead of just returning it. * Default true. * @return bool|string|void Void if no data exists, extra scripts if `$echo` is true, true otherwise. */ public function print_scripts_l10n( $handle, $echo = true ) { _deprecated_function( __FUNCTION__, '3.3.0', 'WP_Scripts::print_extra_script()' ); return $this->print_extra_script( $handle, $echo ); } /** * Prints extra scripts of a registered script. * * @since 3.3.0 * * @param string $handle The script's registered handle. * @param bool $echo Optional. Whether to echo the extra script instead of just returning it. * Default true. * @return bool|string|void Void if no data exists, extra scripts if `$echo` is true, true otherwise. */ public function print_extra_script( $handle, $echo = true ) { if ( !$output = $this->get_data( $handle, 'data' ) ) return; if ( !$echo ) return $output; echo "\n"; return true; } /** * Processes a script dependency. * * @since 2.6.0 * @since 2.8.0 Added the `$group` parameter. * * @see WP_Dependencies::do_item() * * @param string $handle The script's registered handle. * @param int|false $group Optional. Group level: (int) level, (false) no groups. Default false. * @return bool True on success, false on failure. */ public function do_item( $handle, $group = false ) { if ( !parent::do_item($handle) ) return false; if ( 0 === $group && $this->groups[$handle] > 0 ) { $this->in_footer[] = $handle; return false; } if ( false === $group && in_array($handle, $this->in_footer, true) ) $this->in_footer = array_diff( $this->in_footer, (array) $handle ); $obj = $this->registered[$handle]; if ( null === $obj->ver ) { $ver = ''; } else { $ver = $obj->ver ? $obj->ver : $this->default_version; } if ( isset($this->args[$handle]) ) $ver = $ver ? $ver . '&' . $this->args[$handle] : $this->args[$handle]; $src = $obj->src; $cond_before = $cond_after = ''; $conditional = isset( $obj->extra['conditional'] ) ? $obj->extra['conditional'] : ''; if ( $conditional ) { $cond_before = "\n"; } $before_handle = $this->print_inline_script( $handle, 'before', false ); $after_handle = $this->print_inline_script( $handle, 'after', false ); if ( $before_handle ) { $before_handle = sprintf( "\n", $before_handle ); } if ( $after_handle ) { $after_handle = sprintf( "\n", $after_handle ); } if ( $this->do_concat ) { /** * Filters the script loader source. * * @since 2.2.0 * * @param string $src Script loader source path. * @param string $handle Script handle. */ $srce = apply_filters( 'script_loader_src', $src, $handle ); if ( $this->in_default_dir( $srce ) && ( $before_handle || $after_handle ) ) { $this->do_concat = false; // Have to print the so-far concatenated scripts right away to maintain the right order. _print_scripts(); $this->reset(); } elseif ( $this->in_default_dir( $srce ) && ! $conditional ) { $this->print_code .= $this->print_extra_script( $handle, false ); $this->concat .= "$handle,"; $this->concat_version .= "$handle$ver"; return true; } else { $this->ext_handles .= "$handle,"; $this->ext_version .= "$handle$ver"; } } $has_conditional_data = $conditional && $this->get_data( $handle, 'data' ); if ( $has_conditional_data ) { echo $cond_before; } $this->print_extra_script( $handle ); if ( $has_conditional_data ) { echo $cond_after; } // A single item may alias a set of items, by having dependencies, but no source. if ( ! $obj->src ) { return true; } if ( ! preg_match( '|^(https?:)?//|', $src ) && ! ( $this->content_url && 0 === strpos( $src, $this->content_url ) ) ) { $src = $this->base_url . $src; } if ( ! empty( $ver ) ) $src = add_query_arg( 'ver', $ver, $src ); /** This filter is documented in wp-includes/class.wp-scripts.php */ $src = esc_url( apply_filters( 'script_loader_src', $src, $handle ) ); if ( ! $src ) return true; $tag = "{$cond_before}{$before_handle}\n{$after_handle}{$cond_after}"; /** * Filters the HTML script tag of an enqueued script. * * @since 4.1.0 * * @param string $tag The `\n", $output ); } return $output; } /** * Localizes a script, only if the script has already been added. * * @since 2.1.0 * * @param string $handle * @param string $object_name * @param array $l10n * @return bool */ public function localize( $handle, $object_name, $l10n ) { if ( $handle === 'jquery' ) $handle = 'jquery-core'; if ( is_array($l10n) && isset($l10n['l10n_print_after']) ) { // back compat, preserve the code in 'l10n_print_after' if present $after = $l10n['l10n_print_after']; unset($l10n['l10n_print_after']); } foreach ( (array) $l10n as $key => $value ) { if ( !is_scalar($value) ) continue; $l10n[$key] = html_entity_decode( (string) $value, ENT_QUOTES, 'UTF-8'); } $script = "var $object_name = " . wp_json_encode( $l10n ) . ';'; if ( !empty($after) ) $script .= "\n$after;"; $data = $this->get_data( $handle, 'data' ); if ( !empty( $data ) ) $script = "$data\n$script"; return $this->add_data( $handle, 'data', $script ); } /** * Sets handle group. * * @since 2.8.0 * * @see WP_Dependencies::set_group() * * @param string $handle Name of the item. Should be unique. * @param bool $recursion Internal flag that calling function was called recursively. * @param int|false $group Optional. Group level: (int) level, (false) no groups. Default false. * @return bool Not already in the group or a lower group */ public function set_group( $handle, $recursion, $group = false ) { if ( isset( $this->registered[$handle]->args ) && $this->registered[$handle]->args === 1 ) $grp = 1; else $grp = (int) $this->get_data( $handle, 'group' ); if ( false !== $group && $grp > $group ) $grp = $group; return parent::set_group( $handle, $recursion, $grp ); } /** * Determines script dependencies. * * @since 2.1.0 * * @see WP_Dependencies::all_deps() * * @param mixed $handles Item handle and argument (string) or item handles and arguments (array of strings). * @param bool $recursion Internal flag that function is calling itself. * @param int|false $group Optional. Group level: (int) level, (false) no groups. Default false. * @return bool True on success, false on failure. */ public function all_deps( $handles, $recursion = false, $group = false ) { $r = parent::all_deps( $handles, $recursion, $group ); if ( ! $recursion ) { /** * Filters the list of script dependencies left to print. * * @since 2.3.0 * * @param array $to_do An array of script dependencies. */ $this->to_do = apply_filters( 'print_scripts_array', $this->to_do ); } return $r; } /** * Processes items and dependencies for the head group. * * @since 2.8.0 * * @see WP_Dependencies::do_items() * * @return array Handles of items that have been processed. */ public function do_head_items() { $this->do_items(false, 0); return $this->done; } /** * Processes items and dependencies for the footer group. * * @since 2.8.0 * * @see WP_Dependencies::do_items() * * @return array Handles of items that have been processed. */ public function do_footer_items() { $this->do_items(false, 1); return $this->done; } /** * Whether a handle's source is in a default directory. * * @since 2.8.0 * * @param string $src The source of the enqueued script. * @return bool True if found, false if not. */ public function in_default_dir( $src ) { if ( ! $this->default_dirs ) { return true; } if ( 0 === strpos( $src, '/' . WPINC . '/js/l10n' ) ) { return false; } foreach ( (array) $this->default_dirs as $test ) { if ( 0 === strpos( $src, $test ) ) { return true; } } return false; } /** * Resets class properties. * * @since 2.8.0 */ public function reset() { $this->do_concat = false; $this->print_code = ''; $this->concat = ''; $this->concat_version = ''; $this->print_html = ''; $this->ext_version = ''; $this->ext_handles = ''; } } if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Abstract Rest Terms Controller Class * * @author WooThemes * @category API * @package WooCommerce/Abstracts * @version 2.6.0 */ abstract class WC_REST_Terms_Controller extends WC_REST_Controller { /** * Route base. * * @var string */ protected $rest_base = ''; /** * Taxonomy. * * @var string */ protected $taxonomy = ''; /** * Register the routes for terms. */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'create_item' ), 'permission_callback' => array( $this, 'create_item_permissions_check' ), 'args' => array_merge( $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), array( 'name' => array( 'type' => 'string', 'description' => __( 'Name for the resource.', 'woocommerce' ), 'required' => true, ), ) ), ), 'schema' => array( $this, 'get_public_item_schema' ), )); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\d]+)', array( 'args' => array( 'id' => array( 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), 'type' => 'integer', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_item' ), 'permission_callback' => array( $this, 'update_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_item' ), 'permission_callback' => array( $this, 'delete_item_permissions_check' ), 'args' => array( 'force' => array( 'default' => false, 'type' => 'boolean', 'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ), ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', array( array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'batch_items' ), 'permission_callback' => array( $this, 'batch_items_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), ), 'schema' => array( $this, 'get_public_batch_schema' ), ) ); } /** * Check if a given request has access to read the terms. * * @param WP_REST_Request $request Full details about the request. * @return WP_Error|boolean */ public function get_items_permissions_check( $request ) { $permissions = $this->check_permissions( $request, 'read' ); if ( is_wp_error( $permissions ) ) { return $permissions; } if ( ! $permissions ) { return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Check if a given request has access to create a term. * * @param WP_REST_Request $request Full details about the request. * @return WP_Error|boolean */ public function create_item_permissions_check( $request ) { $permissions = $this->check_permissions( $request, 'create' ); if ( is_wp_error( $permissions ) ) { return $permissions; } if ( ! $permissions ) { return new WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Check if a given request has access to read a term. * * @param WP_REST_Request $request Full details about the request. * @return WP_Error|boolean */ public function get_item_permissions_check( $request ) { $permissions = $this->check_permissions( $request, 'read' ); if ( is_wp_error( $permissions ) ) { return $permissions; } if ( ! $permissions ) { return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Check if a given request has access to update a term. * * @param WP_REST_Request $request Full details about the request. * @return WP_Error|boolean */ public function update_item_permissions_check( $request ) { $permissions = $this->check_permissions( $request, 'edit' ); if ( is_wp_error( $permissions ) ) { return $permissions; } if ( ! $permissions ) { return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you are not allowed to edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Check if a given request has access to delete a term. * * @param WP_REST_Request $request Full details about the request. * @return WP_Error|boolean */ public function delete_item_permissions_check( $request ) { $permissions = $this->check_permissions( $request, 'delete' ); if ( is_wp_error( $permissions ) ) { return $permissions; } if ( ! $permissions ) { return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'Sorry, you are not allowed to delete this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Check if a given request has access batch create, update and delete items. * * @param WP_REST_Request $request Full details about the request. * @return boolean|WP_Error */ public function batch_items_permissions_check( $request ) { $permissions = $this->check_permissions( $request, 'batch' ); if ( is_wp_error( $permissions ) ) { return $permissions; } if ( ! $permissions ) { return new WP_Error( 'woocommerce_rest_cannot_batch', __( 'Sorry, you are not allowed to batch manipulate this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Check permissions. * * @param WP_REST_Request $request Full details about the request. * @param string $context Request context. * @return bool|WP_Error */ protected function check_permissions( $request, $context = 'read' ) { // Get taxonomy. $taxonomy = $this->get_taxonomy( $request ); if ( ! $taxonomy ) { return new WP_Error( 'woocommerce_rest_taxonomy_invalid', __( 'Taxonomy does not exist.', 'woocommerce' ), array( 'status' => 404 ) ); } // Check permissions for a single term. if ( $id = intval( $request['id'] ) ) { $term = get_term( $id, $taxonomy ); if ( ! $term || $term->taxonomy !== $taxonomy ) { return new WP_Error( 'woocommerce_rest_term_invalid', __( 'Resource does not exist.', 'woocommerce' ), array( 'status' => 404 ) ); } return wc_rest_check_product_term_permissions( $taxonomy, $context, $term->term_id ); } return wc_rest_check_product_term_permissions( $taxonomy, $context ); } /** * Get terms associated with a taxonomy. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error */ public function get_items( $request ) { $taxonomy = $this->get_taxonomy( $request ); $prepared_args = array( 'exclude' => $request['exclude'], 'include' => $request['include'], 'order' => $request['order'], 'orderby' => $request['orderby'], 'product' => $request['product'], 'hide_empty' => $request['hide_empty'], 'number' => $request['per_page'], 'search' => $request['search'], 'slug' => $request['slug'], ); if ( ! empty( $request['offset'] ) ) { $prepared_args['offset'] = $request['offset']; } else { $prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number']; } $taxonomy_obj = get_taxonomy( $taxonomy ); if ( $taxonomy_obj->hierarchical && isset( $request['parent'] ) ) { if ( 0 === $request['parent'] ) { // Only query top-level terms. $prepared_args['parent'] = 0; } else { if ( $request['parent'] ) { $prepared_args['parent'] = $request['parent']; } } } /** * Filter the query arguments, before passing them to `get_terms()`. * * Enables adding extra arguments or setting defaults for a terms * collection request. * * @see https://developer.wordpress.org/reference/functions/get_terms/ * * @param array $prepared_args Array of arguments to be * passed to get_terms. * @param WP_REST_Request $request The current request. */ $prepared_args = apply_filters( "woocommerce_rest_{$taxonomy}_query", $prepared_args, $request ); if ( ! empty( $prepared_args['product'] ) ) { $query_result = $this->get_terms_for_product( $prepared_args, $request ); $total_terms = $this->total_terms; } else { $query_result = get_terms( $taxonomy, $prepared_args ); $count_args = $prepared_args; unset( $count_args['number'] ); unset( $count_args['offset'] ); $total_terms = wp_count_terms( $taxonomy, $count_args ); // Ensure we don't return results when offset is out of bounds. // See https://core.trac.wordpress.org/ticket/35935 if ( $prepared_args['offset'] >= $total_terms ) { $query_result = array(); } // wp_count_terms can return a falsy value when the term has no children. if ( ! $total_terms ) { $total_terms = 0; } } $response = array(); foreach ( $query_result as $term ) { $data = $this->prepare_item_for_response( $term, $request ); $response[] = $this->prepare_response_for_collection( $data ); } $response = rest_ensure_response( $response ); // Store pagination values for headers then unset for count query. $per_page = (int) $prepared_args['number']; $page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 ); $response->header( 'X-WP-Total', (int) $total_terms ); $max_pages = ceil( $total_terms / $per_page ); $response->header( 'X-WP-TotalPages', (int) $max_pages ); $base = add_query_arg( $request->get_query_params(), rest_url( '/' . $this->namespace . '/' . $this->rest_base ) ); if ( $page > 1 ) { $prev_page = $page - 1; if ( $prev_page > $max_pages ) { $prev_page = $max_pages; } $prev_link = add_query_arg( 'page', $prev_page, $base ); $response->link_header( 'prev', $prev_link ); } if ( $max_pages > $page ) { $next_page = $page + 1; $next_link = add_query_arg( 'page', $next_page, $base ); $response->link_header( 'next', $next_link ); } return $response; } /** * Create a single term for a taxonomy. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Request|WP_Error */ public function create_item( $request ) { $taxonomy = $this->get_taxonomy( $request ); $name = $request['name']; $args = array(); $schema = $this->get_item_schema(); if ( ! empty( $schema['properties']['description'] ) && isset( $request['description'] ) ) { $args['description'] = $request['description']; } if ( isset( $request['slug'] ) ) { $args['slug'] = $request['slug']; } if ( isset( $request['parent'] ) ) { if ( ! is_taxonomy_hierarchical( $taxonomy ) ) { return new WP_Error( 'woocommerce_rest_taxonomy_not_hierarchical', __( 'Can not set resource parent, taxonomy is not hierarchical.', 'woocommerce' ), array( 'status' => 400 ) ); } $parent = get_term( (int) $request['parent'], $taxonomy ); // If is null or WP_Error is invalid parent term. if ( ! $parent || is_wp_error( $parent ) ) { return new WP_Error( 'woocommerce_rest_term_invalid', __( 'Parent resource does not exist.', 'woocommerce' ), array( 'status' => 404 ) ); } $args['parent'] = $parent->term_id; } $term = wp_insert_term( $name, $taxonomy, $args ); if ( is_wp_error( $term ) ) { $error_data = array( 'status' => 400 ); // If we're going to inform the client that the term exists, // give them the identifier they can actually use. if ( $term_id = $term->get_error_data( 'term_exists' ) ) { $error_data['resource_id'] = $term_id; } return new WP_Error( $term->get_error_code(), $term->get_error_message(), $error_data ); } $term = get_term( $term['term_id'], $taxonomy ); $this->update_additional_fields_for_object( $term, $request ); // Add term data. $meta_fields = $this->update_term_meta_fields( $term, $request ); if ( is_wp_error( $meta_fields ) ) { wp_delete_term( $term->term_id, $taxonomy ); return $meta_fields; } /** * Fires after a single term is created or updated via the REST API. * * @param WP_Term $term Inserted Term object. * @param WP_REST_Request $request Request object. * @param boolean $creating True when creating term, false when updating. */ do_action( "woocommerce_rest_insert_{$taxonomy}", $term, $request, true ); $request->set_param( 'context', 'edit' ); $response = $this->prepare_item_for_response( $term, $request ); $response = rest_ensure_response( $response ); $response->set_status( 201 ); $base = '/' . $this->namespace . '/' . $this->rest_base; if ( ! empty( $request['attribute_id'] ) ) { $base = str_replace( '(?P[\d]+)', (int) $request['attribute_id'], $base ); } $response->header( 'Location', rest_url( $base . '/' . $term->term_id ) ); return $response; } /** * Get a single term from a taxonomy. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Request|WP_Error */ public function get_item( $request ) { $taxonomy = $this->get_taxonomy( $request ); $term = get_term( (int) $request['id'], $taxonomy ); if ( is_wp_error( $term ) ) { return $term; } $response = $this->prepare_item_for_response( $term, $request ); return rest_ensure_response( $response ); } /** * Update a single term from a taxonomy. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Request|WP_Error */ public function update_item( $request ) { $taxonomy = $this->get_taxonomy( $request ); $term = get_term( (int) $request['id'], $taxonomy ); $schema = $this->get_item_schema(); $prepared_args = array(); if ( isset( $request['name'] ) ) { $prepared_args['name'] = $request['name']; } if ( ! empty( $schema['properties']['description'] ) && isset( $request['description'] ) ) { $prepared_args['description'] = $request['description']; } if ( isset( $request['slug'] ) ) { $prepared_args['slug'] = $request['slug']; } if ( isset( $request['parent'] ) ) { if ( ! is_taxonomy_hierarchical( $taxonomy ) ) { return new WP_Error( 'woocommerce_rest_taxonomy_not_hierarchical', __( 'Can not set resource parent, taxonomy is not hierarchical.', 'woocommerce' ), array( 'status' => 400 ) ); } $parent_id = (int) $request['parent']; if ( 0 === $parent_id ) { $prepared_args['parent'] = $parent_id; } else { $parent = get_term( $parent_id, $taxonomy ); if ( ! $parent ) { return new WP_Error( 'woocommerce_rest_term_invalid', __( 'Parent resource does not exist.', 'woocommerce' ), array( 'status' => 400 ) ); } $prepared_args['parent'] = $parent->term_id; } } // Only update the term if we haz something to update. if ( ! empty( $prepared_args ) ) { $update = wp_update_term( $term->term_id, $term->taxonomy, $prepared_args ); if ( is_wp_error( $update ) ) { return $update; } } $term = get_term( (int) $request['id'], $taxonomy ); $this->update_additional_fields_for_object( $term, $request ); // Update term data. $meta_fields = $this->update_term_meta_fields( $term, $request ); if ( is_wp_error( $meta_fields ) ) { return $meta_fields; } /** * Fires after a single term is created or updated via the REST API. * * @param WP_Term $term Inserted Term object. * @param WP_REST_Request $request Request object. * @param boolean $creating True when creating term, false when updating. */ do_action( "woocommerce_rest_insert_{$taxonomy}", $term, $request, false ); $request->set_param( 'context', 'edit' ); $response = $this->prepare_item_for_response( $term, $request ); return rest_ensure_response( $response ); } /** * Delete a single term from a taxonomy. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error */ public function delete_item( $request ) { $taxonomy = $this->get_taxonomy( $request ); $force = isset( $request['force'] ) ? (bool) $request['force'] : false; // We don't support trashing for this type, error out. if ( ! $force ) { return new WP_Error( 'woocommerce_rest_trash_not_supported', __( 'Resource does not support trashing.', 'woocommerce' ), array( 'status' => 501 ) ); } $term = get_term( (int) $request['id'], $taxonomy ); $request->set_param( 'context', 'edit' ); $response = $this->prepare_item_for_response( $term, $request ); $retval = wp_delete_term( $term->term_id, $term->taxonomy ); if ( ! $retval ) { return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'The resource cannot be deleted.', 'woocommerce' ), array( 'status' => 500 ) ); } /** * Fires after a single term is deleted via the REST API. * * @param WP_Term $term The deleted term. * @param WP_REST_Response $response The response data. * @param WP_REST_Request $request The request sent to the API. */ do_action( "woocommerce_rest_delete_{$taxonomy}", $term, $response, $request ); return $response; } /** * Prepare links for the request. * * @param object $term Term object. * @param WP_REST_Request $request Full details about the request. * @return array Links for the given term. */ protected function prepare_links( $term, $request ) { $base = '/' . $this->namespace . '/' . $this->rest_base; if ( ! empty( $request['attribute_id'] ) ) { $base = str_replace( '(?P[\d]+)', (int) $request['attribute_id'], $base ); } $links = array( 'self' => array( 'href' => rest_url( trailingslashit( $base ) . $term->term_id ), ), 'collection' => array( 'href' => rest_url( $base ), ), ); if ( $term->parent ) { $parent_term = get_term( (int) $term->parent, $term->taxonomy ); if ( $parent_term ) { $links['up'] = array( 'href' => rest_url( trailingslashit( $base ) . $parent_term->term_id ), ); } } return $links; } /** * Update term meta fields. * * @param WP_Term $term * @param WP_REST_Request $request * @return bool|WP_Error */ protected function update_term_meta_fields( $term, $request ) { return true; } /** * Get the terms attached to a product. * * This is an alternative to `get_terms()` that uses `get_the_terms()` * instead, which hits the object cache. There are a few things not * supported, notably `include`, `exclude`. In `self::get_items()` these * are instead treated as a full query. * * @param array $prepared_args Arguments for `get_terms()`. * @param WP_REST_Request $request Full details about the request. * @return array List of term objects. (Total count in `$this->total_terms`). */ protected function get_terms_for_product( $prepared_args, $request ) { $taxonomy = $this->get_taxonomy( $request ); $query_result = get_the_terms( $prepared_args['product'], $taxonomy ); if ( empty( $query_result ) ) { $this->total_terms = 0; return array(); } // get_items() verifies that we don't have `include` set, and default. // ordering is by `name`. if ( ! in_array( $prepared_args['orderby'], array( 'name', 'none', 'include' ) ) ) { switch ( $prepared_args['orderby'] ) { case 'id' : $this->sort_column = 'term_id'; break; case 'slug' : case 'term_group' : case 'description' : case 'count' : $this->sort_column = $prepared_args['orderby']; break; } usort( $query_result, array( $this, 'compare_terms' ) ); } if ( strtolower( $prepared_args['order'] ) !== 'asc' ) { $query_result = array_reverse( $query_result ); } // Pagination. $this->total_terms = count( $query_result ); $query_result = array_slice( $query_result, $prepared_args['offset'], $prepared_args['number'] ); return $query_result; } /** * Comparison function for sorting terms by a column. * * Uses `$this->sort_column` to determine field to sort by. * * @param stdClass $left Term object. * @param stdClass $right Term object. * @return int <0 if left is higher "priority" than right, 0 if equal, >0 if right is higher "priority" than left. */ protected function compare_terms( $left, $right ) { $col = $this->sort_column; $left_val = $left->$col; $right_val = $right->$col; if ( is_int( $left_val ) && is_int( $right_val ) ) { return $left_val - $right_val; } return strcmp( $left_val, $right_val ); } /** * Get the query params for collections * * @return array */ public function get_collection_params() { $params = parent::get_collection_params(); if ( '' !== $this->taxonomy ) { $taxonomy = get_taxonomy( $this->taxonomy ); } else { $taxonomy = new stdClass(); $taxonomy->hierarchical = true; } $params['context']['default'] = 'view'; $params['exclude'] = array( 'description' => __( 'Ensure result set excludes specific ids.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), 'sanitize_callback' => 'wp_parse_id_list', ); $params['include'] = array( 'description' => __( 'Limit result set to specific ids.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), 'sanitize_callback' => 'wp_parse_id_list', ); if ( ! $taxonomy->hierarchical ) { $params['offset'] = array( 'description' => __( 'Offset the result set by a specific number of items.', 'woocommerce' ), 'type' => 'integer', 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ); } $params['order'] = array( 'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_key', 'default' => 'asc', 'enum' => array( 'asc', 'desc', ), 'validate_callback' => 'rest_validate_request_arg', ); $params['orderby'] = array( 'description' => __( 'Sort collection by resource attribute.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_key', 'default' => 'name', 'enum' => array( 'id', 'include', 'name', 'slug', 'term_group', 'description', 'count', ), 'validate_callback' => 'rest_validate_request_arg', ); $params['hide_empty'] = array( 'description' => __( 'Whether to hide resources not assigned to any products.', 'woocommerce' ), 'type' => 'boolean', 'default' => false, 'validate_callback' => 'rest_validate_request_arg', ); if ( $taxonomy->hierarchical ) { $params['parent'] = array( 'description' => __( 'Limit result set to resources assigned to a specific parent.', 'woocommerce' ), 'type' => 'integer', 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ); } $params['product'] = array( 'description' => __( 'Limit result set to resources assigned to a specific product.', 'woocommerce' ), 'type' => 'integer', 'default' => null, 'validate_callback' => 'rest_validate_request_arg', ); $params['slug'] = array( 'description' => __( 'Limit result set to resources with a specific slug.', 'woocommerce' ), 'type' => 'string', 'validate_callback' => 'rest_validate_request_arg', ); return $params; } /** * Get taxonomy. * * @param WP_REST_Request $request Full details about the request. * @return int|WP_Error */ protected function get_taxonomy( $request ) { // Check if taxonomy is defined. // Prevents check for attribute taxonomy more than one time for each query. if ( '' !== $this->taxonomy ) { return $this->taxonomy; } if ( ! empty( $request['attribute_id'] ) ) { $taxonomy = wc_attribute_taxonomy_name_by_id( (int) $request['attribute_id'] ); $this->taxonomy = $taxonomy; } return $this->taxonomy; } }