Merge pull request #22857 from woocommerce/feature/marketplace-suggestions
Feature/marketplace suggestions
This commit is contained in:
commit
5492369fe9
|
@ -1,7 +1,7 @@
|
||||||
/* jshint node:true */
|
/* jshint node:true */
|
||||||
module.exports = function( grunt ) {
|
module.exports = function( grunt ) {
|
||||||
'use strict';
|
'use strict';
|
||||||
const sass = require( 'node-sass' );
|
var sass = require( 'node-sass' );
|
||||||
|
|
||||||
grunt.initConfig({
|
grunt.initConfig({
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -4352,6 +4352,10 @@ img.help_tip {
|
||||||
content: "\f111";
|
content: "\f111";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.marketplace-suggestions_options a::before {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
&.variations_options a::before {
|
&.variations_options a::before {
|
||||||
content: "\f509";
|
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
|
* 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
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
@ -343,4 +343,5 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
})( jQuery, woocommerce_admin );
|
})( jQuery, woocommerce_admin );
|
||||||
|
|
|
@ -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( '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_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_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.
|
// Add RTL support for admin styles.
|
||||||
wp_style_add_data( 'woocommerce_admin_menu_styles', 'rtl', 'replace' );
|
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_styles', 'rtl', 'replace' );
|
||||||
wp_style_add_data( 'woocommerce_admin_dashboard_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_print_reports_styles', 'rtl', 'replace' );
|
||||||
|
wp_style_add_data( 'woocommerce_admin_marketplace_styles', 'rtl', 'replace' );
|
||||||
|
|
||||||
// Sitewide menu CSS.
|
// Sitewide menu CSS.
|
||||||
wp_enqueue_style( 'woocommerce_admin_menu_styles' );
|
wp_enqueue_style( 'woocommerce_admin_menu_styles' );
|
||||||
|
@ -70,6 +72,10 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
|
||||||
do_action( 'woocommerce_admin_css' );
|
do_action( 'woocommerce_admin_css' );
|
||||||
wc_deprecated_function( 'The woocommerce_admin_css action', '2.3', 'admin_enqueue_scripts' );
|
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( '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( '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( '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(
|
wp_localize_script(
|
||||||
'wc-enhanced-select',
|
'wc-enhanced-select',
|
||||||
'wc_enhanced_select_params',
|
'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;
|
endif;
|
||||||
|
|
|
@ -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-plugin-info.php';
|
||||||
include_once dirname( __FILE__ ) . '/helper/class-wc-helper-compat.php';
|
include_once dirname( __FILE__ ) . '/helper/class-wc-helper-compat.php';
|
||||||
include_once dirname( __FILE__ ) . '/helper/class-wc-helper.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';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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() {
|
public static function get_local_woo_plugins() {
|
||||||
if ( ! function_exists( 'get_plugins' ) ) {
|
if ( ! function_exists( 'get_plugins' ) ) {
|
||||||
|
@ -1046,6 +1046,7 @@ class WC_Helper {
|
||||||
$data['_product_id'] = absint( $product_id );
|
$data['_product_id'] = absint( $product_id );
|
||||||
$data['_file_id'] = $file_id;
|
$data['_file_id'] = $file_id;
|
||||||
$data['_type'] = 'plugin';
|
$data['_type'] = 'plugin';
|
||||||
|
$data['slug'] = dirname( $filename );
|
||||||
$woo_plugins[ $filename ] = $data;
|
$woo_plugins[ $filename ] = $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,9 +47,16 @@ class WC_Admin_List_Table_Orders extends WC_Admin_List_Table {
|
||||||
*/
|
*/
|
||||||
protected function render_blank_state() {
|
protected function render_blank_state() {
|
||||||
echo '<div class="woocommerce-BlankState">';
|
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 '<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 '<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>';
|
echo '</div>';
|
||||||
|
|
||||||
|
do_action( 'wc_marketplace_suggestions_orders_empty_state' );
|
||||||
|
|
||||||
|
echo '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -47,9 +47,18 @@ class WC_Admin_List_Table_Products extends WC_Admin_List_Table {
|
||||||
*/
|
*/
|
||||||
protected function render_blank_state() {
|
protected function render_blank_state() {
|
||||||
echo '<div class="woocommerce-BlankState">';
|
echo '<div class="woocommerce-BlankState">';
|
||||||
|
|
||||||
echo '<h2 class="woocommerce-BlankState-message">' . esc_html__( 'Ready to start selling something awesome?', 'woocommerce' ) . '</h2>';
|
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>';
|
echo '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
|
@ -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>
|
|
@ -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>
|
|
@ -372,6 +372,7 @@ final class WooCommerce {
|
||||||
include_once WC_ABSPATH . 'includes/class-wc-logger.php';
|
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-action-queue.php';
|
||||||
include_once WC_ABSPATH . 'includes/queue/class-wc-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.
|
* Data stores - used to store and retrieve CRUD object data from the database.
|
||||||
|
|
Loading…
Reference in New Issue