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 classnames from 'classnames';
|
||||||
import { Component, Fragment } from '@wordpress/element';
|
import { Component, Fragment } from '@wordpress/element';
|
||||||
import { compose } from '@wordpress/compose';
|
import { compose } from '@wordpress/compose';
|
||||||
import { debounce, isObject } from 'lodash';
|
import { debounce, isObject, isEmpty } from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -193,7 +193,7 @@ class FeaturedProduct extends Component {
|
||||||
className="wc-block-featured-product"
|
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'
|
'woo-gutenberg-products-block'
|
||||||
) }
|
) }
|
||||||
<div className="wc-block-handpicked-products__selection">
|
<div className="wc-block-handpicked-products__selection">
|
||||||
|
@ -305,11 +305,19 @@ class FeaturedProduct extends Component {
|
||||||
__html: product.name,
|
__html: product.name,
|
||||||
} }
|
} }
|
||||||
/>
|
/>
|
||||||
|
{ ! isEmpty( product.variation ) && (
|
||||||
|
<h3
|
||||||
|
className="wc-block-featured-product__variation"
|
||||||
|
dangerouslySetInnerHTML={ {
|
||||||
|
__html: product.variation,
|
||||||
|
} }
|
||||||
|
/>
|
||||||
|
) }
|
||||||
{ showDesc && (
|
{ showDesc && (
|
||||||
<div
|
<div
|
||||||
className="wc-block-featured-product__description"
|
className="wc-block-featured-product__description"
|
||||||
dangerouslySetInnerHTML={ {
|
dangerouslySetInnerHTML={ {
|
||||||
__html: product.short_description,
|
__html: product.description,
|
||||||
} }
|
} }
|
||||||
/>
|
/>
|
||||||
) }
|
) }
|
||||||
|
|
|
@ -24,7 +24,7 @@ registerBlockType( 'woocommerce/featured-product', {
|
||||||
category: 'woocommerce',
|
category: 'woocommerce',
|
||||||
keywords: [ __( 'WooCommerce', 'woo-gutenberg-products-block' ) ],
|
keywords: [ __( 'WooCommerce', 'woo-gutenberg-products-block' ) ],
|
||||||
description: __(
|
description: __(
|
||||||
'Visually highlight a product and encourage prompt action.',
|
'Visually highlight a product or variation and encourage prompt action.',
|
||||||
'woo-gutenberg-products-block'
|
'woo-gutenberg-products-block'
|
||||||
),
|
),
|
||||||
supports: {
|
supports: {
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|
||||||
.wc-block-featured-product__title,
|
.wc-block-featured-product__title,
|
||||||
|
.wc-block-featured-product__variation,
|
||||||
.wc-block-featured-product__description,
|
.wc-block-featured-product__description,
|
||||||
.wc-block-featured-product__price {
|
.wc-block-featured-product__price {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
@ -36,6 +37,7 @@
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|
||||||
.wc-block-featured-product__title,
|
.wc-block-featured-product__title,
|
||||||
|
.wc-block-featured-product__variation,
|
||||||
.wc-block-featured-product__description,
|
.wc-block-featured-product__description,
|
||||||
.wc-block-featured-product__price {
|
.wc-block-featured-product__price {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
|
@ -44,6 +46,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.wc-block-featured-product__title,
|
.wc-block-featured-product__title,
|
||||||
|
.wc-block-featured-product__variation,
|
||||||
.wc-block-featured-product__description,
|
.wc-block-featured-product__description,
|
||||||
.wc-block-featured-product__price {
|
.wc-block-featured-product__price {
|
||||||
color: $white;
|
color: $white;
|
||||||
|
@ -60,25 +63,34 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.wc-block-featured-product__title,
|
.wc-block-featured-product__title,
|
||||||
|
.wc-block-featured-product__variation,
|
||||||
.wc-block-featured-product__description,
|
.wc-block-featured-product__description,
|
||||||
.wc-block-featured-product__price,
|
.wc-block-featured-product__price,
|
||||||
.wc-block-featured-product__link {
|
.wc-block-featured-product__link {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0 48px 16px 48px;
|
padding: 16px 48px 0 48px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wc-block-featured-product__title {
|
.wc-block-featured-product__title,
|
||||||
|
.wc-block-featured-product__variation {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
border: 0;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wc-block-featured-product__variation {
|
||||||
|
font-style: italic;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.wc-block-featured-product__description {
|
.wc-block-featured-product__description {
|
||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
line-height: 1.5em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,14 +42,14 @@
|
||||||
content: '';
|
content: '';
|
||||||
height: $gap-large;
|
height: $gap-large;
|
||||||
width: $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-repeat: no-repeat;
|
||||||
background-position: center right;
|
background-position: center right;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.depth-0[aria-expanded="true"]::after {
|
&.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 {
|
&[disabled].depth-0::after {
|
||||||
|
|
|
@ -1,54 +1,245 @@
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __, _n, sprintf } from '@wordpress/i18n';
|
||||||
import { Component, Fragment } from '@wordpress/element';
|
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 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
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { isLargeCatalog, getProducts } from '../utils';
|
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 {
|
class ProductControl extends Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super( ...arguments );
|
super( ...arguments );
|
||||||
this.state = {
|
this.state = {
|
||||||
list: [],
|
products: [],
|
||||||
|
product: 0,
|
||||||
|
variationsList: {},
|
||||||
|
variationsLoading: false,
|
||||||
loading: true,
|
loading: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.debouncedOnSearch = debounce( this.onSearch.bind( this ), 400 );
|
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() {
|
componentDidMount() {
|
||||||
const { selected } = this.props;
|
const { selected } = this.props;
|
||||||
|
|
||||||
getProducts( { selected } )
|
getProducts( { selected } )
|
||||||
.then( ( list ) => {
|
.then( ( products ) => {
|
||||||
this.setState( { list, loading: false } );
|
products = products.map( ( product ) => {
|
||||||
|
const count = product.variations ? product.variations.length : 0;
|
||||||
|
return {
|
||||||
|
...product,
|
||||||
|
parent: 0,
|
||||||
|
count: count,
|
||||||
|
};
|
||||||
|
} );
|
||||||
|
this.setState( { products, loading: false } );
|
||||||
} )
|
} )
|
||||||
.catch( () => {
|
.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 ) {
|
onSearch( search ) {
|
||||||
const { selected } = this.props;
|
const { selected } = this.props;
|
||||||
getProducts( { selected, search } )
|
getProducts( { selected, search } )
|
||||||
.then( ( list ) => {
|
.then( ( products ) => {
|
||||||
this.setState( { list, loading: false } );
|
this.setState( { products, loading: false } );
|
||||||
} )
|
} )
|
||||||
.catch( () => {
|
.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() {
|
render() {
|
||||||
const { list, loading } = this.state;
|
const { products, loading, product, variationsList } = this.state;
|
||||||
const { onChange, selected } = this.props;
|
const { onChange, selected } = this.props;
|
||||||
|
const currentVariations = variationsList[ product ] || [];
|
||||||
|
const currentList = [ ...products, ...currentVariations ];
|
||||||
const messages = {
|
const messages = {
|
||||||
list: __( 'Products', 'woo-gutenberg-products-block' ),
|
list: __( 'Products', 'woo-gutenberg-products-block' ),
|
||||||
noItems: __(
|
noItems: __(
|
||||||
|
@ -64,19 +255,21 @@ class ProductControl extends Component {
|
||||||
'woo-gutenberg-products-block'
|
'woo-gutenberg-products-block'
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
const selectedListItems = selected ? [ find( currentList, { id: selected } ) ] : [];
|
||||||
|
|
||||||
// Note: selected prop still needs to be array for SearchListControl.
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<SearchListControl
|
<SearchListControl
|
||||||
className="woocommerce-products"
|
className="woocommerce-products"
|
||||||
list={ list }
|
list={ currentList }
|
||||||
isLoading={ loading }
|
isLoading={ loading }
|
||||||
isSingle
|
isSingle
|
||||||
selected={ [ find( list, { id: selected } ) ] }
|
selected={ selectedListItems }
|
||||||
onChange={ onChange }
|
onChange={ onChange }
|
||||||
onSearch={ isLargeCatalog ? this.debouncedOnSearch : null }
|
onSearch={ isLargeCatalog ? this.debouncedOnSearch : null }
|
||||||
messages={ messages }
|
messages={ messages }
|
||||||
|
renderItem={ this.renderItem }
|
||||||
|
isHierarchical
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</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",
|
"name": "phpcompatibility/php-compatibility",
|
||||||
"version": "9.1.1",
|
"version": "9.2.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/PHPCompatibility/PHPCompatibility.git",
|
"url": "https://github.com/PHPCompatibility/PHPCompatibility.git",
|
||||||
"reference": "2b63c5d284ab8857f7b1d5c240ddb507a6b2293c"
|
"reference": "3db1bf1e28123fd574a4ae2e9a84072826d51b5e"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/2b63c5d284ab8857f7b1d5c240ddb507a6b2293c",
|
"url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/3db1bf1e28123fd574a4ae2e9a84072826d51b5e",
|
||||||
"reference": "2b63c5d284ab8857f7b1d5c240ddb507a6b2293c",
|
"reference": "3db1bf1e28123fd574a4ae2e9a84072826d51b5e",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -461,7 +461,7 @@
|
||||||
"phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0"
|
"phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"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."
|
"roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
|
||||||
},
|
},
|
||||||
"type": "phpcodesniffer-standard",
|
"type": "phpcodesniffer-standard",
|
||||||
|
@ -492,7 +492,7 @@
|
||||||
"phpcs",
|
"phpcs",
|
||||||
"standards"
|
"standards"
|
||||||
],
|
],
|
||||||
"time": "2018-12-30T23:16:27+00:00"
|
"time": "2019-06-27T19:58:56+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpcompatibility/phpcompatibility-paragonie",
|
"name": "phpcompatibility/phpcompatibility-paragonie",
|
||||||
|
|
|
@ -60,9 +60,16 @@ class FeaturedProduct extends AbstractDynamicBlock {
|
||||||
wp_kses_post( $product->get_title() )
|
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(
|
$desc_str = sprintf(
|
||||||
'<div class="wc-block-featured-product__description">%s</div>',
|
'<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(
|
$price_str = sprintf(
|
||||||
|
|
|
@ -44,6 +44,7 @@ class RestApi {
|
||||||
'product-attribute-terms' => __NAMESPACE__ . '\RestApi\Controllers\ProductAttributeTerms',
|
'product-attribute-terms' => __NAMESPACE__ . '\RestApi\Controllers\ProductAttributeTerms',
|
||||||
'product-categories' => __NAMESPACE__ . '\RestApi\Controllers\ProductCategories',
|
'product-categories' => __NAMESPACE__ . '\RestApi\Controllers\ProductCategories',
|
||||||
'products' => __NAMESPACE__ . '\RestApi\Controllers\Products',
|
'products' => __NAMESPACE__ . '\RestApi\Controllers\Products',
|
||||||
|
'variations' => __NAMESPACE__ . '\RestApi\Controllers\Variations',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,27 +167,23 @@ class Products extends WC_REST_Products_Controller {
|
||||||
/**
|
/**
|
||||||
* Get product data.
|
* Get product data.
|
||||||
*
|
*
|
||||||
* @param WC_Product $product Product instance.
|
* @param \WC_Product|\WC_Product_Variation $product Product instance.
|
||||||
* @param string $context Request context.
|
* @param string $context Request context. Options: 'view' and 'edit'.
|
||||||
* Options: 'view' and 'edit'.
|
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function get_product_data( $product, $context = 'view' ) {
|
protected function get_product_data( $product, $context = 'view' ) {
|
||||||
$raw_data = parent::get_product_data( $product, $context );
|
return array(
|
||||||
$data = array();
|
'id' => $product->get_id(),
|
||||||
|
'name' => $product->get_title(),
|
||||||
$data['id'] = $raw_data['id'];
|
'variation' => $product->is_type( 'variation' ) ? wc_get_formatted_variation( $product, true, true, false ) : '',
|
||||||
$data['name'] = $raw_data['name'];
|
'permalink' => $product->get_permalink(),
|
||||||
$data['permalink'] = $raw_data['permalink'];
|
'sku' => $product->get_sku(),
|
||||||
$data['sku'] = $raw_data['sku'];
|
'description' => apply_filters( 'woocommerce_short_description', $product->get_short_description() ? $product->get_short_description() : wc_trim_string( $product->get_description(), 400 ) ),
|
||||||
$data['description'] = $raw_data['description'];
|
'price' => $product->get_price(),
|
||||||
$data['short_description'] = $raw_data['short_description'];
|
'price_html' => $product->get_price_html(),
|
||||||
$data['price'] = $raw_data['price'];
|
'images' => $this->get_images( $product ),
|
||||||
$data['price_html'] = $raw_data['price_html'];
|
'average_rating' => $product->get_average_rating(),
|
||||||
$data['images'] = $raw_data['images'];
|
);
|
||||||
$data['average_rating'] = $raw_data['average_rating'];
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -201,7 +197,7 @@ class Products extends WC_REST_Products_Controller {
|
||||||
$attachment_ids = array();
|
$attachment_ids = array();
|
||||||
|
|
||||||
// Add featured image.
|
// Add featured image.
|
||||||
if ( has_post_thumbnail( $product->get_id() ) ) {
|
if ( $product->get_image_id() ) {
|
||||||
$attachment_ids[] = $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',
|
'title' => 'product_block_product',
|
||||||
'type' => 'object',
|
'type' => 'object',
|
||||||
'properties' => array(
|
'properties' => array(
|
||||||
'id' => array(
|
'id' => array(
|
||||||
'description' => __( 'Unique identifier for the resource.', 'woo-gutenberg-products-block' ),
|
'description' => __( 'Unique identifier for the resource.', 'woo-gutenberg-products-block' ),
|
||||||
'type' => 'integer',
|
'type' => 'integer',
|
||||||
'context' => array( 'view', 'edit', 'embed' ),
|
'context' => array( 'view', 'edit', 'embed' ),
|
||||||
'readonly' => true,
|
'readonly' => true,
|
||||||
),
|
),
|
||||||
'name' => array(
|
'name' => array(
|
||||||
'description' => __( 'Product name.', 'woo-gutenberg-products-block' ),
|
'description' => __( 'Product name.', 'woo-gutenberg-products-block' ),
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'context' => array( 'view', 'edit', 'embed' ),
|
'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' ),
|
'description' => __( 'Product URL.', 'woo-gutenberg-products-block' ),
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'format' => 'uri',
|
'format' => 'uri',
|
||||||
'context' => array( 'view', 'edit', 'embed' ),
|
'context' => array( 'view', 'edit', 'embed' ),
|
||||||
'readonly' => true,
|
'readonly' => true,
|
||||||
),
|
),
|
||||||
'description' => array(
|
'description' => array(
|
||||||
'description' => __( 'Product description.', 'woo-gutenberg-products-block' ),
|
'description' => __( 'Short description or excerpt from description.', 'woo-gutenberg-products-block' ),
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'context' => array( 'view', 'edit', 'embed' ),
|
'context' => array( 'view', 'edit', 'embed' ),
|
||||||
),
|
),
|
||||||
'short_description' => array(
|
'sku' => array(
|
||||||
'description' => __( 'Product short description.', 'woo-gutenberg-products-block' ),
|
|
||||||
'type' => 'string',
|
|
||||||
'context' => array( 'view', 'edit', 'embed' ),
|
|
||||||
),
|
|
||||||
'sku' => array(
|
|
||||||
'description' => __( 'Unique identifier.', 'woo-gutenberg-products-block' ),
|
'description' => __( 'Unique identifier.', 'woo-gutenberg-products-block' ),
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'context' => array( 'view', 'edit' ),
|
'context' => array( 'view', 'edit' ),
|
||||||
),
|
),
|
||||||
'price' => array(
|
'price' => array(
|
||||||
'description' => __( 'Current product price.', 'woo-gutenberg-products-block' ),
|
'description' => __( 'Current product price.', 'woo-gutenberg-products-block' ),
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'context' => array( 'view', 'edit' ),
|
'context' => array( 'view', 'edit' ),
|
||||||
'readonly' => true,
|
'readonly' => true,
|
||||||
),
|
),
|
||||||
'price_html' => array(
|
'price_html' => array(
|
||||||
'description' => __( 'Price formatted in HTML.', 'woo-gutenberg-products-block' ),
|
'description' => __( 'Price formatted in HTML.', 'woo-gutenberg-products-block' ),
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'context' => array( 'view', 'edit' ),
|
'context' => array( 'view', 'edit' ),
|
||||||
'readonly' => true,
|
'readonly' => true,
|
||||||
),
|
),
|
||||||
'average_rating' => array(
|
'average_rating' => array(
|
||||||
'description' => __( 'Reviews average rating.', 'woo-gutenberg-products-block' ),
|
'description' => __( 'Reviews average rating.', 'woo-gutenberg-products-block' ),
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'context' => array( 'view', 'edit' ),
|
'context' => array( 'view', 'edit' ),
|
||||||
'readonly' => true,
|
'readonly' => true,
|
||||||
),
|
),
|
||||||
'images' => array(
|
'images' => array(
|
||||||
'description' => __( 'List of images.', 'woo-gutenberg-products-block' ),
|
'description' => __( 'List of images.', 'woo-gutenberg-products-block' ),
|
||||||
'type' => 'object',
|
'type' => 'object',
|
||||||
'context' => array( 'view', 'edit', 'embed' ),
|
'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