Update from master

This commit is contained in:
Mike Jolley 2019-03-08 09:50:56 +00:00
commit a3c2dcd8f2
46 changed files with 1854 additions and 508 deletions

View File

@ -1,5 +1,20 @@
== Changelog ==
= 3.5.6 - 2019-03-07 =
* Fix - Removes invalid product structured data from archives, and include more data on single product pages. #22925
Product structured data should only be generated for visible data, and not on archives when there are single
product pages available, as per the documentation.
Since this change removes structured data from archives, the filters `woocommerce_structured_data_product_limit`
and `woocommerce_structured_data_product_limited` have also been removed.
To customize product structured data, for example adding custom fields or include on archives, see this article:
https://github.com/woocommerce/woocommerce/wiki/Structured-data-for-products.
* Fix - Fix last item in breadcrumb structured data, and include on shop page. #22925
* Fix - Get insert ID before running actions in `_insert_tax_rate`. #22868
* Fix - Add precision to tax in the discount class so min spend checks work correctly. #22888
* Fix - Update troubleshooting link in failed order email. #22943
* Fix - Update Flexslider to 2.7.2.
* Fix - Fill user's account first name and last name only when those fields are empty. #22783
= 3.5.5 - 2019-02-20 =
* Fix - Fix allow product low stock threshold be the WC settings default. #22777
* Fix - Fix error on product category when sorting by multiple fields. #22066

View File

@ -1,7 +1,7 @@
/* jshint node:true */
module.exports = function( grunt ) {
'use strict';
const sass = require( 'node-sass' );
var sass = require( 'node-sass' );
grunt.initConfig({

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4352,6 +4352,10 @@ img.help_tip {
content: "\f111";
}
&.marketplace-suggestions_options a::before {
content: none;
}
&.variations_options a::before {
content: "\f509";
}
@ -5950,6 +5954,27 @@ table.bar_chart {
}
}
.post-type-product .woocommerce-BlankState,
.post-type-shop_order .woocommerce-BlankState {
max-width: 764px;
text-align: center;
margin: auto;
.woocommerce-BlankState-message {
color: #444;
font-size: 1.5em;
margin: 0 auto 1em;
}
.woocommerce-BlankState-message::before {
font-size: 120px;
}
.woocommerce-BlankState-buttons {
margin-bottom: 4em;
}
}
/**
* Small screen optimisation
*/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,315 @@
/**
* marketplace-suggestions.scss
* Styling for in-product marketplace suggestions.
*/
@import "mixins";
@import "variables";
$suggestions-pale-gray: #ddd;
$suggestions-metabox-pale-gray: #eee;
$suggestions-copy-text: #444;
a.suggestion-dismiss {
border: none;
box-shadow: none;
color: $suggestions-pale-gray;
}
a.suggestion-dismiss:hover {
color: #aaa;
}
a.suggestion-dismiss::before {
@include iconbeforedashicons( "\f335" );
font-size: 1.5em;
}
#woocommerce-product-data ul.wc-tabs li.marketplace-suggestions_tab {
a span {
margin: 0;
}
}
.marketplace-suggestions-container.showing-suggestion {
text-align: left;
.marketplace-suggestion-container {
align-items: flex-start;
display: flex;
flex-direction: column;
// Allows us to position the dismiss x button
// relative to container on mobile.
position: relative;
img.marketplace-suggestion-icon {
height: 40px;
margin: 0;
margin-right: 1.5em;
flex: 0 0 40px;
}
.marketplace-suggestion-container-content {
flex: 1 1 60%;
h4 {
margin: 0;
}
p {
margin: 0;
margin-top: 4px;
color: $suggestions-copy-text;
}
}
.marketplace-suggestion-container-cta {
flex: 1 1 30%;
min-width: 160px;
text-align: right;
.suggestion-dismiss {
text-decoration: none;
position: absolute;
top: 1em;
right: 1em;
}
}
}
@media screen and (min-width: 600px) {
.marketplace-suggestion-container {
align-items: center;
flex-direction: row;
img.marketplace-suggestion-icon {
// display: inline-block;
}
}
}
}
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="products-list-empty-header"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="products-list-empty-footer"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="product-edit-meta-tab-header"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="product-edit-meta-tab-footer"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="orders-list-empty-header"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="orders-list-empty-footer"] {
.marketplace-suggestion-container {
.marketplace-suggestion-container-content {
h4 {
font-size: 1.1em;
margin: 0;
margin-bottom: 0;
}
}
}
}
// Additional breathing space margin under empty-state footer.
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="products-list-empty-footer"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="orders-list-empty-footer"] {
margin-bottom: 6em;
}
// Optimise footer suggestion layout for left-aligned CTA link button only.
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="products-list-empty-footer"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="product-edit-meta-tab-footer"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="orders-list-empty-footer"] {
.marketplace-suggestion-container {
flex-direction: row-reverse;
.marketplace-suggestion-container-cta {
text-align: left;
}
}
}
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="product-edit-meta-tab-header"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="product-edit-meta-tab-footer"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="product-edit-meta-tab-body"] {
.marketplace-suggestion-container {
padding: 1em 1.5em;
.marketplace-suggestion-container-content {
p {
padding: 0;
line-height: 1.5;
}
}
}
}
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="products-list-inline"] {
// hide by default (mobile first)
display: none;
td:first-child {
border-left: 4px solid $woocommerce;
}
.marketplace-suggestion-container {
padding: 0.5em 0 0.5em 1em;
img.marketplace-suggestion-icon {
margin-bottom: 0;
}
.marketplace-suggestion-container-content {
h4 {
margin: 0;
font-size: 14px;
}
}
.marketplace-suggestion-container-cta {
.suggestion-dismiss {
position: relative;
top: 4px;
right: auto;
margin-left: 1em;
}
}
}
@media screen and (min-width: 800px) {
// Display inline table suggestion on desktop only.
// The table columns are dynamic, so there's no good way to style the row
// content correctly when columns are hidden.
display: table-row;
}
}
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="products-list-empty-header"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="products-list-empty-footer"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="orders-list-empty-header"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="orders-list-empty-footer"] {
.marketplace-suggestion-container {
padding: 1.5em;
}
}
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="products-list-empty-body"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="orders-list-empty-body"] {
.marketplace-suggestion-container {
padding: 0.75em 1.5em;
&:first-child {
padding-top: 1.5em;
}
&:last-child {
padding-bottom: 1.5em;
}
.marketplace-suggestion-container-content {
p:last-child {
margin-bottom: 0;
}
}
}
}
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="products-list-empty-header"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="products-list-empty-footer"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="products-list-empty-body"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="orders-list-empty-header"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="orders-list-empty-footer"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="orders-list-empty-body"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="product-edit-meta-tab-header"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="product-edit-meta-tab-footer"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="product-edit-meta-tab-body"] {
// hide by default (mobile first)
display: none;
.marketplace-suggestion-container .marketplace-suggestion-container-cta {
a.button {
display: inline-block;
min-width: 120px;
text-align: center;
margin: 0;
}
a.linkout {
font-size: 1.1em;
text-decoration: none;
}
a.linkout .dashicons {
margin-left: 4px;
bottom: 2px;
position: relative;
}
.suggestion-dismiss {
position: relative;
top: 5px;
right: auto;
margin-left: 1em;
}
}
@media screen and (min-width: 600px) {
// Display onboarding table suggestion on desktop only. (for now)
// There's limited room on mobile, and there are edge-case
// styling issues in some browsers.
display: block;
}
}
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="product-edit-meta-tab-header"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="product-edit-meta-tab-footer"] {
border: none;
}
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="product-edit-meta-tab-body"] {
border: none;
border-top: 1px solid $suggestions-metabox-pale-gray;
border-bottom: 1px solid $suggestions-metabox-pale-gray;
}
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="products-list-empty-header"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="products-list-empty-footer"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="products-list-empty-body"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="orders-list-empty-header"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="orders-list-empty-footer"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="orders-list-empty-body"] {
border: 1px solid $suggestions-pale-gray;
border-bottom: none;
&:last-child {
border-bottom: 1px solid $suggestions-pale-gray;
}
}

View File

@ -0,0 +1,446 @@
/* global marketplace_suggestions, ajaxurl, Cookies */
( function( $, marketplace_suggestions, ajaxurl ) {
$( function() {
if ( 'undefined' === typeof marketplace_suggestions ) {
return;
}
// Stand-in wcTracks.recordEvent in case tracks is not available (for any reason).
window.wcTracks = window.wcTracks || {};
window.wcTracks.recordEvent = window.wcTracks.recordEvent || function() { };
// Tracks events sent in this file:
// - marketplace_suggestion_displayed
// - marketplace_suggestion_clicked
// - marketplace_suggestion_dismissed
// All are prefixed by {WC_Tracks::PREFIX}.
// All have one property for `suggestionSlug`, to identify the specific suggestion message.
// Dismiss the specified suggestion from the UI, and save the dismissal in settings.
function dismissSuggestion( context, suggestionSlug ) {
// hide the suggestion in the UI
var selector = '[data-suggestion-slug=' + suggestionSlug + ']';
$( selector ).fadeOut( function() {
$( this ).remove();
tidyProductEditMetabox();
} );
// save dismissal in user settings
jQuery.post(
ajaxurl,
{
'action': 'woocommerce_add_dismissed_marketplace_suggestion',
'_wpnonce': marketplace_suggestions.dismiss_suggestion_nonce,
'slug': suggestionSlug
}
);
// if this is a high-use area, delay new suggestion that area for a short while
var highUseSuggestionContexts = [ 'products-list-inline' ];
if ( _.contains( highUseSuggestionContexts, context ) ) {
Cookies.set( 'woocommerce_snooze_products_list_suggestions', '1', { expires: 2 } );
}
window.wcTracks.recordEvent( 'marketplace_suggestion_dismissed', {
suggestionSlug: suggestionSlug
} );
}
// Render DOM element for suggestion dismiss button.
function renderDismissButton( context, suggestionSlug ) {
var dismissButton = document.createElement( 'a' );
dismissButton.classList.add( 'suggestion-dismiss' );
dismissButton.setAttribute( 'title', marketplace_suggestions.i18n_marketplace_suggestions_dismiss_tooltip );
dismissButton.setAttribute( 'href', '#' );
dismissButton.onclick = function( event ) {
event.preventDefault();
dismissSuggestion( context, suggestionSlug );
};
return dismissButton;
}
function addUTMParameters( context, url ) {
var utmParams = {
utm_source: 'unknown',
utm_campaign: 'marketplacesuggestions',
utm_medium: 'product'
};
var sourceContextMap = {
'productstable': [
'products-list-inline'
],
'productsempty': [
'products-list-empty-header',
'products-list-empty-footer',
'products-list-empty-body'
],
'ordersempty': [
'orders-list-empty-header',
'orders-list-empty-footer',
'orders-list-empty-body'
],
'editproduct': [
'product-edit-meta-tab-header',
'product-edit-meta-tab-footer',
'product-edit-meta-tab-body'
]
};
var utmSource = _.findKey( sourceContextMap, function( sourceInfo ) {
return _.contains( sourceInfo, context );
} );
if ( utmSource ) {
utmParams.utm_source = utmSource;
}
return url + '?' + jQuery.param( utmParams );
}
// Render DOM element for suggestion linkout, optionally with button style.
function renderLinkout( context, slug, url, text, isButton ) {
var linkoutButton = document.createElement( 'a' );
var utmUrl = addUTMParameters( context, url );
linkoutButton.setAttribute( 'href', utmUrl );
linkoutButton.setAttribute( 'target', 'blank' );
linkoutButton.textContent = text;
linkoutButton.onclick = function() {
window.wcTracks.recordEvent( 'marketplace_suggestion_clicked', {
suggestionSlug: slug
} );
};
if ( isButton ) {
linkoutButton.classList.add( 'button' );
} else {
linkoutButton.classList.add( 'linkout' );
var linkoutIcon = document.createElement( 'span' );
linkoutIcon.classList.add( 'dashicons', 'dashicons-external' );
linkoutButton.appendChild( linkoutIcon );
}
return linkoutButton;
}
// Render DOM element for suggestion icon image.
function renderSuggestionIcon( iconUrl ) {
if ( ! iconUrl ) {
return null;
}
var image = document.createElement( 'img' );
image.src = iconUrl;
image.classList.add( 'marketplace-suggestion-icon' );
return image;
}
// Render DOM elements for suggestion content.
function renderSuggestionContent( title, copy ) {
var container = document.createElement( 'div' );
container.classList.add( 'marketplace-suggestion-container-content' );
if ( title ) {
var titleHeading = document.createElement( 'h4' );
titleHeading.textContent = title;
container.appendChild( titleHeading );
}
if ( copy ) {
var body = document.createElement( 'p' );
body.textContent = copy;
container.appendChild( body );
}
return container;
}
// Render DOM elements for suggestion call-to-action button or link with dismiss 'x'.
function renderSuggestionCTA( context, slug, url, linkText, linkIsButton, allowDismiss ) {
var container = document.createElement( 'div' );
if ( ! linkText ) {
linkText = marketplace_suggestions.i18n_marketplace_suggestions_default_cta;
}
container.classList.add( 'marketplace-suggestion-container-cta' );
if ( url && linkText ) {
var linkoutElement = renderLinkout( context, slug, url, linkText, linkIsButton );
container.appendChild( linkoutElement );
}
if ( allowDismiss ) {
container.appendChild( renderDismissButton( context, slug ) );
}
return container;
}
function getTableBannerColspan() {
return $( 'table.wp-list-table.posts thead th:not(.hidden)' ).length + 1;
}
// Render a "table banner" style suggestion.
// These are used in admin lists, e.g. products list.
function renderTableBanner( context, slug, iconUrl, title, copy, url, buttonText, allowDismiss ) {
if ( ! title || ! url ) {
return;
}
var row = document.createElement( 'tr' );
row.classList.add( 'marketplace-table-banner' );
row.classList.add( 'marketplace-suggestions-container' );
row.classList.add( 'showing-suggestion' );
row.dataset.marketplaceSuggestionsContext = 'products-list-inline';
row.dataset.suggestionSlug = slug;
var cell = document.createElement( 'td' );
cell.classList.add( 'marketplace-table-banner-td' );
cell.setAttribute( 'colspan', getTableBannerColspan() );
var container = document.createElement( 'div' );
container.classList.add( 'marketplace-suggestion-container' );
container.dataset.suggestionSlug = slug;
var icon = renderSuggestionIcon( iconUrl );
if ( icon ) {
container.appendChild( icon );
}
container.appendChild(
renderSuggestionContent( title, copy )
);
container.appendChild(
renderSuggestionCTA( context, slug, url, buttonText, true, allowDismiss )
);
cell.appendChild( container );
row.appendChild( cell );
return row;
}
// Render a "list item" style suggestion.
// These are used in onboarding style contexts, e.g. products list empty state.
function renderListItem( context, slug, iconUrl, title, copy, url, linkText, linkIsButton, allowDismiss ) {
var container = document.createElement( 'div' );
container.classList.add( 'marketplace-suggestion-container' );
container.dataset.suggestionSlug = slug;
var icon = renderSuggestionIcon( iconUrl );
if ( icon ) {
container.appendChild( icon );
}
container.appendChild(
renderSuggestionContent( title, copy )
);
container.appendChild(
renderSuggestionCTA( context, slug, url, linkText, linkIsButton, allowDismiss )
);
return container;
}
// Filter suggestion data to remove less-relevant suggestions.
function getRelevantPromotions( marketplaceSuggestionsApiData, displayContext ) {
// select based on display context
var promos = _.filter( marketplaceSuggestionsApiData, function( promo ) {
if ( _.isArray( promo.context ) ) {
return _.contains( promo.context, displayContext );
}
return ( displayContext === promo.context );
} );
// hide promos the user has dismissed
promos = _.filter( promos, function( promo ) {
return ! _.contains( marketplace_suggestions.dismissed_suggestions, promo.slug );
} );
// hide promos for things the user already has installed
promos = _.filter( promos, function( promo ) {
return ! _.contains( marketplace_suggestions.active_plugins, promo['hide-if-active'] );
} );
// hide promos that are not applicable based on user's installed extensions
promos = _.filter( promos, function( promo ) {
if ( ! promo['show-if-active'] ) {
// this promotion is relevant to all
return true;
}
// if the user has any of the prerequisites, show the promo
return ( _.intersection( marketplace_suggestions.active_plugins, promo['show-if-active'] ).length > 0 );
} );
return promos;
}
// Show and hide page elements dependent on suggestion state.
function hidePageElementsForSuggestionState( usedSuggestionsContexts ) {
var showingEmptyStateSuggestions = _.intersection(
usedSuggestionsContexts,
[ 'products-list-empty-body', 'orders-list-empty-body' ]
).length > 0;
// Streamline onboarding UI if we're in 'empty state' welcome mode.
if ( showingEmptyStateSuggestions ) {
$( '#screen-meta-links' ).hide();
$( '#wpfooter' ).hide();
}
// Hide the header & footer, they don't make sense without specific promotion content
if ( ! showingEmptyStateSuggestions ) {
$( '.marketplace-suggestions-container[data-marketplace-suggestions-context="products-list-empty-header"]' ).hide();
$( '.marketplace-suggestions-container[data-marketplace-suggestions-context="products-list-empty-footer"]' ).hide();
$( '.marketplace-suggestions-container[data-marketplace-suggestions-context="orders-list-empty-header"]' ).hide();
$( '.marketplace-suggestions-container[data-marketplace-suggestions-context="orders-list-empty-footer"]' ).hide();
}
}
// Streamline the product edit suggestions tab dependent on what's visible.
function tidyProductEditMetabox() {
var productMetaboxSuggestions = $(
'.marketplace-suggestions-container[data-marketplace-suggestions-context="product-edit-meta-tab-body"]'
).children();
if ( 0 >= productMetaboxSuggestions.length ) {
$( '.marketplace-suggestions-container[data-marketplace-suggestions-context="product-edit-meta-tab-header"]' ).slideUp();
$( '.marketplace-suggestions-container[data-marketplace-suggestions-context="product-edit-meta-tab-body"]' ).fadeOut();
}
}
function refreshBannerColspanForScreenOptions( content ) {
$( '#show-settings-link' ).on( 'focus.scroll-into-view', function() {
$( '.marketplace-table-banner-td' ).attr( 'colspan', getTableBannerColspan() );
});
}
// Render suggestion data in appropriate places in UI.
function displaySuggestions( marketplaceSuggestionsApiData ) {
var usedSuggestionsContexts = [];
// iterate over all suggestions containers, rendering promos
$( '.marketplace-suggestions-container' ).each( function() {
// determine the context / placement we're populating
var context = this.dataset.marketplaceSuggestionsContext;
// find promotions that target this context
var promos = getRelevantPromotions( marketplaceSuggestionsApiData, context );
// shuffle/randomly select five suggestions to display
var suggestionsToDisplay = _.sample( promos, 5 );
// render the promo content
for ( var i in suggestionsToDisplay ) {
var linkText = suggestionsToDisplay[ i ]['link-text'];
var linkoutIsButton = true;
if ( suggestionsToDisplay[ i ]['link-text'] ) {
linkText = suggestionsToDisplay[ i ]['link-text'];
linkoutIsButton = false;
}
// dismiss is allowed by default
var allowDismiss = true;
if ( suggestionsToDisplay[ i ]['allow-dismiss'] === false ) {
allowDismiss = false;
}
var content = renderListItem(
context,
suggestionsToDisplay[ i ].slug,
suggestionsToDisplay[ i ].icon,
suggestionsToDisplay[ i ].title,
suggestionsToDisplay[ i ].copy,
suggestionsToDisplay[ i ].url,
linkText,
linkoutIsButton,
allowDismiss
);
$( this ).append( content );
$( this ).addClass( 'showing-suggestion' );
usedSuggestionsContexts.push( context );
window.wcTracks.recordEvent( 'marketplace_suggestion_displayed', {
suggestionSlug: suggestionsToDisplay[ i ].slug
} );
}
} );
// render inline promos in products list
if ( 0 === usedSuggestionsContexts.length ) {
$( '.wp-admin.admin-bar.edit-php.post-type-product table.wp-list-table.posts tbody').first().each( function() {
var context = 'products-list-inline';
// product list banner suggestion is temporarily suppressed after a recent dismissal
if ( Cookies.get( 'woocommerce_snooze_products_list_suggestions' ) ) {
return;
}
// find promotions that target this context
var promos = getRelevantPromotions( marketplaceSuggestionsApiData, context );
if ( ! promos || ! promos.length ) {
return;
}
// shuffle/randomly select the suggestion to display
var suggestionToDisplay = _.sample( promos );
// dismiss is allowed by default
var allowDismiss = true;
if ( false === suggestionToDisplay['allow-dismiss'] ) {
allowDismiss = false;
}
// render first promo
var content = renderTableBanner(
context,
suggestionToDisplay.slug,
suggestionToDisplay.icon,
suggestionToDisplay.title,
suggestionToDisplay.copy,
suggestionToDisplay.url,
suggestionToDisplay['button-text'],
allowDismiss
);
if ( content ) {
// where should we put it in the list?
var rows = $( this ).children();
var minRow = 3;
$( content ).hide();
if ( rows.length <= minRow ) {
// if small number of rows, append at end
$( this ).append( content );
}
else {
// for more rows, insert
$( rows[ minRow - 1 ] ).after( content );
}
$( content ).fadeIn();
usedSuggestionsContexts.push( context );
refreshBannerColspanForScreenOptions( content );
window.wcTracks.recordEvent( 'marketplace_suggestion_displayed', {
suggestionSlug: suggestionToDisplay.slug
} );
}
} );
}
hidePageElementsForSuggestionState( usedSuggestionsContexts );
tidyProductEditMetabox();
}
if ( marketplace_suggestions.suggestions_data ) {
displaySuggestions( marketplace_suggestions.suggestions_data );
}
});
})( jQuery, marketplace_suggestions, ajaxurl );

File diff suppressed because one or more lines are too long

View File

@ -343,4 +343,5 @@
}
});
});
})( jQuery, woocommerce_admin );

270
i18n/phone.php Normal file
View File

@ -0,0 +1,270 @@
<?php
/**
* Calling codes.
*
* Returns an array of calling codes.
*
* @package WooCommerce/i18n
*/
defined( 'ABSPATH' ) || exit;
return array(
'BD' => '+880',
'BE' => '+32',
'BF' => '+226',
'BG' => '+359',
'BA' => '+387',
'BB' => '+1246',
'WF' => '+681',
'BL' => '+590',
'BM' => '+1441',
'BN' => '+673',
'BO' => '+591',
'BH' => '+973',
'BI' => '+257',
'BJ' => '+229',
'BT' => '+975',
'JM' => '+1876',
'BV' => '',
'BW' => '+267',
'WS' => '+685',
'BQ' => '+599',
'BR' => '+55',
'BS' => '+1242',
'JE' => '+441534',
'BY' => '+375',
'BZ' => '+501',
'RU' => '+7',
'RW' => '+250',
'RS' => '+381',
'TL' => '+670',
'RE' => '+262',
'TM' => '+993',
'TJ' => '+992',
'RO' => '+40',
'TK' => '+690',
'GW' => '+245',
'GU' => '+1671',
'GT' => '+502',
'GS' => '',
'GR' => '+30',
'GQ' => '+240',
'GP' => '+590',
'JP' => '+81',
'GY' => '+592',
'GG' => '+441481',
'GF' => '+594',
'GE' => '+995',
'GD' => '+1473',
'GB' => '+44',
'GA' => '+241',
'SV' => '+503',
'GN' => '+224',
'GM' => '+220',
'GL' => '+299',
'GI' => '+350',
'GH' => '+233',
'OM' => '+968',
'TN' => '+216',
'JO' => '+962',
'HR' => '+385',
'HT' => '+509',
'HU' => '+36',
'HK' => '+852',
'HN' => '+504',
'HM' => '',
'VE' => '+58',
'PR' => array(
'+1787',
'+1939',
),
'PS' => '+970',
'PW' => '+680',
'PT' => '+351',
'SJ' => '+47',
'PY' => '+595',
'IQ' => '+964',
'PA' => '+507',
'PF' => '+689',
'PG' => '+675',
'PE' => '+51',
'PK' => '+92',
'PH' => '+63',
'PN' => '+870',
'PL' => '+48',
'PM' => '+508',
'ZM' => '+260',
'EH' => '+212',
'EE' => '+372',
'EG' => '+20',
'ZA' => '+27',
'EC' => '+593',
'IT' => '+39',
'VN' => '+84',
'SB' => '+677',
'ET' => '+251',
'SO' => '+252',
'ZW' => '+263',
'SA' => '+966',
'ES' => '+34',
'ER' => '+291',
'ME' => '+382',
'MD' => '+373',
'MG' => '+261',
'MF' => '+590',
'MA' => '+212',
'MC' => '+377',
'UZ' => '+998',
'MM' => '+95',
'ML' => '+223',
'MO' => '+853',
'MN' => '+976',
'MH' => '+692',
'MK' => '+389',
'MU' => '+230',
'MT' => '+356',
'MW' => '+265',
'MV' => '+960',
'MQ' => '+596',
'MP' => '+1670',
'MS' => '+1664',
'MR' => '+222',
'IM' => '+441624',
'UG' => '+256',
'TZ' => '+255',
'MY' => '+60',
'MX' => '+52',
'IL' => '+972',
'FR' => '+33',
'IO' => '+246',
'SH' => '+290',
'FI' => '+358',
'FJ' => '+679',
'FK' => '+500',
'FM' => '+691',
'FO' => '+298',
'NI' => '+505',
'NL' => '+31',
'NO' => '+47',
'NA' => '+264',
'VU' => '+678',
'NC' => '+687',
'NE' => '+227',
'NF' => '+672',
'NG' => '+234',
'NZ' => '+64',
'NP' => '+977',
'NR' => '+674',
'NU' => '+683',
'CK' => '+682',
'XK' => '',
'CI' => '+225',
'CH' => '+41',
'CO' => '+57',
'CN' => '+86',
'CM' => '+237',
'CL' => '+56',
'CC' => '+61',
'CA' => '+1',
'CG' => '+242',
'CF' => '+236',
'CD' => '+243',
'CZ' => '+420',
'CY' => '+357',
'CX' => '+61',
'CR' => '+506',
'CW' => '+599',
'CV' => '+238',
'CU' => '+53',
'SZ' => '+268',
'SY' => '+963',
'SX' => '+599',
'KG' => '+996',
'KE' => '+254',
'SS' => '+211',
'SR' => '+597',
'KI' => '+686',
'KH' => '+855',
'KN' => '+1869',
'KM' => '+269',
'ST' => '+239',
'SK' => '+421',
'KR' => '+82',
'SI' => '+386',
'KP' => '+850',
'KW' => '+965',
'SN' => '+221',
'SM' => '+378',
'SL' => '+232',
'SC' => '+248',
'KZ' => '+7',
'KY' => '+1345',
'SG' => '+65',
'SE' => '+46',
'SD' => '+249',
'DO' => array(
'+1809',
'+1829',
'+1849',
),
'DM' => '+1767',
'DJ' => '+253',
'DK' => '+45',
'VG' => '+1284',
'DE' => '+49',
'YE' => '+967',
'DZ' => '+213',
'US' => '+1',
'UY' => '+598',
'YT' => '+262',
'UM' => '+1',
'LB' => '+961',
'LC' => '+1758',
'LA' => '+856',
'TV' => '+688',
'TW' => '+886',
'TT' => '+1868',
'TR' => '+90',
'LK' => '+94',
'LI' => '+423',
'LV' => '+371',
'TO' => '+676',
'LT' => '+370',
'LU' => '+352',
'LR' => '+231',
'LS' => '+266',
'TH' => '+66',
'TF' => '',
'TG' => '+228',
'TD' => '+235',
'TC' => '+1649',
'LY' => '+218',
'VA' => '+379',
'VC' => '+1784',
'AE' => '+971',
'AD' => '+376',
'AG' => '+1268',
'AF' => '+93',
'AI' => '+1264',
'VI' => '+1340',
'IS' => '+354',
'IR' => '+98',
'AM' => '+374',
'AL' => '+355',
'AO' => '+244',
'AQ' => '',
'AS' => '+1684',
'AR' => '+54',
'AU' => '+61',
'AT' => '+43',
'AW' => '+297',
'IN' => '+91',
'AX' => '+35818',
'AZ' => '+994',
'IE' => '+353',
'ID' => '+62',
'UA' => '+380',
'QA' => '+974',
'MZ' => '+258',
);

