Featured Product Block: Make it possible to feature a specific product variation (https://github.com/woocommerce/woocommerce-blocks/pull/608)
* Duplicate SearchListControl into ProductSearchListControl component * Undo control copy; it's not needed * Variation search + counts showing * Dropdown styling (also fixed SVG icon markup in SCSS file) * Style the variation count * Handle variation display on frontend and backend * Fixed selection callbacks hooray * Extend v3 api to return name. Use v3 API for featured product block. * Switch description based on type * Fix isSelected check * Define a11yProps * Variations rest endpoint * Remove isTertiary * REST endpoints with variation handling * Handle variation data frontend * Handle variation data in editor * renamed description schema * tweak variation display * Update assets/js/components/product-control/style.scss Co-Authored-By: Albert Juhé Lluveras <contact@albertjuhe.com> * Flip icon direction * Use classnames * fix isSingle warning * standards * Only try to load variations if product has them
This commit is contained in:
parent
3625225892
commit
5362b468dc
|
@ -29,7 +29,7 @@ import {
|
|||
import classnames from 'classnames';
|
||||
import { Component, Fragment } from '@wordpress/element';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { debounce, isObject } from 'lodash';
|
||||
import { debounce, isObject, isEmpty } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
|
@ -193,7 +193,7 @@ class FeaturedProduct extends Component {
|
|||
className="wc-block-featured-product"
|
||||
>
|
||||
{ __(
|
||||
'Visually highlight a product and encourage prompt action',
|
||||
'Visually highlight a product or variation and encourage prompt action',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
<div className="wc-block-handpicked-products__selection">
|
||||
|
@ -305,11 +305,19 @@ class FeaturedProduct extends Component {
|
|||
__html: product.name,
|
||||
} }
|
||||
/>
|
||||
{ ! isEmpty( product.variation ) && (
|
||||
<h3
|
||||
className="wc-block-featured-product__variation"
|
||||
dangerouslySetInnerHTML={ {
|
||||
__html: product.variation,
|
||||
} }
|
||||
/>
|
||||
) }
|
||||
{ showDesc && (
|
||||
<div
|
||||
className="wc-block-featured-product__description"
|
||||
dangerouslySetInnerHTML={ {
|
||||
__html: product.short_description,
|
||||
__html: product.description,
|
||||
} }
|
||||
/>
|
||||
) }
|
||||
|
|
|
@ -24,7 +24,7 @@ registerBlockType( 'woocommerce/featured-product', {
|
|||
category: 'woocommerce',
|
||||
keywords: [ __( 'WooCommerce', 'woo-gutenberg-products-block' ) ],
|
||||
description: __(
|
||||
'Visually highlight a product and encourage prompt action.',
|
||||
'Visually highlight a product or variation and encourage prompt action.',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
supports: {
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
justify-content: flex-start;
|
||||
|
||||
.wc-block-featured-product__title,
|
||||
.wc-block-featured-product__variation,
|
||||
.wc-block-featured-product__description,
|
||||
.wc-block-featured-product__price {
|
||||
margin-left: 0;
|
||||
|
@ -36,6 +37,7 @@
|
|||
justify-content: flex-end;
|
||||
|
||||
.wc-block-featured-product__title,
|
||||
.wc-block-featured-product__variation,
|
||||
.wc-block-featured-product__description,
|
||||
.wc-block-featured-product__price {
|
||||
margin-right: 0;
|
||||
|
@ -44,6 +46,7 @@
|
|||
}
|
||||
|
||||
.wc-block-featured-product__title,
|
||||
.wc-block-featured-product__variation,
|
||||
.wc-block-featured-product__description,
|
||||
.wc-block-featured-product__price {
|
||||
color: $white;
|
||||
|
@ -60,25 +63,34 @@
|
|||
}
|
||||
|
||||
.wc-block-featured-product__title,
|
||||
.wc-block-featured-product__variation,
|
||||
.wc-block-featured-product__description,
|
||||
.wc-block-featured-product__price,
|
||||
.wc-block-featured-product__link {
|
||||
width: 100%;
|
||||
padding: 0 48px 16px 48px;
|
||||
padding: 16px 48px 0 48px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.wc-block-featured-product__title {
|
||||
.wc-block-featured-product__title,
|
||||
.wc-block-featured-product__variation {
|
||||
margin-top: 0;
|
||||
border: 0;
|
||||
|
||||
&:before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-featured-product__variation {
|
||||
font-style: italic;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.wc-block-featured-product__description {
|
||||
p {
|
||||
margin: 0;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,14 +42,14 @@
|
|||
content: '';
|
||||
height: $gap-large;
|
||||
width: $gap-large;
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z" fill="#{$core-grey-dark-300}" /></svg>');
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z" fill="{$core-grey-dark-300}" /></svg>');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center right;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
&.depth-0[aria-expanded="true"]::after {
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z" fill="#{$core-grey-dark-300}" /></svg>');
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z" fill="{$core-grey-dark-300}" /></svg>');
|
||||
}
|
||||
|
||||
&[disabled].depth-0::after {
|
||||
|
|
|
@ -1,54 +1,245 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { __, _n, sprintf } from '@wordpress/i18n';
|
||||
import { Component, Fragment } from '@wordpress/element';
|
||||
import { debounce, find } from 'lodash';
|
||||
import { addQueryArgs } from '@wordpress/url';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { debounce, find, escapeRegExp, isEmpty } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import { SearchListControl } from '@woocommerce/components';
|
||||
import {
|
||||
SearchListControl,
|
||||
SearchListItem,
|
||||
} from '@woocommerce/components';
|
||||
import { Spinner, MenuItem } from '@wordpress/components';
|
||||
import classnames from 'classnames';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { isLargeCatalog, getProducts } from '../utils';
|
||||
import {
|
||||
IconRadioSelected,
|
||||
IconRadioUnselected,
|
||||
} from '../icons';
|
||||
import './style.scss';
|
||||
|
||||
function getHighlightedName( name, search ) {
|
||||
if ( ! search ) {
|
||||
return name;
|
||||
}
|
||||
const re = new RegExp( escapeRegExp( search ), 'ig' );
|
||||
return name.replace( re, '<strong>$&</strong>' );
|
||||
}
|
||||
|
||||
const getInteractionIcon = ( isSelected = false ) => {
|
||||
return isSelected ? <IconRadioSelected /> : <IconRadioUnselected />;
|
||||
};
|
||||
|
||||
class ProductControl extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
this.state = {
|
||||
list: [],
|
||||
products: [],
|
||||
product: 0,
|
||||
variationsList: {},
|
||||
variationsLoading: false,
|
||||
loading: true,
|
||||
};
|
||||
|
||||
this.debouncedOnSearch = debounce( this.onSearch.bind( this ), 400 );
|
||||
this.debouncedGetVariations = debounce( this.getVariations.bind( this ), 200 );
|
||||
this.renderItem = this.renderItem.bind( this );
|
||||
this.onProductSelect = this.onProductSelect.bind( this );
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { selected } = this.props;
|
||||
|
||||
getProducts( { selected } )
|
||||
.then( ( list ) => {
|
||||
this.setState( { list, loading: false } );
|
||||
.then( ( products ) => {
|
||||
products = products.map( ( product ) => {
|
||||
const count = product.variations ? product.variations.length : 0;
|
||||
return {
|
||||
...product,
|
||||
parent: 0,
|
||||
count: count,
|
||||
};
|
||||
} );
|
||||
this.setState( { products, loading: false } );
|
||||
} )
|
||||
.catch( () => {
|
||||
this.setState( { list: [], loading: false } );
|
||||
this.setState( { products: [], loading: false } );
|
||||
} );
|
||||
}
|
||||
|
||||
componentDidUpdate( prevProps, prevState ) {
|
||||
if ( prevState.product !== this.state.product ) {
|
||||
this.debouncedGetVariations();
|
||||
}
|
||||
}
|
||||
|
||||
getVariations() {
|
||||
const { product, variationsList } = this.state;
|
||||
|
||||
if ( ! product ) {
|
||||
this.setState( {
|
||||
variationsList: {},
|
||||
variationsLoading: false,
|
||||
} );
|
||||
return;
|
||||
}
|
||||
|
||||
const productDetails = this.state.products.find( ( findProduct ) => findProduct.id === product );
|
||||
|
||||
if ( ! productDetails.variations || productDetails.variations.length === 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! variationsList[ product ] ) {
|
||||
this.setState( { variationsLoading: true } );
|
||||
}
|
||||
|
||||
apiFetch( {
|
||||
path: addQueryArgs( `/wc/blocks/products/${ product }/variations`, {
|
||||
per_page: -1,
|
||||
} ),
|
||||
} )
|
||||
.then( ( variations ) => {
|
||||
variations = variations.map( ( variation ) => ( { ...variation, parent: product } ) );
|
||||
this.setState( ( prevState ) => ( {
|
||||
variationsList: { ...prevState.variationsList, [ product ]: variations },
|
||||
variationsLoading: false,
|
||||
} ) );
|
||||
} )
|
||||
.catch( () => {
|
||||
this.setState( { termsLoading: false } );
|
||||
} );
|
||||
}
|
||||
|
||||
onSearch( search ) {
|
||||
const { selected } = this.props;
|
||||
getProducts( { selected, search } )
|
||||
.then( ( list ) => {
|
||||
this.setState( { list, loading: false } );
|
||||
.then( ( products ) => {
|
||||
this.setState( { products, loading: false } );
|
||||
} )
|
||||
.catch( () => {
|
||||
this.setState( { list: [], loading: false } );
|
||||
this.setState( { products: [], loading: false } );
|
||||
} );
|
||||
}
|
||||
|
||||
onProductSelect( item, isSelected ) {
|
||||
return () => {
|
||||
this.setState( {
|
||||
product: isSelected ? 0 : item.id,
|
||||
} );
|
||||
};
|
||||
}
|
||||
|
||||
renderItem( args ) {
|
||||
const { item, search, depth = 0, isSelected, onSelect } = args;
|
||||
const { product, variationsLoading } = this.state;
|
||||
const classes = classnames(
|
||||
'woocommerce-search-product__item',
|
||||
'woocommerce-search-list__item',
|
||||
`depth-${ depth }`,
|
||||
{
|
||||
'is-searching': search.length > 0,
|
||||
'is-skip-level': depth === 0 && item.parent !== 0,
|
||||
'is-variable': item.count > 0,
|
||||
}
|
||||
);
|
||||
|
||||
const itemArgs = Object.assign( {}, args );
|
||||
delete itemArgs.isSingle;
|
||||
|
||||
const a11yProps = {
|
||||
role: 'menuitemradio',
|
||||
};
|
||||
|
||||
if ( item.breadcrumbs.length ) {
|
||||
a11yProps[ 'aria-label' ] = `${ item.breadcrumbs[ 0 ] }: ${ item.name }`;
|
||||
}
|
||||
|
||||
if ( item.count ) {
|
||||
a11yProps[ 'aria-expanded' ] = item.id === product;
|
||||
}
|
||||
|
||||
// Top level items custom rendering based on SearchListItem.
|
||||
if ( ! item.breadcrumbs.length ) {
|
||||
return [
|
||||
<MenuItem
|
||||
key={ `product-${ item.id }` }
|
||||
isSelected={ isSelected }
|
||||
{ ...itemArgs }
|
||||
{ ...a11yProps }
|
||||
className={ classes }
|
||||
onClick={ () => {
|
||||
onSelect( item )();
|
||||
this.onProductSelect( item, isSelected )();
|
||||
} }
|
||||
>
|
||||
<span className="woocommerce-search-list__item-state">
|
||||
{ getInteractionIcon( isSelected ) }
|
||||
</span>
|
||||
|
||||
<span className="woocommerce-search-list__item-label">
|
||||
<span
|
||||
className="woocommerce-search-list__item-name"
|
||||
dangerouslySetInnerHTML={ {
|
||||
__html: getHighlightedName( item.name, search ),
|
||||
} }
|
||||
/>
|
||||
</span>
|
||||
|
||||
{ item.count ? (
|
||||
<span
|
||||
className="woocommerce-search-list__item-variation-count"
|
||||
>
|
||||
{ sprintf(
|
||||
_n(
|
||||
'%d variation',
|
||||
'%d variations',
|
||||
item.count,
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
item.count
|
||||
) }
|
||||
</span>
|
||||
) : null }
|
||||
</MenuItem>,
|
||||
product === item.id && item.count > 0 && variationsLoading && (
|
||||
<div
|
||||
key="loading"
|
||||
className={
|
||||
'woocommerce-search-list__item woocommerce-search-product__item' +
|
||||
'depth-1 is-loading is-not-active'
|
||||
}
|
||||
>
|
||||
<Spinner />
|
||||
</div>
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
if ( ! isEmpty( item.variation ) ) {
|
||||
item.name = item.variation;
|
||||
}
|
||||
|
||||
return (
|
||||
<SearchListItem
|
||||
className={ classes }
|
||||
{ ...args }
|
||||
{ ...a11yProps }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { list, loading } = this.state;
|
||||
const { products, loading, product, variationsList } = this.state;
|
||||
const { onChange, selected } = this.props;
|
||||
const currentVariations = variationsList[ product ] || [];
|
||||
const currentList = [ ...products, ...currentVariations ];
|
||||
const messages = {
|
||||
list: __( 'Products', 'woo-gutenberg-products-block' ),
|
||||
noItems: __(
|
||||
|
@ -64,19 +255,21 @@ class ProductControl extends Component {
|
|||
'woo-gutenberg-products-block'
|
||||
),
|
||||
};
|
||||
const selectedListItems = selected ? [ find( currentList, { id: selected } ) ] : [];
|
||||
|
||||
// Note: selected prop still needs to be array for SearchListControl.
|
||||
return (
|
||||
<Fragment>
|
||||
<SearchListControl
|
||||
className="woocommerce-products"
|
||||
list={ list }
|
||||
list={ currentList }
|
||||
isLoading={ loading }
|
||||
isSingle
|
||||
selected={ [ find( list, { id: selected } ) ] }
|
||||
selected={ selectedListItems }
|
||||
onChange={ onChange }
|
||||
onSearch={ isLargeCatalog ? this.debouncedOnSearch : null }
|
||||
messages={ messages }
|
||||
renderItem={ this.renderItem }
|
||||
isHierarchical
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
.woocommerce-search-product__item {
|
||||
.woocommerce-search-list__item-name {
|
||||
.description {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-searching,
|
||||
&.is-skip-level {
|
||||
.woocommerce-search-list__item-prefix::after {
|
||||
content: ":";
|
||||
}
|
||||
}
|
||||
|
||||
&.is-not-active {
|
||||
@include hover-state {
|
||||
background: $white;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-loading {
|
||||
justify-content: center;
|
||||
|
||||
.components-spinner {
|
||||
margin-bottom: $gap-small;
|
||||
}
|
||||
}
|
||||
|
||||
&.depth-0.is-variable::after {
|
||||
margin-left: $gap-smaller;
|
||||
content: '';
|
||||
height: $gap-large;
|
||||
width: $gap-large;
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z" fill="{$core-grey-dark-300}" /></svg>');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center right;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
&.depth-0.is-variable[aria-expanded="true"]::after {
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z" fill="{$core-grey-dark-300}" /></svg>');
|
||||
}
|
||||
}
|
|
@ -438,16 +438,16 @@
|
|||
},
|
||||
{
|
||||
"name": "phpcompatibility/php-compatibility",
|
||||
"version": "9.1.1",
|
||||
"version": "9.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHPCompatibility/PHPCompatibility.git",
|
||||
"reference": "2b63c5d284ab8857f7b1d5c240ddb507a6b2293c"
|
||||
"reference": "3db1bf1e28123fd574a4ae2e9a84072826d51b5e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/2b63c5d284ab8857f7b1d5c240ddb507a6b2293c",
|
||||
"reference": "2b63c5d284ab8857f7b1d5c240ddb507a6b2293c",
|
||||
"url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/3db1bf1e28123fd574a4ae2e9a84072826d51b5e",
|
||||
"reference": "3db1bf1e28123fd574a4ae2e9a84072826d51b5e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -461,7 +461,7 @@
|
|||
"phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0"
|
||||
},
|
||||
"suggest": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "^0.4.3 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.",
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.",
|
||||
"roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
|
||||
},
|
||||
"type": "phpcodesniffer-standard",
|
||||
|
@ -492,7 +492,7 @@
|
|||
"phpcs",
|
||||
"standards"
|
||||
],
|
||||
"time": "2018-12-30T23:16:27+00:00"
|
||||
"time": "2019-06-27T19:58:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpcompatibility/phpcompatibility-paragonie",
|
||||
|
|
|
@ -60,9 +60,16 @@ class FeaturedProduct extends AbstractDynamicBlock {
|
|||
wp_kses_post( $product->get_title() )
|
||||
);
|
||||
|
||||
if ( $product->is_type( 'variation' ) ) {
|
||||
$title .= sprintf(
|
||||
'<h3 class="wc-block-featured-product__variation">%s</h3>',
|
||||
wc_get_formatted_variation( $product, true, true, false )
|
||||
);
|
||||
}
|
||||
|
||||
$desc_str = sprintf(
|
||||
'<div class="wc-block-featured-product__description">%s</div>',
|
||||
apply_filters( 'woocommerce_short_description', $product->get_short_description() )
|
||||
apply_filters( 'woocommerce_short_description', $product->get_short_description() ? $product->get_short_description() : wc_trim_string( $product->get_description(), 400 ) )
|
||||
);
|
||||
|
||||
$price_str = sprintf(
|
||||
|
|
|
@ -44,6 +44,7 @@ class RestApi {
|
|||
'product-attribute-terms' => __NAMESPACE__ . '\RestApi\Controllers\ProductAttributeTerms',
|
||||
'product-categories' => __NAMESPACE__ . '\RestApi\Controllers\ProductCategories',
|
||||
'products' => __NAMESPACE__ . '\RestApi\Controllers\Products',
|
||||
'variations' => __NAMESPACE__ . '\RestApi\Controllers\Variations',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,27 +167,23 @@ class Products extends WC_REST_Products_Controller {
|
|||
/**
|
||||
* Get product data.
|
||||
*
|
||||
* @param WC_Product $product Product instance.
|
||||
* @param string $context Request context.
|
||||
* Options: 'view' and 'edit'.
|
||||
* @param \WC_Product|\WC_Product_Variation $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['permalink'] = $raw_data['permalink'];
|
||||
$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'];
|
||||
$data['average_rating'] = $raw_data['average_rating'];
|
||||
|
||||
return $data;
|
||||
return array(
|
||||
'id' => $product->get_id(),
|
||||
'name' => $product->get_title(),
|
||||
'variation' => $product->is_type( 'variation' ) ? wc_get_formatted_variation( $product, true, true, false ) : '',
|
||||
'permalink' => $product->get_permalink(),
|
||||
'sku' => $product->get_sku(),
|
||||
'description' => apply_filters( 'woocommerce_short_description', $product->get_short_description() ? $product->get_short_description() : wc_trim_string( $product->get_description(), 400 ) ),
|
||||
'price' => $product->get_price(),
|
||||
'price_html' => $product->get_price_html(),
|
||||
'images' => $this->get_images( $product ),
|
||||
'average_rating' => $product->get_average_rating(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -201,7 +197,7 @@ class Products extends WC_REST_Products_Controller {
|
|||
$attachment_ids = array();
|
||||
|
||||
// Add featured image.
|
||||
if ( has_post_thumbnail( $product->get_id() ) ) {
|
||||
if ( $product->get_image_id() ) {
|
||||
$attachment_ids[] = $product->get_image_id();
|
||||
}
|
||||
|
||||
|
@ -287,58 +283,58 @@ class Products extends WC_REST_Products_Controller {
|
|||
'title' => 'product_block_product',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'id' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'Unique identifier for the resource.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'name' => array(
|
||||
'name' => array(
|
||||
'description' => __( 'Product name.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'permalink' => array(
|
||||
'variation' => array(
|
||||
'description' => __( 'Product variation attributes, if applicable.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'permalink' => array(
|
||||
'description' => __( 'Product URL.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'format' => 'uri',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'description' => array(
|
||||
'description' => __( 'Product description.', 'woo-gutenberg-products-block' ),
|
||||
'description' => array(
|
||||
'description' => __( 'Short description or excerpt from description.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'short_description' => array(
|
||||
'description' => __( 'Product short description.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'sku' => array(
|
||||
'sku' => array(
|
||||
'description' => __( 'Unique identifier.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'price' => array(
|
||||
'price' => array(
|
||||
'description' => __( 'Current product price.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'price_html' => array(
|
||||
'price_html' => array(
|
||||
'description' => __( 'Price formatted in HTML.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'average_rating' => array(
|
||||
'average_rating' => array(
|
||||
'description' => __( 'Reviews average rating.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'images' => array(
|
||||
'images' => array(
|
||||
'description' => __( 'List of images.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API Variations controller customized for Products Block.
|
||||
*
|
||||
* Handles requests to the /products/product/variations endpoint. These endpoints allow read-only access to editors.
|
||||
*
|
||||
* @internal This API is used internally by the block post editor--it is still in flux. It should not be used outside of wc-blocks.
|
||||
* @package WooCommerce/Blocks
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\RestApi\Controllers;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use \WC_REST_Product_Variations_Controller;
|
||||
|
||||
/**
|
||||
* REST API variations controller class.
|
||||
*/
|
||||
class Variations extends WC_REST_Product_Variations_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/blocks';
|
||||
|
||||
/**
|
||||
* Register the routes for variations.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
'args' => array(
|
||||
'product_id' => array(
|
||||
'description' => __( 'Unique identifier for the variable product.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'integer',
|
||||
),
|
||||
),
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'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' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a single variation output for response.
|
||||
*
|
||||
* @param \WC_Data $object Object data.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
public function prepare_object_for_response( $object, $request ) {
|
||||
$data = array(
|
||||
'id' => $object->get_id(),
|
||||
'name' => $object->get_title(),
|
||||
'variation' => wc_get_formatted_variation( $object, true, true, false ),
|
||||
);
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
$response = rest_ensure_response( $data );
|
||||
$response->add_links( $this->prepare_links( $object, $request ) );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Product's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'product_block_variation',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'Unique identifier for the resource.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'name' => array(
|
||||
'description' => __( 'Product name.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'variation' => array(
|
||||
'description' => __( 'Product variation attributes, if applicable.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
),
|
||||
);
|
||||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue