* REST API Product Attribute Terms Controller
* Handles requests to /products/attributes/<slug>/terms
namespace Automattic\WooCommerce\Admin\API;
defined( 'ABSPATH' ) || exit;
* Product attribute terms controller.
* @extends WC_REST_Product_Attribute_Terms_Controller
class ProductAttributeTerms extends \WC_REST_Product_Attribute_Terms_Controller {
use CustomAttributeTraits;
protected $namespace = 'wc-analytics';
* Register the routes for custom product attributes.
public function register_routes() {
parent::register_routes();
'products/attributes/(?P<slug>[a-z0-9_\-]+)/terms',
'description' => __( 'Slug identifier for the resource.', 'woocommerce' ),
'methods' => \WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item_by_slug' ),
'permission_callback' => array( $this, 'get_custom_attribute_permissions_check' ),
'args' => $this->get_collection_params(),
'schema' => array( $this, 'get_public_item_schema' ),
* Check if a given request has access to read a custom attribute.
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
public function get_custom_attribute_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'attributes', 'read' ) ) {
'woocommerce_rest_cannot_view',
__( 'Sorry, you cannot view this resource.', 'woocommerce' ),
'status' => rest_authorization_required_code(),
* Get the Attribute's schema, conforming to JSON Schema.
public function get_item_schema() {
$schema = parent::get_item_schema();
// Custom attributes substitute slugs for numeric IDs.
$schema['properties']['id']['type'] = array( 'integer', 'string' );
* Query custom attribute values by slug.
* @param string $slug Attribute slug.
* @return array Attribute values, formatted for response.
protected function get_custom_attribute_values( $slug ) {
$attribute_values = array();
// Get the attribute properties.
$attribute = $this->get_custom_attribute_by_slug( $slug );
if ( is_wp_error( $attribute ) ) {
// Find all attribute values assigned to products.
$query_results = $wpdb->get_results(
"SELECT meta_value, COUNT(meta_id) AS product_count
'attribute_' . esc_sql( $slug )
// Ensure all defined properties are in the response.
$defined_values = wc_get_text_attributes( $attribute[ $slug ]['value'] );
foreach ( $defined_values as $defined_value ) {
if ( array_key_exists( $defined_value, $query_results ) ) {
$query_results[ $defined_value ] = (object) array(
'meta_value' => $defined_value, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
foreach ( $query_results as $term_value => $term ) {
// Mimic the structure of a taxonomy-backed attribute values for response.
'count' => (int) $term->product_count,
$response = rest_ensure_response( $data );
$this->namespace . '/products/attributes/' . $slug . '/terms'
$response = $this->prepare_response_for_collection( $response );
$attribute_values[ $term_value ] = $response;
return array_values( $attribute_values );
* Get a single custom attribute.
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Request|WP_Error
public function get_item_by_slug( $request ) {
return $this->get_custom_attribute_values( $request['slug'] );