View File

@ -40,12 +40,14 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
wp_register_style( 'jquery-ui-style', WC()->plugin_url() . '/assets/css/jquery-ui/jquery-ui.min.css', array(), WC_VERSION );
wp_register_style( 'woocommerce_admin_dashboard_styles', WC()->plugin_url() . '/assets/css/dashboard.css', array(), WC_VERSION );
wp_register_style( 'woocommerce_admin_print_reports_styles', WC()->plugin_url() . '/assets/css/reports-print.css', array(), WC_VERSION, 'print' );
wp_register_style( 'woocommerce_admin_marketplace_styles', WC()->plugin_url() . '/assets/css/marketplace-suggestions.css', array(), WC_VERSION );
// Add RTL support for admin styles.
wp_style_add_data( 'woocommerce_admin_menu_styles', 'rtl', 'replace' );
wp_style_add_data( 'woocommerce_admin_styles', 'rtl', 'replace' );
wp_style_add_data( 'woocommerce_admin_dashboard_styles', 'rtl', 'replace' );
wp_style_add_data( 'woocommerce_admin_print_reports_styles', 'rtl', 'replace' );
wp_style_add_data( 'woocommerce_admin_marketplace_styles', 'rtl', 'replace' );
// Sitewide menu CSS.
wp_enqueue_style( 'woocommerce_admin_menu_styles' );
@ -70,6 +72,10 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
do_action( 'woocommerce_admin_css' );
wc_deprecated_function( 'The woocommerce_admin_css action', '2.3', 'admin_enqueue_scripts' );
}
if ( WC_Marketplace_Suggestions::show_suggestions_for_screen( $screen_id ) ) {
wp_enqueue_style( 'woocommerce_admin_marketplace_styles' );
}
}
@ -108,6 +114,8 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
wp_register_script( 'select2', WC()->plugin_url() . '/assets/js/select2/select2.full' . $suffix . '.js', array( 'jquery' ), '4.0.3' );
wp_register_script( 'selectWoo', WC()->plugin_url() . '/assets/js/selectWoo/selectWoo.full' . $suffix . '.js', array( 'jquery' ), '1.0.4' );
wp_register_script( 'wc-enhanced-select', WC()->plugin_url() . '/assets/js/admin/wc-enhanced-select' . $suffix . '.js', array( 'jquery', 'selectWoo' ), WC_VERSION );
wp_register_script( 'js-cookie', WC()->plugin_url() . '/assets/js/js-cookie/js.cookie' . $suffix . '.js', array(), '2.1.4', true );
wp_localize_script(
'wc-enhanced-select',
'wc_enhanced_select_params',
@ -416,7 +424,35 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
)
);
}
if ( WC_Marketplace_Suggestions::show_suggestions_for_screen( $screen_id ) ) {
$active_plugin_slugs = array_map( 'dirname', get_option( 'active_plugins' ) );
wp_register_script(
'marketplace-suggestions',
WC()->plugin_url() . '/assets/js/admin/marketplace-suggestions' . $suffix . '.js',
array( 'jquery', 'underscore', 'js-cookie' ),
WC_VERSION,
true
);
wp_localize_script(
'marketplace-suggestions',
'marketplace_suggestions',
array(
'dismiss_suggestion_nonce' => wp_create_nonce( 'add_dismissed_marketplace_suggestion' ),
'active_plugins' => $active_plugin_slugs,
'dismissed_suggestions' => WC_Marketplace_Suggestions::get_dismissed_suggestions(),
'suggestions_data' => WC_Marketplace_Suggestions::get_suggestions_api_data(),
'i18n_marketplace_suggestions_default_cta'
=> esc_html__( 'Learn More', 'woocommerce' ),
'i18n_marketplace_suggestions_dismiss_tooltip'
=> esc_attr__( 'Dismiss this suggestion', 'woocommerce' ),
)
);
wp_enqueue_script( 'marketplace-suggestions' );
}
}
}
endif;

View File

