woocommerce/plugins/woocommerce-blocks/includes/class-wgpb-products-control...

392 lines
13 KiB
PHP
Raw Normal View History

<?php
/**
* REST API Products controller customized for Products Block.
*
* Handles requests to the /products endpoint.
*
* @package WooCommerce\Blocks\Products\Rest\Controller
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* REST API Products controller class.
*
* @package WooCommerce/API
*/
class WGPB_Products_Controller extends WC_REST_Products_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc-pb/v3';
/**
* Register the routes for products.
*/
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(),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<id>[\d]+)',
array(
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woo-gutenberg-products-block' ),
'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',
)
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Check if a given request has access to read items.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( ! current_user_can( 'edit_posts' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woo-gutenberg-products-block' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check if a given request has access to read an item.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_item_permissions_check( $request ) {
if ( ! current_user_can( 'edit_posts' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woo-gutenberg-products-block' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Get a collection of posts.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$query_args = $this->prepare_objects_query( $request );
$query_results = $this->get_objects( $query_args );
$objects = array();
foreach ( $query_results['objects'] as $object ) {
$data = $this->prepare_object_for_response( $object, $request );
$objects[] = $this->prepare_response_for_collection( $data );
}
$page = (int) $query_args['paged'];
$max_pages = $query_results['pages'];
$response = rest_ensure_response( $objects );
$response->header( 'X-WP-Total', $query_results['total'] );
$response->header( 'X-WP-TotalPages', (int) $max_pages );
$base = $this->rest_base;
$attrib_prefix = '(?P<';
if ( strpos( $base, $attrib_prefix ) !== false ) {
$attrib_names = array();
preg_match( '/\(\?P<[^>]+>.*\)/', $base, $attrib_names, PREG_OFFSET_CAPTURE );
foreach ( $attrib_names as $attrib_name_match ) {
$beginning_offset = strlen( $attrib_prefix );
$attrib_name_end = strpos( $attrib_name_match[0], '>', $attrib_name_match[1] );
$attrib_name = substr( $attrib_name_match[0], $beginning_offset, $attrib_name_end - $beginning_offset );
if ( isset( $request[ $attrib_name ] ) ) {
$base = str_replace( "(?P<$attrib_name>[\d]+)", $request[ $attrib_name ], $base );
}
}
}
$base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $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;
}
/**
* Get the images for a product or product variation.
*
* @param WC_Product|WC_Product_Variation $product Product instance.
* @return array
*/
protected function get_images( $product ) {
$images = array();
$attachment_ids = array();
// Add featured image.
if ( has_post_thumbnail( $product->get_id() ) ) {
$attachment_ids[] = $product->get_image_id();
}
// Add gallery images.
$attachment_ids = array_merge( $attachment_ids, $product->get_gallery_image_ids() );
// Build image data.
foreach ( $attachment_ids as $attachment_id ) {
$attachment_post = get_post( $attachment_id );
if ( is_null( $attachment_post ) ) {
continue;
}
$attachment = wp_get_attachment_image_src( $attachment_id, 'full' );
if ( ! is_array( $attachment ) ) {
continue;
}
$images[] = array(
'id' => (int) $attachment_id,
'src' => current( $attachment ),
'name' => get_the_title( $attachment_id ),
'alt' => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ),
);
}
return $images;
}
/**
* Prepare a single product output for response.
*
* @deprecated 3.0.0
*
* @param WP_Post $post Post object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response
*/
public function prepare_item_for_response( $post, $request ) {
$product = wc_get_product( $post );
$data = $this->get_product_data( $product );
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $product, $request ) );
return $response;
}
/**
* Make extra product orderby features supported by WooCommerce available to the WC API.
* This includes 'price', 'popularity', and 'rating'.
*
* @param WP_REST_Request $request Request data.
* @return array
*/
protected function prepare_objects_query( $request ) {
$args = parent::prepare_objects_query( $request );
$orderby = $request->get_param( 'orderby' );
$order = $request->get_param( 'order' );
$cat_operator = $request->get_param( 'cat_operator' );
$attributes = $request->get_param( 'attributes' );
$attr_operator = $request->get_param( 'attr_operator' );
$ordering_args = WC()->query->get_catalog_ordering_args( $orderby, $order );
$args['orderby'] = $ordering_args['orderby'];
$args['order'] = $ordering_args['order'];
if ( $ordering_args['meta_key'] ) {
$args['meta_key'] = $ordering_args['meta_key']; // WPCS: slow query ok.
}
if ( $cat_operator && isset( $args['tax_query'] ) ) {
foreach ( $args['tax_query'] as $i => $tax_query ) {
if ( 'product_cat' === $tax_query['taxonomy'] ) {
$args['tax_query'][ $i ]['operator'] = $cat_operator;
$args['tax_query'][ $i ]['include_children'] = 'AND' === $cat_operator ? false : true;
}
}
}
$tax_query = array();
if ( $attributes ) {
foreach ( $attributes as $attribute => $attribute_terms ) {
if ( in_array( $attribute, wc_get_attribute_taxonomy_names(), true ) ) {
$tax_query[] = array(
'taxonomy' => $attribute,
'field' => 'term_id',
'terms' => $attribute_terms,
'operator' => ! $attr_operator ? 'IN' : $attr_operator,
);
}
}
}
// Merge attribute `$tax_query`s into the request's WP_Query args.
if ( ! empty( $tax_query ) ) {
if ( ! empty( $args['tax_query'] ) ) {
$args['tax_query'] = array_merge( $tax_query, $args['tax_query'] ); // WPCS: slow query ok.
} else {
$args['tax_query'] = $tax_query; // WPCS: slow query ok.
}
$args['tax_query']['relation'] = 'AND' === $attr_operator ? 'AND' : 'OR';
}
return $args;
}
/**
* Get product data.
*
* @param WC_Product $product Product instance.
* @param string $context Request context.
* Options: 'view' and 'edit'.
* @return array
*/
protected function get_product_data( $product, $context = 'view' ) {
$raw_data = parent::get_product_data( $product, $context );
$data = array();
$data['id'] = $raw_data['id'];
$data['name'] = $raw_data['name'];
$data['sku'] = $raw_data['sku'];
$data['description'] = $raw_data['description'];
$data['short_description'] = $raw_data['short_description'];
$data['price'] = $raw_data['price'];
$data['price_html'] = $raw_data['price_html'];
$data['images'] = $raw_data['images'];
return $data;
}
/**
* Update the collection params.
*
* Adds new options for 'orderby', and new parameter 'cat_operator'.
*
* @return array
*/
public function get_collection_params() {
$params = parent::get_collection_params();
$params['orderby']['enum'] = array_merge( $params['orderby']['enum'], array( 'price', 'popularity', 'rating', 'menu_order' ) );
$params['cat_operator'] = array(
'description' => __( 'Operator to compare product category terms.', 'woo-gutenberg-products-block' ),
'type' => 'string',
'enum' => array( 'IN', 'NOT IN', 'AND' ),
'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => 'rest_validate_request_arg',
);
$params['attr_operator'] = array(
'description' => __( 'Operator to compare product attribute terms.', 'woo-gutenberg-products-block' ),
'type' => 'string',
'enum' => array( 'IN', 'AND' ),
'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => 'rest_validate_request_arg',
);
$attr_properties = array();
foreach ( wc_get_attribute_taxonomy_names() as $name ) {
$attr_properties[ $name ] = array(
'type' => 'array',
'items' => array( 'type' => 'string' ),
);
}
$params['attributes'] = array(
'description' => __( 'Map of attributes to selected terms.', 'woo-gutenberg-products-block' ),
'type' => 'object',
'validate_callback' => 'rest_validate_request_arg',
);
if ( ! empty( $attr_properties ) ) {
$params['attributes']['properties'] = $attr_properties;
$params['attributes']['additionalProperties'] = false;
}
return $params;
}
/**
* Get the Product's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$raw_schema = parent::get_item_schema();
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'product_block_product',
'type' => 'object',
'properties' => array(),
);
$schema['properties']['id'] = $raw_schema['properties']['id'];
$schema['properties']['name'] = $raw_schema['properties']['name'];
$schema['properties']['sku'] = $raw_schema['properties']['sku'];
$schema['properties']['description'] = $raw_schema['properties']['description'];
$schema['properties']['short_description'] = $raw_schema['properties']['short_description'];
$schema['properties']['price'] = $raw_schema['properties']['price'];
$schema['properties']['price_html'] = $raw_schema['properties']['price_html'];
$schema['properties']['images'] = array(
'description' => $raw_schema['properties']['images']['description'],
'type' => 'object',
'context' => array( 'view', 'edit' ),
'items' => array(
'type' => 'object',
'properties' => array(),
),
);
$images_schema = $raw_schema['properties']['images']['items']['properties'];
$schema['properties']['images']['items']['properties']['id'] = $images_schema['id'];
$schema['properties']['images']['items']['properties']['src'] = $images_schema['src'];
$schema['properties']['images']['items']['properties']['name'] = $images_schema['name'];
$schema['properties']['images']['items']['properties']['alt'] = $images_schema['alt'];
return $this->add_additional_fields_schema( $schema );
}
}