@ -26,9 +26,9 @@ class WC_Admin_Attributes {
$action = '';
// Action to perform: add, edit, delete or none.
if ( ! empty( $_POST['add_new_attribute'] ) ) {
if ( ! empty( $_POST['add_new_attribute'] ) ) { // WPCS: CSRF ok.
$action = 'add';
} elseif ( ! empty( $_POST['save_attribute'] ) && ! empty( $_GET['edit'] ) ) {
} elseif ( ! empty( $_POST['save_attribute'] ) && ! empty( $_GET['edit'] ) ) { // WPCS: CSRF ok.
$action = 'edit';
} elseif ( ! empty( $_GET['delete'] ) ) {
$action = 'delete';
@ -65,11 +65,11 @@ class WC_Admin_Attributes {
*/
private static function get_posted_attribute() {
$attribute = array(
'attribute_label' => isset( $_POST['attribute_label'] ) ? wc_clean( stripslashes( $_POST['attribute_label'] ) ) : '',
'attribute_name' => isset( $_POST['attribute_name'] ) ? wc_sanitize_taxonomy_name( stripslashes( $_POST['attribute_name'] ) ) : '',
'attribute_type' => isset( $_POST['attribute_type'] ) ? wc_clean( $_POST['attribute_type'] ) : 'select',
'attribute_orderby' => isset( $_POST['attribute_orderby'] ) ? wc_clean( $_POST['attribute_orderby'] ) : '',
'attribute_public' => isset( $_POST['attribute_public'] ) ? 1 : 0,
'attribute_label' => isset( $_POST['attribute_label'] ) ? wc_clean( wp_unslash( $_POST['attribute_label'] ) ) : '', // WPCS: input var ok, CSRF ok.
'attribute_name' => isset( $_POST['attribute_name'] ) ? wc_sanitize_taxonomy_name( wp_unslash( $_POST['attribute_name'] ) ) : '', // WPCS: input var ok, CSRF ok, sanitization ok.
'attribute_type' => isset( $_POST['attribute_type'] ) ? wc_clean( wp_unslash( $_POST['attribute_type'] ) ) : 'select', // WPCS: input var ok, CSRF ok.
'attribute_orderby' => isset( $_POST['attribute_orderby'] ) ? wc_clean( wp_unslash( $_POST['attribute_orderby'] ) ) : '', // WPCS: input var ok, CSRF ok.
'attribute_public' => isset( $_POST['attribute_public'] ) ? 1 : 0, // WPCS: input var ok, CSRF ok.
);
if ( empty( $attribute['attribute_type'] ) ) {
@ -117,7 +117,7 @@ class WC_Admin_Attributes {
* @return bool|WP_Error
*/
private static function process_edit_attribute() {
$attribute_id = absint( $_GET['edit'] );
$attribute_id = isset( $_GET['edit'] ) ? absint( $_GET['edit'] ) : 0;
check_admin_referer( 'woocommerce-save-attribute_' . $attribute_id );
$attribute = self::get_posted_attribute();
@ -135,7 +135,7 @@ class WC_Admin_Attributes {
return $id;
}
echo '<div class="updated"><p>' . __( 'Attribute updated successfully', 'woocommerce' ) . '</p><p><a href="' . esc_url( admin_url( 'edit.php?post_type=product&amp;page=product_attributes' ) ) . '">' . __( 'Back to Attributes', 'woocommerce' ) . '</a></p></div>';
echo '<div class="updated"><p>' . esc_html__( 'Attribute updated successfully', 'woocommerce' ) . '</p><p><a href="' . esc_url( admin_url( 'edit.php?post_type=product&amp;page=product_attributes' ) ) . '">' . esc_html__( 'Back to Attributes', 'woocommerce' ) . '</a></p></div>';
return true;
}
@ -146,7 +146,7 @@ class WC_Admin_Attributes {
* @return bool
*/
private static function process_delete_attribute() {
$attribute_id = absint( $_GET['delete'] );
$attribute_id = isset( $_GET['delete'] ) ? absint( $_GET['delete'] ) : 0;
check_admin_referer( 'woocommerce-delete-attribute_' . $attribute_id );
return wc_delete_attribute( $attribute_id );
@ -160,9 +160,17 @@ class WC_Admin_Attributes {
public static function edit_attribute() {
global $wpdb;
$edit = absint( $_GET['edit'] );
$edit = isset( $_GET['edit'] ) ? absint( $_GET['edit'] ) : 0;
$attribute_to_edit = $wpdb->get_row( 'SELECT attribute_type, attribute_label, attribute_name, attribute_orderby, attribute_public FROM ' . $wpdb->prefix . "woocommerce_attribute_taxonomies WHERE attribute_id = '$edit'" );
$attribute_to_edit = $wpdb->get_row(
$wpdb->prepare(
"
SELECT attribute_type, attribute_label, attribute_name, attribute_orderby, attribute_public
FROM {$wpdb->prefix}woocommerce_attribute_taxonomies WHERE attribute_id = %d
",
$edit
)
);
?>
<div class="wrap woocommerce">
@ -297,7 +305,8 @@ class WC_Admin_Attributes {
</thead>
<tbody>
<?php
if ( $attribute_taxonomies = wc_get_attribute_taxonomies() ) :
$attribute_taxonomies = wc_get_attribute_taxonomies();
if ( $attribute_taxonomies ) :
foreach ( $attribute_taxonomies as $tax ) :
?>
<tr>
@ -333,21 +342,7 @@ class WC_Admin_Attributes {
$taxonomy = wc_attribute_taxonomy_name( $tax->attribute_name );
if ( taxonomy_exists( $taxonomy ) ) {
if ( 'menu_order' === wc_attribute_orderby( $taxonomy ) ) {
$terms = get_terms( $taxonomy, 'hide_empty=0&menu_order=ASC' );
} else {
$terms = get_terms( $taxonomy, 'hide_empty=0&menu_order=false' );
}
switch ( $tax->attribute_orderby ) {
case 'name_num':
usort( $terms, '_wc_get_product_terms_name_num_usort_callback' );
break;
case 'parent':
usort( $terms, '_wc_get_product_terms_parent_usort_callback' );
break;
}
$terms = get_terms( $taxonomy, 'hide_empty=0' );
$terms_string = implode( ', ', wp_list_pluck( $terms, 'name' ) );
if ( $terms_string ) {
echo esc_html( $terms_string );
@ -370,7 +365,7 @@ class WC_Admin_Attributes {
</tr>
<?php
endif;
?>
?>
</tbody>
</table>
</div>

View File

@ -91,7 +91,7 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :
$sales_by_date = new WC_Report_Sales_By_Date();
$sales_by_date->start_date = strtotime( date( 'Y-m-01', current_time( 'timestamp' ) ) );
$sales_by_date->end_date = current_time( 'timestamp' );
$sales_by_date->end_date = strtotime( date( 'Y-m-d', current_time( 'timestamp' ) ) );
$sales_by_date->chart_groupby = 'day';
$sales_by_date->group_by_query = 'YEAR(posts.post_date), MONTH(posts.post_date), DAY(posts.post_date)';

View File

@ -49,7 +49,6 @@ class WC_Admin_Taxonomies {
// Category/term ordering.
add_action( 'create_term', array( $this, 'create_term' ), 5, 3 );
add_action( 'delete_term', array( $this, 'delete_term' ), 5 );
// Add form.
add_action( 'product_cat_add_form_fields', array( $this, 'add_category_fields' ) );
@ -98,22 +97,17 @@ class WC_Admin_Taxonomies {
$meta_name = taxonomy_is_product_attribute( $taxonomy ) ? 'order_' . esc_attr( $taxonomy ) : 'order';
update_woocommerce_term_meta( $term_id, $meta_name, 0 );
update_term_meta( $term_id, $meta_name, 0 );
}
/**
* When a term is deleted, delete its meta.
*
* @deprecated 3.6.0 No longer needed.
* @param mixed $term_id Term ID.
*/
public function delete_term( $term_id ) {
global $wpdb;
$term_id = absint( $term_id );
if ( $term_id && get_option( 'db_version' ) < 34370 ) {
$wpdb->delete( $wpdb->woocommerce_termmeta, array( 'woocommerce_term_id' => $term_id ), array( '%d' ) );
}
wc_deprecated_function( 'delete_term', '3.6' );
}
/**
@ -219,8 +213,8 @@ class WC_Admin_Taxonomies {
*/
public function edit_category_fields( $term ) {
$display_type = get_woocommerce_term_meta( $term->term_id, 'display_type', true );
$thumbnail_id = absint( get_woocommerce_term_meta( $term->term_id, 'thumbnail_id', true ) );
$display_type = get_term_meta( $term->term_id, 'display_type', true );
$thumbnail_id = absint( get_term_meta( $term->term_id, 'thumbnail_id', true ) );
if ( $thumbnail_id ) {
$image = wp_get_attachment_thumb_url( $thumbnail_id );
@ -314,10 +308,10 @@ class WC_Admin_Taxonomies {
*/
public function save_category_fields( $term_id, $tt_id = '', $taxonomy = '' ) {
if ( isset( $_POST['display_type'] ) && 'product_cat' === $taxonomy ) { // WPCS: CSRF ok, input var ok.
update_woocommerce_term_meta( $term_id, 'display_type', esc_attr( $_POST['display_type'] ) ); // WPCS: CSRF ok, sanitization ok, input var ok.
update_term_meta( $term_id, 'display_type', esc_attr( $_POST['display_type'] ) ); // WPCS: CSRF ok, sanitization ok, input var ok.
}
if ( isset( $_POST['product_cat_thumbnail_id'] ) && 'product_cat' === $taxonomy ) { // WPCS: CSRF ok, input var ok.
update_woocommerce_term_meta( $term_id, 'thumbnail_id', absint( $_POST['product_cat_thumbnail_id'] ) ); // WPCS: CSRF ok, input var ok.
update_term_meta( $term_id, 'thumbnail_id', absint( $_POST['product_cat_thumbnail_id'] ) ); // WPCS: CSRF ok, input var ok.
}
}
@ -440,7 +434,7 @@ class WC_Admin_Taxonomies {
$columns .= wc_help_tip( __( 'This is the default category and it cannot be deleted. It will be automatically assigned to products with no category.', 'woocommerce' ) );
}
$thumbnail_id = get_woocommerce_term_meta( $id, 'thumbnail_id', true );
$thumbnail_id = get_term_meta( $id, 'thumbnail_id', true );
if ( $thumbnail_id ) {
$image = wp_get_attachment_thumb_url( $thumbnail_id );
@ -481,14 +475,14 @@ class WC_Admin_Taxonomies {
return;
}
// Ensure the tooltip is displayed when the image column is disabled on product categories.
wc_enqueue_js("
(function( $ ) {
wc_enqueue_js(
"(function( $ ) {
'use strict';
var product_cat = $( 'tr#tag-" . absint( $this->default_cat_id ) . "' );
product_cat.find( 'th' ).empty();
product_cat.find( 'td.thumb span' ).detach( 'span' ).appendTo( product_cat.find( 'th' ) );
})( jQuery );
");
})( jQuery );"
);
}
}

View File

@ -85,6 +85,10 @@ class WC_Admin {
include_once dirname( __FILE__ ) . '/helper/class-wc-helper-plugin-info.php';
include_once dirname( __FILE__ ) . '/helper/class-wc-helper-compat.php';
include_once dirname( __FILE__ ) . '/helper/class-wc-helper.php';
// Marketplace suggestions & related REST API.
include_once dirname( __FILE__ ) . '/marketplace-suggestions/class-wc-marketplace-suggestions.php';
include_once dirname( __FILE__ ) . '/marketplace-suggestions/class-wc-marketplace-updater.php';
}
/**

View File

@ -1007,7 +1007,7 @@ class WC_Helper {
}
/**
* Obtain a list of locally installed Woo extensions.
* Obtain a list of data about locally installed Woo extensions.
*/
public static function get_local_woo_plugins() {
if ( ! function_exists( 'get_plugins' ) ) {
@ -1046,6 +1046,7 @@ class WC_Helper {
$data['_product_id'] = absint( $product_id );
$data['_file_id'] = $file_id;
$data['_type'] = 'plugin';
$data['slug'] = dirname( $filename );
$woo_plugins[ $filename ] = $data;
}

View File

@ -47,9 +47,16 @@ class WC_Admin_List_Table_Orders extends WC_Admin_List_Table {
*/
protected function render_blank_state() {
echo '<div class="woocommerce-BlankState">';
echo '<h2 class="woocommerce-BlankState-message">' . esc_html__( 'When you receive a new order, it will appear here.', 'woocommerce' ) . '</h2>';
echo '<div class="woocommerce-BlankState-buttons">';
echo '<a class="woocommerce-BlankState-cta button-primary button" target="_blank" href="https://docs.woocommerce.com/document/managing-orders/?utm_source=blankslate&utm_medium=product&utm_content=ordersdoc&utm_campaign=woocommerceplugin">' . esc_html__( 'Learn more about orders', 'woocommerce' ) . '</a>';
echo '</div>';
do_action( 'wc_marketplace_suggestions_orders_empty_state' );
echo '</div>';
}
/**

View File

@ -48,9 +48,18 @@ class WC_Admin_List_Table_Products extends WC_Admin_List_Table {
*/
protected function render_blank_state() {
echo '<div class="woocommerce-BlankState">';
echo '<h2 class="woocommerce-BlankState-message">' . esc_html__( 'Ready to start selling something awesome?', 'woocommerce' ) . '</h2>';
echo '<a class="woocommerce-BlankState-cta button-primary button" href="' . esc_url( admin_url( 'post-new.php?post_type=product&tutorial=true' ) ) . '">' . esc_html__( 'Create your first product!', 'woocommerce' ) . '</a>';
echo '<a class="woocommerce-BlankState-cta button" href="' . esc_url( admin_url( 'edit.php?post_type=product&page=product_importer' ) ) . '">' . esc_html__( 'Import products from a CSV file', 'woocommerce' ) . '</a>';
echo '<div class="woocommerce-BlankState-buttons">';
echo '<a class="woocommerce-BlankState-cta button-primary button" href="' . esc_url( admin_url( 'post-new.php?post_type=product&tutorial=true' ) ) . '">' . esc_html__( 'Create Product', 'woocommerce' ) . '</a>';
echo '<a class="woocommerce-BlankState-cta button" href="' . esc_url( admin_url( 'edit.php?post_type=product&page=product_importer' ) ) . '">' . esc_html__( 'Start Import', 'woocommerce' ) . '</a>';
echo '</div>';
do_action( 'wc_marketplace_suggestions_products_empty_state' );
echo '</div>';
}

View File

@ -0,0 +1,197 @@
<?php
/**
* Marketplace suggestions
*
* Behaviour for displaying in-context suggestions for marketplace extensions.
*
* @package WooCommerce\Classes
* @since 3.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* Marketplace suggestions core behaviour.
*/
class WC_Marketplace_Suggestions {
/**
* Initialise.
*/
public static function init() {
if ( ! self::allow_suggestions() ) {
return;
}
// Add suggestions to the product tabs.
add_action( 'woocommerce_product_data_tabs', array( __CLASS__, 'product_data_tabs' ) );
add_action( 'woocommerce_product_data_panels', array( __CLASS__, 'product_data_panels' ) );
// Register ajax api handlers.
add_action( 'wp_ajax_woocommerce_add_dismissed_marketplace_suggestion', array( __CLASS__, 'post_add_dismissed_suggestion_handler' ) );
// Register hooks for rendering suggestions container markup.
add_action( 'wc_marketplace_suggestions_products_empty_state', array( __CLASS__, 'render_products_list_empty_state' ) );
add_action( 'wc_marketplace_suggestions_orders_empty_state', array( __CLASS__, 'render_orders_list_empty_state' ) );
}
/**
* Product data tabs filter
*
* Adds a new Extensions tab to the product data meta box.
*
* @param array $tabs Existing tabs.
*
* @return array
*/
public static function product_data_tabs( $tabs ) {
$tabs['marketplace-suggestions'] = array(
'label' => _x( 'Get more options', 'Marketplace suggestions', 'woocommerce' ),
'target' => 'marketplace_suggestions',
'class' => array(),
'priority' => 1000,
);
return $tabs;
}
/**
* Render additional panels in the proudct data metabox.
*/
public static function product_data_panels() {
include dirname( __FILE__ ) . '/templates/html-product-data-extensions.php';
}
/**
* Return an array of suggestions the user has dismissed.
*/
public static function get_dismissed_suggestions() {
$dismissed_suggestions = array();
$dismissed_suggestions_data = get_user_meta( get_current_user_id(), 'wc_marketplace_suggestions_dismissed_suggestions', true );
if ( $dismissed_suggestions_data ) {
$dismissed_suggestions = $dismissed_suggestions_data;
if ( ! is_array( $dismissed_suggestions ) ) {
$dismissed_suggestions = array();
}
}
return $dismissed_suggestions;
}
/**
* POST handler for adding a dismissed suggestion.
*/
public static function post_add_dismissed_suggestion_handler() {
if ( ! check_ajax_referer( 'add_dismissed_marketplace_suggestion' ) ) {
wp_die();
}
$post_data = wp_unslash( $_POST );
$suggestion_slug = sanitize_text_field( $post_data['slug'] );
if ( ! $suggestion_slug ) {
wp_die();
}
$dismissed_suggestions = self::get_dismissed_suggestions();
if ( in_array( $suggestion_slug, $dismissed_suggestions, true ) ) {
wp_die();
}
$dismissed_suggestions[] = $suggestion_slug;
update_user_meta(
get_current_user_id(),
'wc_marketplace_suggestions_dismissed_suggestions',
$dismissed_suggestions
);
wp_die();
}
/**
* Render suggestions containers in products list empty state.
*/
public static function render_products_list_empty_state() {
self::render_suggestions_container( 'products-list-empty-header' );
self::render_suggestions_container( 'products-list-empty-body' );
self::render_suggestions_container( 'products-list-empty-footer' );
}
/**
* Render suggestions containers in orders list empty state.
*/
public static function render_orders_list_empty_state() {
self::render_suggestions_container( 'orders-list-empty-header' );
self::render_suggestions_container( 'orders-list-empty-body' );
self::render_suggestions_container( 'orders-list-empty-footer' );
}
/**
* Render a suggestions container element, with the specified context.
*
* @param string $context Suggestion context name (rendered as a css class).
*/
public static function render_suggestions_container( $context ) {
include dirname( __FILE__ ) . '/views/container.php';
}
/**
* Should suggestions be displayed?
*
* @param string $screen_id The current admin screen.
*
* @return bool
*/
public static function show_suggestions_for_screen( $screen_id ) {
// We only show suggestions on certain admin screens.
if ( ! in_array( $screen_id, array( 'edit-product', 'edit-shop_order', 'product' ), true ) ) {
return false;
}
return self::allow_suggestions();
}
/**
* Should suggestions be displayed?
*
* @return bool
*/
public static function allow_suggestions() {
// We currently only support English suggestions.
$locale = get_locale();
$suggestion_locales = array(
'en_AU',
'en_CA',
'en_GB',
'en_NZ',
'en_US',
'en_ZA',
);
if ( ! in_array( $locale, $suggestion_locales, true ) ) {
return false;
}
// Suggestions are only displayed if user can install plugins.
if ( ! current_user_can( 'install_plugins' ) ) {
return false;
}
// User can disabled all suggestions via filter.
return apply_filters( 'woocommerce_allow_marketplace_suggestions', true );
}
/**
* Pull suggestion data from remote endpoint & cache in a transient.
*
* @return array of json API data
*/
public static function get_suggestions_api_data() {
$data = get_option( 'woocommerce_marketplace_suggestions', array() );
return ! empty( $data['suggestions'] ) ? $data['suggestions'] : array();
}
}
WC_Marketplace_Suggestions::init();

View File

@ -0,0 +1,86 @@
<?php
/**
* Marketplace suggestions updater
*
* Uses WC_Queue to ensure marketplace suggestions data is up to date and cached locally.
*
* @package WooCommerce\Classes
* @since 3.6.0
*/
defined( 'ABSPATH' ) || exit;
/**
* Marketplace Suggestions Updater
*/
class WC_Marketplace_Updater {
/**
* Setup.
*/
public static function load() {
add_action( 'init', array( __CLASS__, 'init' ) );
}
/**
* Schedule events and hook appropriate actions.
*/
public static function init() {
$queue = WC()->queue();
$next = $queue->get_next( 'woocommerce_update_marketplace_suggestions' );
if ( ! $next ) {
$queue->schedule_recurring( time(), WEEK_IN_SECONDS, 'woocommerce_update_marketplace_suggestions' );
}
add_action( 'woocommerce_update_marketplace_suggestions', array( __CLASS__, 'update_marketplace_suggestions' ) );
}
/**
* Fetches new marketplace data, updates wc_marketplace_suggestions.
*/
public static function update_marketplace_suggestions() {
$data = get_option(
'woocommerce_marketplace_suggestions',
array(
'suggestions' => array(),
'updated' => time(),
)
);
$data['updated'] = time();
$url = 'https://woocommerce.com/wp-json/wccom/marketplace-suggestions/1.0/suggestions.json';
$request = wp_safe_remote_get( $url );
if ( is_wp_error( $request ) ) {
self::retry();
return update_option( 'woocommerce_marketplace_suggestions', $data, false );
}
$body = wp_remote_retrieve_body( $request );
if ( empty( $body ) ) {
self::retry();
return update_option( 'woocommerce_marketplace_suggestions', $data, false );
}
$body = json_decode( $body, true );
if ( empty( $body ) || ! is_array( $body ) ) {
self::retry();
return update_option( 'woocommerce_marketplace_suggestions', $data, false );
}
$data['suggestions'] = $body;
return update_option( 'woocommerce_marketplace_suggestions', $data, false );
}
/**
* Used when an error has occured when fetching suggestions.
* Re-schedules the job earlier than the main weekly one.
*/
public static function retry() {
WC()->queue()->cancel( 'woocommerce_update_marketplace_suggestions' );
WC()->queue()->schedule_single( time() + DAY_IN_SECONDS, 'woocommerce_update_marketplace_suggestions' );
}
}
WC_Marketplace_Updater::load();

View File

@ -0,0 +1,20 @@
<?php
/**
* The marketplace suggestions tab HTML in the product tabs
*
* @package WooCommerce\Classes
* @since 3.6.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<div id="marketplace_suggestions" class="panel woocommerce_options_panel hidden">
<?php
WC_Marketplace_Suggestions::render_suggestions_container( 'product-edit-meta-tab-header' );
WC_Marketplace_Suggestions::render_suggestions_container( 'product-edit-meta-tab-body' );
WC_Marketplace_Suggestions::render_suggestions_container( 'product-edit-meta-tab-footer' );
?>
</div>

View File

@ -0,0 +1,17 @@
<?php
/**
* Marketplace suggestions container
*
* @package WooCommerce/Templates
* @version 3.6.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<div class="marketplace-suggestions-container"
data-marketplace-suggestions-context="<?php echo esc_attr( $context ); ?>"
>
</div>

View File

@ -34,10 +34,10 @@ class WC_REST_Product_Categories_Controller extends WC_REST_Product_Categories_V
*/
public function prepare_item_for_response( $item, $request ) {
// Get category display type.
$display_type = get_woocommerce_term_meta( $item->term_id, 'display_type' );
$display_type = get_term_meta( $item->term_id, 'display_type' );
// Get category order.
$menu_order = get_woocommerce_term_meta( $item->term_id, 'order' );
$menu_order = get_term_meta( $item->term_id, 'order' );
$data = array(
'id' => (int) $item->term_id,
@ -52,7 +52,7 @@ class WC_REST_Product_Categories_Controller extends WC_REST_Product_Categories_V
);
// Get category image.
$image_id = get_woocommerce_term_meta( $item->term_id, 'thumbnail_id' );
$image_id = get_term_meta( $item->term_id, 'thumbnail_id' );
if ( $image_id ) {
$attachment = get_post( $image_id );

View File

@ -607,11 +607,11 @@ class WC_API_Products extends WC_API_Resource {
$term_id = intval( $term->term_id );
// Get category display type
$display_type = get_woocommerce_term_meta( $term_id, 'display_type' );
$display_type = get_term_meta( $term_id, 'display_type' );
// Get category image
$image = '';
if ( $image_id = get_woocommerce_term_meta( $term_id, 'thumbnail_id' ) ) {
if ( $image_id = get_term_meta( $term_id, 'thumbnail_id' ) ) {
$image = wp_get_attachment_url( $image_id );
}

View File

@ -666,11 +666,11 @@ class WC_API_Products extends WC_API_Resource {
$term_id = intval( $term->term_id );
// Get category display type
$display_type = get_woocommerce_term_meta( $term_id, 'display_type' );
$display_type = get_term_meta( $term_id, 'display_type' );
// Get category image
$image = '';
if ( $image_id = get_woocommerce_term_meta( $term_id, 'thumbnail_id' ) ) {
if ( $image_id = get_term_meta( $term_id, 'thumbnail_id' ) ) {
$image = wp_get_attachment_url( $image_id );
}
@ -750,11 +750,11 @@ class WC_API_Products extends WC_API_Resource {
$id = $insert['term_id'];
update_woocommerce_term_meta( $id, 'display_type', 'default' === $data['display'] ? '' : sanitize_text_field( $data['display'] ) );
update_term_meta( $id, 'display_type', 'default' === $data['display'] ? '' : sanitize_text_field( $data['display'] ) );
// Check if image_id is a valid image attachment before updating the term meta.
if ( $image_id && wp_attachment_is_image( $image_id ) ) {
update_woocommerce_term_meta( $id, 'thumbnail_id', $image_id );
update_term_meta( $id, 'thumbnail_id', $image_id );
}
do_action( 'woocommerce_api_create_product_category', $id, $data );
@ -823,11 +823,11 @@ class WC_API_Products extends WC_API_Resource {
}
if ( ! empty( $data['display'] ) ) {
update_woocommerce_term_meta( $id, 'display_type', 'default' === $data['display'] ? '' : sanitize_text_field( $data['display'] ) );
update_term_meta( $id, 'display_type', 'default' === $data['display'] ? '' : sanitize_text_field( $data['display'] ) );
}
if ( isset( $image_id ) ) {
update_woocommerce_term_meta( $id, 'thumbnail_id', $image_id );
update_term_meta( $id, 'thumbnail_id', $image_id );
}
do_action( 'woocommerce_api_edit_product_category', $id, $data );
@ -861,11 +861,6 @@ class WC_API_Products extends WC_API_Resource {
throw new WC_API_Exception( 'woocommerce_api_cannot_delete_product_category', __( 'Could not delete the category', 'woocommerce' ), 401 );
}
// When a term is deleted, delete its meta.
if ( get_option( 'db_version' ) < 34370 ) {
$wpdb->delete( $wpdb->woocommerce_termmeta, array( 'woocommerce_term_id' => $id ), array( '%d' ) );
}
do_action( 'woocommerce_api_delete_product_category', $id, $this );
return array( 'message' => sprintf( __( 'Deleted %s', 'woocommerce' ), 'product_category' ) );
@ -2760,25 +2755,7 @@ class WC_API_Products extends WC_API_Resource {
throw new WC_API_Exception( 'woocommerce_api_invalid_product_attribute_id', __( 'A product attribute with the provided ID could not be found', 'woocommerce' ), 404 );
}
$args = array( 'hide_empty' => false );
$orderby = wc_attribute_orderby( $taxonomy );
switch ( $orderby ) {
case 'name' :
$args['orderby'] = 'name';
$args['menu_order'] = false;
break;
case 'id' :
$args['orderby'] = 'id';
$args['order'] = 'ASC';
$args['menu_order'] = false;
break;
case 'menu_order' :
$args['menu_order'] = 'ASC';
break;
}
$terms = get_terms( $taxonomy, $args );
$terms = get_terms( $taxonomy, array( 'hide_empty' => false ) );
$attribute_terms = array();
foreach ( $terms as $term ) {

View File

@ -134,7 +134,7 @@ class WC_REST_Product_Attribute_Terms_V1_Controller extends WC_REST_Terms_Contro
*/
public function prepare_item_for_response( $item, $request ) {
// Get term order.
$menu_order = get_woocommerce_term_meta( $item->term_id, 'order_' . $this->taxonomy );
$menu_order = get_term_meta( $item->term_id, 'order_' . $this->taxonomy );
$data = array(
'id' => (int) $item->term_id,
@ -175,7 +175,7 @@ class WC_REST_Product_Attribute_Terms_V1_Controller extends WC_REST_Terms_Contro
protected function update_term_meta_fields( $term, $request ) {
$id = (int) $term->term_id;
update_woocommerce_term_meta( $id, 'order_' . $this->taxonomy, $request['menu_order'] );
update_term_meta( $id, 'order_' . $this->taxonomy, $request['menu_order'] );
return true;
}

View File

@ -52,10 +52,10 @@ class WC_REST_Product_Categories_V1_Controller extends WC_REST_Terms_Controller
*/
public function prepare_item_for_response( $item, $request ) {
// Get category display type.
$display_type = get_woocommerce_term_meta( $item->term_id, 'display_type' );
$display_type = get_term_meta( $item->term_id, 'display_type' );
// Get category order.
$menu_order = get_woocommerce_term_meta( $item->term_id, 'order' );
$menu_order = get_term_meta( $item->term_id, 'order' );
$data = array(
'id' => (int) $item->term_id,
@ -70,7 +70,7 @@ class WC_REST_Product_Categories_V1_Controller extends WC_REST_Terms_Controller
);
// Get category image.
$image_id = get_woocommerce_term_meta( $item->term_id, 'thumbnail_id' );
$image_id = get_term_meta( $item->term_id, 'thumbnail_id' );
if ( $image_id ) {
$attachment = get_post( $image_id );
@ -115,11 +115,11 @@ class WC_REST_Product_Categories_V1_Controller extends WC_REST_Terms_Controller
$id = (int) $term->term_id;
if ( isset( $request['display'] ) ) {
update_woocommerce_term_meta( $id, 'display_type', 'default' === $request['display'] ? '' : $request['display'] );
update_term_meta( $id, 'display_type', 'default' === $request['display'] ? '' : $request['display'] );
}
if ( isset( $request['menu_order'] ) ) {
update_woocommerce_term_meta( $id, 'order', $request['menu_order'] );
update_term_meta( $id, 'order', $request['menu_order'] );
}
if ( isset( $request['image'] ) ) {
@ -137,7 +137,7 @@ class WC_REST_Product_Categories_V1_Controller extends WC_REST_Terms_Controller
// Check if image_id is a valid image attachment before updating the term meta.
if ( $image_id && wp_attachment_is_image( $image_id ) ) {
update_woocommerce_term_meta( $id, 'thumbnail_id', $image_id );
update_term_meta( $id, 'thumbnail_id', $image_id );
// Set the image alt.
if ( ! empty( $request['image']['alt'] ) ) {
@ -152,7 +152,7 @@ class WC_REST_Product_Categories_V1_Controller extends WC_REST_Terms_Controller
) );
}
} else {
delete_woocommerce_term_meta( $id, 'thumbnail_id' );
delete_term_meta( $id, 'thumbnail_id' );
}
}

View File

@ -34,10 +34,10 @@ class WC_REST_Product_Categories_V2_Controller extends WC_REST_Product_Categorie
*/
public function prepare_item_for_response( $item, $request ) {
// Get category display type.
$display_type = get_woocommerce_term_meta( $item->term_id, 'display_type' );
$display_type = get_term_meta( $item->term_id, 'display_type' );
// Get category order.
$menu_order = get_woocommerce_term_meta( $item->term_id, 'order' );
$menu_order = get_term_meta( $item->term_id, 'order' );
$data = array(
'id' => (int) $item->term_id,
@ -52,7 +52,7 @@ class WC_REST_Product_Categories_V2_Controller extends WC_REST_Product_Categorie
);
// Get category image.
$image_id = get_woocommerce_term_meta( $item->term_id, 'thumbnail_id' );
$image_id = get_term_meta( $item->term_id, 'thumbnail_id' );
if ( $image_id ) {
$attachment = get_post( $image_id );

View File

@ -698,10 +698,6 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
)
);
if ( get_option( 'db_version' ) < 34370 ) {
$core_tables[] = 'woocommerce_termmeta';
}
/**
* Adding the prefix to the tables array, for backwards compatibility.
*

View File

@ -90,6 +90,30 @@ class WC_Countries {
return '';
}
/**
* Get calling code for a country code.
*
* @since 3.6.0
* @param string $cc Country code.
* @return string|array Some countries have multiple. The code will be stripped of - and spaces and always be prefixed with +.
*/
public function get_country_calling_code( $cc ) {
$codes = wp_cache_get( 'calling-codes', 'countries' );
if ( ! $codes ) {
$codes = include WC()->plugin_path() . '/i18n/phone.php';
wp_cache_set( 'calling-codes', $codes, 'countries' );
}
$calling_code = isset( $codes[ $cc ] ) ? $codes[ $cc ] : '';
if ( is_array( $calling_code ) ) {
$calling_code = $calling_code[0];
}
return $calling_code;
}
/**
* Get continents that the store ships to.
*

View File

@ -126,6 +126,7 @@ class WC_Install {
),
'3.6.0' => array(
'wc_update_360_product_lookup_tables',
'wc_update_360_term_meta',
'wc_update_360_downloadable_product_permissions_index',
'wc_update_360_db_version',
),
@ -539,7 +540,6 @@ class WC_Install {
*
* Tables:
* woocommerce_attribute_taxonomies - Table for storing attribute taxonomies - these are user defined
* woocommerce_termmeta - Term meta table - sadly WordPress does not have termmeta so we need our own
* woocommerce_downloadable_product_permissions - Table for storing user and guest download permissions.
* KEY(order_id, product_id, download_id) used for organizing downloads on the My Account page
* woocommerce_order_items - Order line items are stored in a table to make them easily queryable for reports
@ -844,23 +844,6 @@ CREATE TABLE {$wpdb->prefix}wc_product_meta_lookup (
) $collate;
";
/**
* Term meta is only needed for old installs and is now @deprecated by WordPress term meta.
*/
if ( ! function_exists( 'get_term_meta' ) ) {
$tables .= "
CREATE TABLE {$wpdb->prefix}woocommerce_termmeta (
meta_id BIGINT UNSIGNED NOT NULL auto_increment,
woocommerce_term_id BIGINT UNSIGNED NOT NULL,
meta_key varchar(255) default NULL,
meta_value longtext NULL,
PRIMARY KEY (meta_id),
KEY woocommerce_term_id (woocommerce_term_id),
KEY meta_key (meta_key(32))
) $collate;
";
}
return $tables;
}
@ -893,11 +876,6 @@ CREATE TABLE {$wpdb->prefix}woocommerce_termmeta (
"{$wpdb->prefix}woocommerce_tax_rates",
);
if ( ! function_exists( 'get_term_meta' ) ) {
// This table is only needed for old installs and is now @deprecated by WordPress term meta.
$tables[] = "{$wpdb->prefix}woocommerce_termmeta";
}
/**
* Filter the list of known WooCommerce tables.
*

View File

@ -372,6 +372,7 @@ final class WooCommerce {
include_once WC_ABSPATH . 'includes/class-wc-logger.php';
include_once WC_ABSPATH . 'includes/queue/class-wc-action-queue.php';
include_once WC_ABSPATH . 'includes/queue/class-wc-queue.php';
include_once WC_ABSPATH . 'includes/admin/marketplace-suggestions/class-wc-marketplace-updater.php';
/**
* Data stores - used to store and retrieve CRUD object data from the database.
@ -712,12 +713,7 @@ final class WooCommerce {
$wpdb->tables[] = 'woocommerce_order_itemmeta';
$wpdb->wc_product_meta_lookup = $wpdb->prefix . 'wc_product_meta_lookup';
$wpdb->tables[] = 'wc_product_meta_lookup';
if ( get_option( 'db_version' ) < 34370 ) {
$wpdb->woocommerce_termmeta = $wpdb->prefix . 'woocommerce_termmeta';
$wpdb->tables[] = 'woocommerce_termmeta';
}
$wpdb->tables[] = 'wc_product_meta_lookup';
}
/**

View File

@ -216,8 +216,9 @@ class WC_Gateway_Paypal_Request {
* @return array
*/
protected function get_phone_number_args( $order ) {
$phone_number = str_replace( array( '(', '-', ' ', ')', '.' ), '', $order->get_billing_phone() );
if ( in_array( $order->get_billing_country(), array( 'US', 'CA' ), true ) ) {
$phone_number = str_replace( array( '(', '-', ' ', ')', '.' ), '', $order->get_billing_phone() );
$phone_number = ltrim( $phone_number, '+1' );
$phone_args = array(
'night_phone_a' => substr( $phone_number, 0, 3 ),
@ -225,8 +226,16 @@ class WC_Gateway_Paypal_Request {
'night_phone_c' => substr( $phone_number, 6, 4 ),
);
} else {
$calling_code = WC()->countries->get_country_calling_code( $order->get_billing_country() );
$calling_code = is_array( $calling_code ) ? $calling_code[0] : $calling_code;
if ( $calling_code ) {
$phone_number = str_replace( $calling_code, '', preg_replace( '/^0/', '', $order->get_billing_phone() ) );
}
$phone_args = array(
'night_phone_b' => $order->get_billing_phone(),
'night_phone_a' => $calling_code,
'night_phone_b' => $phone_number,
);
}
return $phone_args;

View File

@ -531,9 +531,8 @@ function wc_create_attribute( $args ) {
);
// Update taxonomy ordering term meta.
$table_name = get_option( 'db_version' ) < 34370 ? $wpdb->prefix . 'woocommerce_termmeta' : $wpdb->termmeta;
$wpdb->update(
$table_name,
$wpdb->termmeta,
array( 'meta_key' => 'order_pa_' . sanitize_title( $data['attribute_name'] ) ), // WPCS: slow query ok.
array( 'meta_key' => 'order_pa_' . sanitize_title( $old_slug ) ) // WPCS: slow query ok.
);

View File

@ -1038,3 +1038,87 @@ function wc_get_min_max_price_meta_query( $args ) {
$args
);
}
/**
* When a term is split, ensure meta data maintained.
*
* @deprecated 3.6.0
* @param int $old_term_id Old term ID.
* @param int $new_term_id New term ID.
* @param string $term_taxonomy_id Term taxonomy ID.
* @param string $taxonomy Taxonomy.
*/
function wc_taxonomy_metadata_update_content_for_split_terms( $old_term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
wc_deprecated_function( 'wc_taxonomy_metadata_update_content_for_split_terms', '3.6' );
}
/**
* WooCommerce Term Meta API.
*
* WC tables for storing term meta are deprecated from WordPress 4.4 since 4.4 has its own table.
* This function serves as a wrapper, using the new table if present, or falling back to the WC table.
*
* @deprecated 3.6.0
* @param int $term_id Term ID.
* @param string $meta_key Meta key.
* @param mixed $meta_value Meta value.
* @param string $prev_value Previous value. (default: '').
* @return bool
*/
function update_woocommerce_term_meta( $term_id, $meta_key, $meta_value, $prev_value = '' ) {
wc_deprecated_function( 'update_woocommerce_term_meta', '3.6', 'update_term_meta' );
return function_exists( 'update_term_meta' ) ? update_term_meta( $term_id, $meta_key, $meta_value, $prev_value ) : update_metadata( 'woocommerce_term', $term_id, $meta_key, $meta_value, $prev_value );
}
/**
* WooCommerce Term Meta API.
*
* WC tables for storing term meta are deprecated from WordPress 4.4 since 4.4 has its own table.
* This function serves as a wrapper, using the new table if present, or falling back to the WC table.
*
* @deprecated 3.6.0
* @param int $term_id Term ID.
* @param string $meta_key Meta key.
* @param mixed $meta_value Meta value.
* @param bool $unique Make meta key unique. (default: false).
* @return bool
*/
function add_woocommerce_term_meta( $term_id, $meta_key, $meta_value, $unique = false ) {
wc_deprecated_function( 'add_woocommerce_term_meta', '3.6', 'add_term_meta' );
return function_exists( 'add_term_meta' ) ? add_term_meta( $term_id, $meta_key, $meta_value, $unique ) : add_metadata( 'woocommerce_term', $term_id, $meta_key, $meta_value, $unique );
}
/**
* WooCommerce Term Meta API
*
* WC tables for storing term meta are deprecated from WordPress 4.4 since 4.4 has its own table.
* This function serves as a wrapper, using the new table if present, or falling back to the WC table.
*
* @deprecated 3.6.0
* @param int $term_id Term ID.
* @param string $meta_key Meta key.
* @param string $meta_value Meta value (default: '').
* @param bool $deprecated Deprecated param (default: false).
* @return bool
*/
function delete_woocommerce_term_meta( $term_id, $meta_key, $meta_value = '', $deprecated = false ) {
wc_deprecated_function( 'delete_woocommerce_term_meta', '3.6', 'delete_term_meta' );
return function_exists( 'delete_term_meta' ) ? delete_term_meta( $term_id, $meta_key, $meta_value ) : delete_metadata( 'woocommerce_term', $term_id, $meta_key, $meta_value );
}
/**
* WooCommerce Term Meta API
*
* WC tables for storing term meta are deprecated from WordPress 4.4 since 4.4 has its own table.
* This function serves as a wrapper, using the new table if present, or falling back to the WC table.
*
* @deprecated 3.6.0
* @param int $term_id Term ID.
* @param string $key Meta key.
* @param bool $single Whether to return a single value. (default: true).
* @return mixed
*/
function get_woocommerce_term_meta( $term_id, $key, $single = true ) {
wc_deprecated_function( 'get_woocommerce_term_meta', '3.6', 'get_term_meta' );
return function_exists( 'get_term_meta' ) ? get_term_meta( $term_id, $key, $single ) : get_metadata( 'woocommerce_term', $term_id, $key, $single );
}

View File

@ -2239,7 +2239,7 @@ if ( ! function_exists( 'woocommerce_get_loop_display_mode' ) ) {
$display_type = get_option( 'woocommerce_shop_page_display', '' );
} elseif ( is_product_category() ) {
$parent_id = get_queried_object_id();
$display_type = get_woocommerce_term_meta( $parent_id, 'display_type', true );
$display_type = get_term_meta( $parent_id, 'display_type', true );
$display_type = '' === $display_type ? get_option( 'woocommerce_category_archive_display', '' ) : $display_type;
}
@ -2415,7 +2415,8 @@ if ( ! function_exists( 'woocommerce_get_product_subcategories' ) ) {
*/
function woocommerce_get_product_subcategories( $parent_id = 0 ) {
$parent_id = absint( $parent_id );
$product_categories = wp_cache_get( 'product-category-hierarchy-' . $parent_id, 'product_cat' );
$cache_key = apply_filters( 'woocommerce_get_product_subcategories_cache_key', 'product-category-hierarchy-' . $parent_id, $parent_id );
$product_categories = $cache_key ? wp_cache_get( $cache_key, 'product_cat' ) : false;
if ( false === $product_categories ) {
// NOTE: using child_of instead of parent - this is not ideal but due to a WP bug ( https://core.trac.wordpress.org/ticket/15626 ) pad_counts won't work.
@ -2424,7 +2425,6 @@ if ( ! function_exists( 'woocommerce_get_product_subcategories' ) ) {
'woocommerce_product_subcategories_args',
array(
'parent' => $parent_id,
'menu_order' => 'ASC',
'hide_empty' => 0,
'hierarchical' => 1,
'taxonomy' => 'product_cat',
@ -2433,7 +2433,9 @@ if ( ! function_exists( 'woocommerce_get_product_subcategories' ) ) {
)
);
wp_cache_set( 'product-category-hierarchy-' . $parent_id, $product_categories, 'product_cat' );
if ( $cache_key ) {
wp_cache_set( $cache_key, $product_categories, 'product_cat' );
}
}
if ( apply_filters( 'woocommerce_product_subcategories_hide_empty', true ) ) {
@ -2454,7 +2456,7 @@ if ( ! function_exists( 'woocommerce_subcategory_thumbnail' ) ) {
function woocommerce_subcategory_thumbnail( $category ) {
$small_thumbnail_size = apply_filters( 'subcategory_archive_thumbnail_size', 'woocommerce_thumbnail' );
$dimensions = wc_get_image_size( $small_thumbnail_size );
$thumbnail_id = get_woocommerce_term_meta( $category->term_id, 'thumbnail_id', true );
$thumbnail_id = get_term_meta( $category->term_id, 'thumbnail_id', true );
if ( $thumbnail_id ) {
$image = wp_get_attachment_image_src( $thumbnail_id, $small_thumbnail_size );

View File

@ -10,6 +10,99 @@
defined( 'ABSPATH' ) || exit;
/**
* Change get terms defaults for attributes to order by the sorting setting, or default to menu_order for sortable taxonomies.
*
* @since 3.6.0 Sorting options are now set as the default automatically, so you no longer have to request to orderby menu_order.
*
* @param array $defaults An array of default get_terms() arguments.
* @param array $taxonomies An array of taxonomies.
* @return array
*/
function wc_change_get_terms_defaults( $defaults, $taxonomies ) {
if ( is_array( $taxonomies ) && 1 < count( $taxonomies ) ) {
return $defaults;
}
$taxonomy = is_array( $taxonomies ) ? $taxonomies[0] : $taxonomies;
$orderby = 'name';
if ( taxonomy_is_product_attribute( $taxonomy ) ) {
$orderby = wc_attribute_orderby( $taxonomy );
} elseif ( in_array( $taxonomy, apply_filters( 'woocommerce_sortable_taxonomies', array( 'product_cat' ) ), true ) ) {
$orderby = 'menu_order';
}
switch ( $orderby ) {
case 'menu_order':
$defaults['orderby'] = 'meta_value_num';
$defaults['meta_key'] = 'order'; // phpcs:ignore
$defaults['force_menu_order_sort'] = true;
break;
case 'name_num':
$defaults['orderby'] = 'name';
$defaults['force_numeric_name'] = true;
break;
case 'parent':
$defaults['orderby'] = 'parent';
break;
}
return $defaults;
}
add_filter( 'get_terms_defaults', 'wc_change_get_terms_defaults', 10, 2 );
/**
* Adds support to get_terms for menu_order argument.
*
* @since 3.6.0
* @param WP_Term_Query $terms_query Instance of WP_Term_Query.
*/
function wc_change_pre_get_terms( $terms_query ) {
$args = &$terms_query->query_vars;
if ( ! empty( $args['menu_order'] ) ) {
$args['order'] = 'DESC' === strtoupper( $args['menu_order'] ) ? 'DESC' : 'ASC';
$args['orderby'] = 'meta_value_num';
$args['meta_key'] = 'order'; // phpcs:ignore
$args['force_menu_order_sort'] = true;
$terms_query->meta_query->parse_query_vars( $args );
}
}
add_action( 'pre_get_terms', 'wc_change_pre_get_terms', 10, 1 );
/**
* Adjust term query to handle custom sorting parameters.
*
* @param array $clauses Clauses.
* @param array $taxonomies Taxonomies.
* @param array $args Arguments.
* @return array
*/
function wc_terms_clauses( $clauses, $taxonomies, $args ) {
global $wpdb;
// No need to filter when counting.
if ( strpos( 'COUNT(*)', $clauses['fields'] ) !== false ) {
return $clauses;
}
// Force numeric sort if using name_num custom sorting param.
if ( ! empty( $args['force_numeric_name'] ) ) {
$clauses['orderby'] = str_replace( 'ORDER BY t.name', 'ORDER BY t.name+0', $clauses['orderby'] );
}
// For sorting, force left join in case order meta is missing.
if ( ! empty( $args['force_menu_order_sort'] ) ) {
$clauses['join'] = str_replace( "INNER JOIN {$wpdb->termmeta} ON ( t.term_id = {$wpdb->termmeta}.term_id )", "LEFT JOIN {$wpdb->termmeta} ON ( t.term_id = {$wpdb->termmeta}.term_id AND {$wpdb->termmeta}.meta_key='order')", $clauses['join'] );
$clauses['where'] = str_replace( "{$wpdb->termmeta}.meta_key = 'order'", "{$wpdb->termmeta}.meta_key = 'order' OR {$wpdb->termmeta}.meta_key IS NULL", $clauses['where'] );
$clauses['orderby'] = 'DESC' === $args['order'] ? str_replace( 'meta_value+0', 'meta_value+0 DESC, t.name', $clauses['orderby'] ) : str_replace( 'meta_value+0', 'meta_value+0 ASC, t.name', $clauses['orderby'] );
}
return $clauses;
}
add_filter( 'terms_clauses', 'wc_terms_clauses', 99, 3 );
/**
* Helper to get cached object terms and filter by field using wp_list_pluck().
* Works as a cached alternative for wp_get_post_terms() and wp_get_object_terms().
@ -59,10 +152,7 @@ function _wc_get_cached_product_terms( $product_id, $taxonomy, $args = array() )
}
/**
* Wrapper for wp_get_post_terms which supports ordering by parent.
*
* NOTE: At this point in time, ordering by menu_order for example isn't possible with this function. wp_get_post_terms has no.
* filters which we can utilise to modify it's query. https://core.trac.wordpress.org/ticket/19094.
* Wrapper used to get terms for a product.
*
* @param int $product_id Product ID.
* @param string $taxonomy Taxonomy slug.
@ -74,65 +164,7 @@ function wc_get_product_terms( $product_id, $taxonomy, $args = array() ) {
return array();
}
if ( empty( $args['orderby'] ) && taxonomy_is_product_attribute( $taxonomy ) ) {
$args['orderby'] = wc_attribute_orderby( $taxonomy );
}
// Support ordering by parent.
if ( ! empty( $args['orderby'] ) && in_array( $args['orderby'], array( 'name_num', 'parent' ), true ) ) {
$fields = isset( $args['fields'] ) ? $args['fields'] : 'all';
$orderby = $args['orderby'];
// Unset for wp_get_post_terms.
unset( $args['orderby'] );
unset( $args['fields'] );
$terms = _wc_get_cached_product_terms( $product_id, $taxonomy, $args );
switch ( $orderby ) {
case 'name_num':
usort( $terms, '_wc_get_product_terms_name_num_usort_callback' );
break;
case 'parent':
usort( $terms, '_wc_get_product_terms_parent_usort_callback' );
break;
}
switch ( $fields ) {
case 'names':
$terms = wp_list_pluck( $terms, 'name' );
break;
case 'ids':
$terms = wp_list_pluck( $terms, 'term_id' );
break;
case 'slugs':
$terms = wp_list_pluck( $terms, 'slug' );
break;
}
} elseif ( ! empty( $args['orderby'] ) && 'menu_order' === $args['orderby'] ) {
// wp_get_post_terms doesn't let us use custom sort order.
$args['include'] = wc_get_object_terms( $product_id, $taxonomy, 'term_id' );
if ( empty( $args['include'] ) ) {
$terms = array();
} else {
// This isn't needed for get_terms.
unset( $args['orderby'] );
// Set args for get_terms.
$args['menu_order'] = isset( $args['order'] ) ? $args['order'] : 'ASC';
$args['hide_empty'] = isset( $args['hide_empty'] ) ? $args['hide_empty'] : 0;
$args['fields'] = isset( $args['fields'] ) ? $args['fields'] : 'names';
// Ensure slugs is valid for get_terms - slugs isn't supported.
$args['fields'] = ( 'slugs' === $args['fields'] ) ? 'id=>slug' : $args['fields'];
$terms = get_terms( $taxonomy, $args );
}
} else {
$terms = _wc_get_cached_product_terms( $product_id, $taxonomy, $args );
}
return apply_filters( 'woocommerce_get_product_terms', $terms, $product_id, $taxonomy, $args );
return apply_filters( 'woocommerce_get_product_terms', _wc_get_cached_product_terms( $product_id, $taxonomy, $args ), $product_id, $taxonomy, $args );
}
/**
@ -176,7 +208,8 @@ function wc_product_dropdown_categories( $args = array() ) {
global $wp_query;
$args = wp_parse_args(
$args, array(
$args,
array(
'pad_counts' => 1,
'show_count' => 1,
'hierarchical' => 1,
@ -184,7 +217,6 @@ function wc_product_dropdown_categories( $args = array() ) {
'show_uncategorized' => 1,
'orderby' => 'name',
'selected' => isset( $wp_query->query_vars['product_cat'] ) ? $wp_query->query_vars['product_cat'] : '',
'menu_order' => false,
'show_option_none' => __( 'Select a category', 'woocommerce' ),
'option_none_value' => '',
'value_field' => 'slug',
@ -195,8 +227,8 @@ function wc_product_dropdown_categories( $args = array() ) {
);
if ( 'order' === $args['orderby'] ) {
$args['menu_order'] = 'asc';
$args['orderby'] = 'name';
$args['orderby'] = 'meta_value_num';
$args['meta_key'] = 'order'; // phpcs:ignore
}
wp_dropdown_categories( $args );
@ -226,39 +258,6 @@ function wc_walk_category_dropdown_tree() {
return call_user_func_array( array( &$walker, 'walk' ), $args );
}
/**
* When a term is split, ensure meta data maintained.
*
* @param int $old_term_id Old term ID.
* @param int $new_term_id New term ID.
* @param string $term_taxonomy_id Term taxonomy ID.
* @param string $taxonomy Taxonomy.
*/
function wc_taxonomy_metadata_update_content_for_split_terms( $old_term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
global $wpdb;
if ( get_option( 'db_version' ) < 34370 ) {
if ( 'product_cat' === $taxonomy || taxonomy_is_product_attribute( $taxonomy ) ) {
$old_meta_data = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value FROM {$wpdb->prefix}woocommerce_termmeta WHERE woocommerce_term_id = %d;", $old_term_id ) );
// Copy across to split term.
if ( $old_meta_data ) {
foreach ( $old_meta_data as $meta_data ) {
$wpdb->insert(
"{$wpdb->prefix}woocommerce_termmeta",
array(
'woocommerce_term_id' => $new_term_id,
'meta_key' => $meta_data->meta_key, // WPCS: slow query ok.
'meta_value' => $meta_data->meta_value, // WPCS: slow query ok.
)
);
}
}
}
}
}
add_action( 'split_shared_term', 'wc_taxonomy_metadata_update_content_for_split_terms', 10, 4 );
/**
* Migrate data from WC term meta to WP term meta.
*
@ -278,69 +277,6 @@ function wc_taxonomy_metadata_migrate_data( $wp_db_version, $wp_current_db_versi
}
add_action( 'wp_upgrade', 'wc_taxonomy_metadata_migrate_data', 10, 2 );
/**
* WooCommerce Term Meta API.
*
* WC tables for storing term meta are @deprecated from WordPress 4.4 since 4.4 has its own table.
* This function serves as a wrapper, using the new table if present, or falling back to the WC table.
*
* @param int $term_id Term ID.
* @param string $meta_key Meta key.
* @param mixed $meta_value Meta value.
* @param string $prev_value Previous value. (default: '').
* @return bool
*/
function update_woocommerce_term_meta( $term_id, $meta_key, $meta_value, $prev_value = '' ) {
return function_exists( 'update_term_meta' ) ? update_term_meta( $term_id, $meta_key, $meta_value, $prev_value ) : update_metadata( 'woocommerce_term', $term_id, $meta_key, $meta_value, $prev_value );
}
/**
* WooCommerce Term Meta API.
*
* WC tables for storing term meta are @deprecated from WordPress 4.4 since 4.4 has its own table.
* This function serves as a wrapper, using the new table if present, or falling back to the WC table.
*
* @param int $term_id Term ID.
* @param string $meta_key Meta key.
* @param mixed $meta_value Meta value.
* @param bool $unique Make meta key unique. (default: false).
* @return bool
*/
function add_woocommerce_term_meta( $term_id, $meta_key, $meta_value, $unique = false ) {
return function_exists( 'add_term_meta' ) ? add_term_meta( $term_id, $meta_key, $meta_value, $unique ) : add_metadata( 'woocommerce_term', $term_id, $meta_key, $meta_value, $unique );
}
/**
* WooCommerce Term Meta API
*
* WC tables for storing term meta are @deprecated from WordPress 4.4 since 4.4 has its own table.
* This function serves as a wrapper, using the new table if present, or falling back to the WC table.
*
* @param int $term_id Term ID.
* @param string $meta_key Meta key.
* @param string $meta_value Meta value (default: '').
* @param bool $deprecated Deprecated param (default: false).
* @return bool
*/
function delete_woocommerce_term_meta( $term_id, $meta_key, $meta_value = '', $deprecated = false ) {
return function_exists( 'delete_term_meta' ) ? delete_term_meta( $term_id, $meta_key, $meta_value ) : delete_metadata( 'woocommerce_term', $term_id, $meta_key, $meta_value );
}
/**
* WooCommerce Term Meta API
*
* WC tables for storing term meta are @deprecated from WordPress 4.4 since 4.4 has its own table.
* This function serves as a wrapper, using the new table if present, or falling back to the WC table.
*
* @param int $term_id Term ID.
* @param string $key Meta key.
* @param bool $single Whether to return a single value. (default: true).
* @return mixed
*/
function get_woocommerce_term_meta( $term_id, $key, $single = true ) {
return function_exists( 'get_term_meta' ) ? get_term_meta( $term_id, $key, $single ) : get_metadata( 'woocommerce_term', $term_id, $key, $single );
}
/**
* Move a term before the a given element of its hierarchy level.
*
@ -353,7 +289,7 @@ function get_woocommerce_term_meta( $term_id, $key, $single = true ) {
*/
function wc_reorder_terms( $the_term, $next_id, $taxonomy, $index = 0, $terms = null ) {
if ( ! $terms ) {
$terms = get_terms( $taxonomy, 'menu_order=ASC&hide_empty=0&parent=0' );
$terms = get_terms( $taxonomy, 'hide_empty=0&parent=0&menu_order=ASC' );
}
if ( empty( $terms ) ) {
return $index;
@ -386,7 +322,7 @@ function wc_reorder_terms( $the_term, $next_id, $taxonomy, $index = 0, $terms =
do_action( 'woocommerce_after_set_term_order', $term, $index, $taxonomy );
// If that term has children we walk through them.
$children = get_terms( $taxonomy, "parent={$term_id}&menu_order=ASC&hide_empty=0" );
$children = get_terms( $taxonomy, "parent={$term_id}&hide_empty=0&menu_order=ASC" );
if ( ! empty( $children ) ) {
$index = wc_reorder_terms( $the_term, $next_id, $taxonomy, $index, $children );
}
@ -414,20 +350,13 @@ function wc_set_term_order( $term_id, $index, $taxonomy, $recursive = false ) {
$term_id = (int) $term_id;
$index = (int) $index;
// Meta name.
if ( taxonomy_is_product_attribute( $taxonomy ) ) {
$meta_name = 'order_' . esc_attr( $taxonomy );
} else {
$meta_name = 'order';
}
update_woocommerce_term_meta( $term_id, $meta_name, $index );
update_term_meta( $term_id, 'order', $index );
if ( ! $recursive ) {
return $index;
}
$children = get_terms( $taxonomy, "parent=$term_id&menu_order=ASC&hide_empty=0" );
$children = get_terms( $taxonomy, "parent=$term_id&hide_empty=0&menu_order=ASC" );
foreach ( $children as $term ) {
$index++;
@ -439,104 +368,6 @@ function wc_set_term_order( $term_id, $index, $taxonomy, $recursive = false ) {
return $index;
}
/**
* Add term ordering to get_terms.
*
* It enables the support a 'menu_order' parameter to get_terms for the product_cat taxonomy.
* By default it is 'ASC'. It accepts 'DESC' too.
*
* To disable it, set it ot false (or 0).
*
* @param array $clauses Clauses.
* @param array $taxonomies Taxonomies.
* @param array $args Arguments.
* @return array
*/
function wc_terms_clauses( $clauses, $taxonomies, $args ) {
global $wpdb;
// No sorting when menu_order is false.
if ( isset( $args['menu_order'] ) && ( false === $args['menu_order'] || 'false' === $args['menu_order'] ) ) {
return $clauses;
}
// No sorting when orderby is non default.
if ( isset( $args['orderby'] ) && 'name' !== $args['orderby'] ) {
return $clauses;
}
// No sorting in admin when sorting by a column.
if ( is_admin() && isset( $_GET['orderby'] ) ) { // WPCS: input var ok, CSRF ok.
return $clauses;
}
// No need to filter counts.
if ( strpos( 'COUNT(*)', $clauses['fields'] ) !== false ) {
return $clauses;
}
// WordPress should give us the taxonomies asked when calling the get_terms function. Only apply to categories and pa_ attributes.
$found = false;
foreach ( (array) $taxonomies as $taxonomy ) {
if ( taxonomy_is_product_attribute( $taxonomy ) || in_array( $taxonomy, apply_filters( 'woocommerce_sortable_taxonomies', array( 'product_cat' ) ), true ) ) {
// Don't modify the orderby when we're ordering attributes by name.
if ( taxonomy_is_product_attribute( $taxonomy ) && 'name' === wc_attribute_orderby( $taxonomy ) ) {
return $clauses;
}
$found = true;
break;
}
}
if ( ! $found ) {
return $clauses;
}
// Meta name.
if ( ! empty( $taxonomies[0] ) && taxonomy_is_product_attribute( $taxonomies[0] ) ) {
$meta_name = 'order_' . esc_attr( $taxonomies[0] );
} else {
$meta_name = 'order';
}
// Query fields.
$clauses['fields'] = $clauses['fields'] . ', tm.meta_value';
// Query join.
if ( get_option( 'db_version' ) < 34370 ) {
$clauses['join'] .= " LEFT JOIN {$wpdb->woocommerce_termmeta} AS tm ON (t.term_id = tm.woocommerce_term_id AND tm.meta_key = '" . esc_sql( $meta_name ) . "') ";
} else {
$clauses['join'] .= " LEFT JOIN {$wpdb->termmeta} AS tm ON (t.term_id = tm.term_id AND tm.meta_key = '" . esc_sql( $meta_name ) . "') ";
}
// Default to ASC.
if ( ! isset( $args['menu_order'] ) || ! in_array( strtoupper( $args['menu_order'] ), array( 'ASC', 'DESC' ), true ) ) {
$args['menu_order'] = 'ASC';
}
$order = 'ORDER BY tm.meta_value+0 ' . $args['menu_order'];
if ( $clauses['orderby'] ) {
$clauses['orderby'] = str_replace( 'ORDER BY', $order . ',', $clauses['orderby'] );
} else {
$clauses['orderby'] = $order;
}
// Grouping.
if ( strstr( $clauses['fields'], 'tr.object_id' ) ) {
$clauses['orderby'] = ' GROUP BY t.term_id, tr.object_id ' . $clauses['orderby'];
} else {
$clauses['orderby'] = ' GROUP BY t.term_id ' . $clauses['orderby'];
}
return $clauses;
}
add_filter( 'terms_clauses', 'wc_terms_clauses', 99, 3 );
/**
* Function for recounting product terms, ignoring hidden products.
*
@ -632,7 +463,7 @@ function _wc_term_recount( $terms, $taxonomy, $callback = true, $terms_are_term_
$count = $wpdb->get_var( implode( ' ', $term_query ) ); // WPCS: unprepared SQL ok.
// Update the count.
update_woocommerce_term_meta( $term_id, 'product_count_' . $taxonomy->name, absint( $count ) );
update_term_meta( $term_id, 'product_count_' . $taxonomy->name, absint( $count ) );
}
delete_transient( 'wc_term_counts' );
@ -697,7 +528,7 @@ function wc_change_term_counts( $terms, $taxonomies ) {
foreach ( $terms as &$term ) {
if ( is_object( $term ) ) {
$term_counts[ $term->term_id ] = isset( $term_counts[ $term->term_id ] ) ? $term_counts[ $term->term_id ] : get_woocommerce_term_meta( $term->term_id, 'product_count_' . $taxonomies[0], true );
$term_counts[ $term->term_id ] = isset( $term_counts[ $term->term_id ] ) ? $term_counts[ $term->term_id ] : get_term_meta( $term->term_id, 'product_count_' . $taxonomies[0], true );
if ( '' !== $term_counts[ $term->term_id ] ) {
$term->count = absint( $term_counts[ $term->term_id ] );
@ -724,11 +555,11 @@ add_filter( 'get_terms', 'wc_change_term_counts', 10, 2 );
* @return array
*/
function wc_get_term_product_ids( $term_id, $taxonomy ) {
$product_ids = get_woocommerce_term_meta( $term_id, 'product_ids', true );
$product_ids = get_term_meta( $term_id, 'product_ids', true );
if ( false === $product_ids || ! is_array( $product_ids ) ) {
$product_ids = get_objects_in_term( $term_id, $taxonomy );
update_woocommerce_term_meta( $term_id, 'product_ids', $product_ids );
update_term_meta( $term_id, 'product_ids', $product_ids );
}
return $product_ids;
@ -746,10 +577,10 @@ function wc_get_term_product_ids( $term_id, $taxonomy ) {
*/
function wc_clear_term_product_ids( $object_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids ) {
foreach ( $old_tt_ids as $term_id ) {
delete_woocommerce_term_meta( $term_id, 'product_ids' );
delete_term_meta( $term_id, 'product_ids' );
}
foreach ( $tt_ids as $term_id ) {
delete_woocommerce_term_meta( $term_id, 'product_ids' );
delete_term_meta( $term_id, 'product_ids' );
}
}
add_action( 'set_object_terms', 'wc_clear_term_product_ids', 10, 6 );
@ -766,7 +597,8 @@ function wc_get_product_visibility_term_ids() {
return array();
}
return array_map(
'absint', wp_parse_args(
'absint',
wp_parse_args(
wp_list_pluck(
get_terms(
array(

View File

@ -1940,6 +1940,15 @@ function wc_update_360_product_lookup_tables() {
wc_update_product_lookup_tables();
}
/**
* Renames ordering meta to be consistent across taxonomies.
*/
function wc_update_360_term_meta() {
global $wpdb;
$wpdb->query( "UPDATE {$wpdb->termmeta} SET meta_key = 'order' WHERE meta_key LIKE 'order_pa_%';" );
}
/**
* Add new user_order_remaining_expires to speed up user download permission fetching.
*

View File

@ -167,40 +167,12 @@ class WC_Widget_Layered_Nav extends WC_Widget {
return;
}
$get_terms_args = array( 'hide_empty' => '1' );
$orderby = wc_attribute_orderby( $taxonomy );
switch ( $orderby ) {
case 'name':
$get_terms_args['orderby'] = 'name';
$get_terms_args['menu_order'] = false;
break;
case 'id':
$get_terms_args['orderby'] = 'id';
$get_terms_args['order'] = 'ASC';
$get_terms_args['menu_order'] = false;
break;
case 'menu_order':
$get_terms_args['menu_order'] = 'ASC';
break;
}
$terms = get_terms( $taxonomy, $get_terms_args );
$terms = get_terms( $taxonomy, array( 'hide_empty' => '1' ) );
if ( 0 === count( $terms ) ) {
return;
}
switch ( $orderby ) {
case 'name_num':
usort( $terms, '_wc_get_product_terms_name_num_usort_callback' );
break;
case 'parent':
usort( $terms, '_wc_get_product_terms_parent_usort_callback' );
break;
}
ob_start();
$this->widget_start( $args, $instance );

View File

@ -119,9 +119,10 @@ class WC_Widget_Product_Categories extends WC_Widget {
$list_args['depth'] = $max_depth;
if ( 'order' === $orderby ) {
$list_args['menu_order'] = 'asc';
} else {
$list_args['orderby'] = 'title';
$list_args['orderby'] = 'meta_value_num';
$dropdown_args['orderby'] = 'meta_value_num';
$list_args['meta_key'] = 'order';
$dropdown_args['meta_key'] = 'order';
}
$this->current_cat = false;
@ -133,8 +134,11 @@ class WC_Widget_Product_Categories extends WC_Widget {
} elseif ( is_singular( 'product' ) ) {
$terms = wc_get_product_terms(
$post->ID, 'product_cat', apply_filters(
'woocommerce_product_categories_widget_product_terms_args', array(
$post->ID,
'product_cat',
apply_filters(
'woocommerce_product_categories_widget_product_terms_args',
array(
'orderby' => 'parent',
'order' => 'DESC',
)
@ -177,7 +181,8 @@ class WC_Widget_Product_Categories extends WC_Widget {
if ( $this->cat_ancestors ) {
foreach ( $this->cat_ancestors as $ancestor ) {
$include = array_merge(
$include, get_terms(
$include,
get_terms(
'product_cat',
array(
'fields' => 'ids',
@ -222,12 +227,13 @@ class WC_Widget_Product_Categories extends WC_Widget {
if ( $dropdown ) {
wc_product_dropdown_categories(
apply_filters(
'woocommerce_product_categories_widget_dropdown_args', wp_parse_args(
$dropdown_args, array(
'woocommerce_product_categories_widget_dropdown_args',
wp_parse_args(
$dropdown_args,
array(
'show_count' => $count,
'hierarchical' => $hierarchical,
'show_uncategorized' => 0,
'orderby' => $orderby,
'selected' => $this->current_cat ? $this->current_cat->slug : '',
)
)

View File

@ -49,7 +49,10 @@ do_action( 'woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text,
do_action( 'woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email );
?>
<p>
<?php echo wp_kses_post( __( 'Hopefully theyll be back. Read more about <a href="https://docs.woocommerce.com/document/managing-orders/">troubleshooting failed payments</a>.', 'woocommerce' ) ); ?>
<?php
/* translators: %s: documentation link */
echo wp_kses_post( sprintf( __( 'Hopefully theyll be back. Read more about <a href="%s">troubleshooting failed payments</a>.', 'woocommerce' ), 'https://docs.woocommerce.com/document/managing-orders/' ) );
?>
</p>
<?php

View File

@ -47,7 +47,8 @@ do_action( 'woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text,
*/
do_action( 'woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email );
echo esc_html__( 'Hopefully theyll be back. Read more about <a href="https://docs.woocommerce.com/document/managing-orders/">troubleshooting failed payments</a>.', 'woocommerce' ) . "\n\n";
/* translators: %s: documentation link */
printf( esc_html__( 'Hopefully theyll be back. Read more about <a href="%s">troubleshooting failed payments</a>.', 'woocommerce' ), 'https://docs.woocommerce.com/document/managing-orders/' ) . "\n\n";
echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n";

View File

@ -6,6 +6,9 @@
* @since 3.5.0
*/
/**
* WC_Tests_API_Product class.
*/
class WC_Tests_API_Product extends WC_REST_Unit_Test_Case {
/**
@ -87,7 +90,8 @@ class WC_Tests_API_Product extends WC_REST_Unit_Test_Case {
'status' => 'publish',
'sku' => 'DUMMY EXTERNAL SKU',
'regular_price' => 10,
), $product
),
$product
);
}
@ -157,7 +161,7 @@ class WC_Tests_API_Product extends WC_REST_Unit_Test_Case {
public function test_update_product() {
wp_set_current_user( $this->user );
// test simple products
// test simple products.
$product = WC_Helper_Product::create_simple_product();
$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v3/products/' . $product->get_id() ) );
$data = $response->get_data();
@ -196,7 +200,7 @@ class WC_Tests_API_Product extends WC_REST_Unit_Test_Case {
$this->assertContains( 'test upload image', $data['images'][0]['alt'] );
$product->delete( true );
// test variable product (variations are tested in product-variations.php)
// test variable product (variations are tested in product-variations.php).
$product = WC_Helper_Product::create_variation_product();
$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v3/products/' . $product->get_id() ) );
$data = $response->get_data();
@ -235,10 +239,14 @@ class WC_Tests_API_Product extends WC_REST_Unit_Test_Case {
$data = $response->get_data();
$this->assertEquals( array( 'small' ), $data['attributes'][0]['options'] );
$this->assertEquals( array( 'red', 'yellow' ), $data['attributes'][1]['options'] );
foreach ( array( 'red', 'yellow' ) as $term_name ) {
$this->assertContains( $term_name, $data['attributes'][1]['options'] );
}
$product->delete( true );
// test external product
// test external product.
$product = WC_Helper_Product::create_external_product();
$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v3/products/' . $product->get_id() ) );
$data = $response->get_data();
@ -313,7 +321,7 @@ class WC_Tests_API_Product extends WC_REST_Unit_Test_Case {
$data = $response->get_data();
$shipping_class_id = $data['id'];
// Create simple
// Create simple.
$request = new WP_REST_Request( 'POST', '/wc/v3/products' );
$request->set_body_params(
array(
@ -335,7 +343,7 @@ class WC_Tests_API_Product extends WC_REST_Unit_Test_Case {
$this->assertEquals( 'simple', $data['type'] );
$this->assertEquals( $shipping_class_id, $data['shipping_class_id'] );
// Create external
// Create external.
$request = new WP_REST_Request( 'POST', '/wc/v3/products' );
$request->set_body_params(
array(
@ -359,7 +367,7 @@ class WC_Tests_API_Product extends WC_REST_Unit_Test_Case {
$this->assertEquals( 'Test Button', $data['button_text'] );
$this->assertEquals( 'https://wordpress.org', $data['external_url'] );
// Create variable
// Create variable.
$request = new WP_REST_Request( 'POST', '/wc/v3/products' );
$request->set_body_params(
array(
@ -468,7 +476,7 @@ class WC_Tests_API_Product extends WC_REST_Unit_Test_Case {
$this->assertEquals( 3, count( $data ) );
}
/*
/**
* Tests to make sure you can filter products post statuses by both
* the status query arg and WP_Query.
*
@ -488,7 +496,7 @@ class WC_Tests_API_Product extends WC_REST_Unit_Test_Case {
}
}
// Test filtering with status=publish
// Test filtering with status=publish.
$request = new WP_REST_Request( 'GET', '/wc/v3/products' );
$request->set_param( 'status', 'publish' );
$response = $this->server->dispatch( $request );
@ -499,7 +507,7 @@ class WC_Tests_API_Product extends WC_REST_Unit_Test_Case {
$this->assertEquals( 'publish', $product['status'] );
}
// Test filtering with status=draft
// Test filtering with status=draft.
$request = new WP_REST_Request( 'GET', '/wc/v3/products' );
$request->set_param( 'status', 'draft' );
$response = $this->server->dispatch( $request );
@ -510,7 +518,7 @@ class WC_Tests_API_Product extends WC_REST_Unit_Test_Case {
$this->assertEquals( 'draft', $product['status'] );
}
// Test filtering with no filters - which should return 'any' (all 8)
// Test filtering with no filters - which should return 'any' (all 8).
$request = new WP_REST_Request( 'GET', '/wc/v3/products' );
$response = $this->server->dispatch( $request );
$products = $response->get_data();
@ -573,7 +581,7 @@ class WC_Tests_API_Product extends WC_REST_Unit_Test_Case {
$response_product = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertCount( 1, $response_product['categories'] );
$this->assertCount( 1, $response_product['categories'], print_r( $response_product, true ) );
$this->assertEquals( 'uncategorized', $response_product['categories'][0]['slug'] );
}
@ -749,7 +757,8 @@ class WC_Tests_API_Product extends WC_REST_Unit_Test_Case {
// Link the product to the term.
$wpdb->insert(
$wpdb->prefix . 'term_relationships', array(
$wpdb->prefix . 'term_relationships',
array(
'object_id' => $product_2->get_id(),
'term_taxonomy_id' => $term_large->term_id,
'term_order' => 0,

View File

@ -6,6 +6,9 @@
* @since 3.0.0
*/
/**
* Products_API_V2 class.
*/
class Products_API_V2 extends WC_REST_Unit_Test_Case {
/**
@ -87,7 +90,8 @@ class Products_API_V2 extends WC_REST_Unit_Test_Case {
'status' => 'publish',
'sku' => 'DUMMY EXTERNAL SKU',
'regular_price' => 10,
), $product
),
$product
);
}
@ -157,7 +161,7 @@ class Products_API_V2 extends WC_REST_Unit_Test_Case {
public function test_update_product() {
wp_set_current_user( $this->user );
// test simple products
// test simple products.
$product = WC_Helper_Product::create_simple_product();
$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v2/products/' . $product->get_id() ) );
$data = $response->get_data();
@ -193,37 +197,41 @@ class Products_API_V2 extends WC_REST_Unit_Test_Case {
$this->assertContains( 'test upload image', $data['images'][0]['alt'] );
$product->delete( true );
// test variable product (variations are tested in product-variations.php)
// test variable product (variations are tested in product-variations.php).
$product = WC_Helper_Product::create_variation_product();
$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v2/products/' . $product->get_id() ) );
$data = $response->get_data();
$this->assertEquals( array( 'large', 'small' ), $data['attributes'][0]['options'] );
foreach ( array( 'small', 'large' ) as $term_name ) {
$this->assertContains( $term_name, $data['attributes'][0]['options'] );
}
$request = new WP_REST_Request( 'PUT', '/wc/v2/products/' . $product->get_id() );
$request->set_body_params( array(
'attributes' => array(
array(
'id' => 0,
'name' => 'pa_color',
'options' => array(
'red',
'yellow',
$request->set_body_params(
array(
'attributes' => array(
array(
'id' => 0,
'name' => 'pa_color',
'options' => array(
'red',
'yellow',
),
'visible' => false,
'variation' => 1,
),
'visible' => false,
'variation' => 1,
),
array(
'id' => 0,
'name' => 'pa_size',
'options' => array(
'small',
array(
'id' => 0,
'name' => 'pa_size',
'options' => array(
'small',
),
'visible' => false,
'variation' => 1,
),
'visible' => false,
'variation' => 1,
),
),
) );
)
);
$response = $this->server->dispatch( $request );
$data = $response->get_data();
@ -231,7 +239,7 @@ class Products_API_V2 extends WC_REST_Unit_Test_Case {
$this->assertEquals( array( 'red', 'yellow' ), $data['attributes'][1]['options'] );
$product->delete( true );
// test external product
// test external product.
$product = WC_Helper_Product::create_external_product();
$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v2/products/' . $product->get_id() ) );
$data = $response->get_data();
@ -307,7 +315,7 @@ class Products_API_V2 extends WC_REST_Unit_Test_Case {
$data = $response->get_data();
$shipping_class_id = $data['id'];
// Create simple
// Create simple.
$request = new WP_REST_Request( 'POST', '/wc/v2/products' );
$request->set_body_params(
array(
@ -329,7 +337,7 @@ class Products_API_V2 extends WC_REST_Unit_Test_Case {
$this->assertEquals( 'simple', $data['type'] );
$this->assertEquals( $shipping_class_id, $data['shipping_class_id'] );
// Create external
// Create external.
$request = new WP_REST_Request( 'POST', '/wc/v2/products' );
$request->set_body_params(
array(
@ -353,7 +361,7 @@ class Products_API_V2 extends WC_REST_Unit_Test_Case {
$this->assertEquals( 'Test Button', $data['button_text'] );
$this->assertEquals( 'https://wordpress.org', $data['external_url'] );
// Create variable
// Create variable.
$request = new WP_REST_Request( 'POST', '/wc/v2/products' );
$request->set_body_params(
array(
@ -460,7 +468,7 @@ class Products_API_V2 extends WC_REST_Unit_Test_Case {
$this->assertEquals( 3, count( $data ) );
}
/*
/**
* Tests to make sure you can filter products post statuses by both
* the status query arg and WP_Query.
*
@ -480,7 +488,7 @@ class Products_API_V2 extends WC_REST_Unit_Test_Case {
}
}
// Test filtering with status=publish
// Test filtering with status=publish.
$request = new WP_REST_Request( 'GET', '/wc/v2/products' );
$request->set_param( 'status', 'publish' );
$response = $this->server->dispatch( $request );
@ -491,7 +499,7 @@ class Products_API_V2 extends WC_REST_Unit_Test_Case {
$this->assertEquals( 'publish', $product['status'] );
}
// Test filtering with status=draft
// Test filtering with status=draft.
$request = new WP_REST_Request( 'GET', '/wc/v2/products' );
$request->set_param( 'status', 'draft' );
$response = $this->server->dispatch( $request );
@ -502,7 +510,7 @@ class Products_API_V2 extends WC_REST_Unit_Test_Case {
$this->assertEquals( 'draft', $product['status'] );
}
// Test filtering with no filters - which should return 'any' (all 8)
// Test filtering with no filters - which should return 'any' (all 8).
$request = new WP_REST_Request( 'GET', '/wc/v2/products' );
$response = $this->server->dispatch( $request );
$products = $response->get_data();