Merge branch feature/marketplace-suggestions into add/product-edit-suggestions

# Conflicts:
#assets/css/admin-rtl.css
#assets/css/admin.css
#assets/css/marketplace-suggestions-rtl.css
#assets/css/marketplace-suggestions.css
#assets/css/marketplace-suggestions.scss
#assets/js/admin/marketplace-suggestions.js
#includes/admin/class-wc-admin-assets.php
#includes/admin/marketplace-suggestions/class-wc-marketplace-suggestions.php
This commit is contained in:
haszari 2019-02-27 10:56:13 +13:00
commit 0dcc87110b
14 changed files with 205 additions and 86 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5921,26 +5921,6 @@ table.bar_chart {
@include icon( "\e01b" );
}
.post-type-product .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;
}
}
.woocommerce-BlankState {
text-align: center;
padding: 5em 0 0;
@ -5974,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

@ -88,7 +88,9 @@ a.suggestion-dismiss::before {
.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="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 {
@ -105,7 +107,8 @@ a.suggestion-dismiss::before {
// 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="product-edit-meta-tab-footer"],
.marketplace-suggestions-container.showing-suggestion[data-marketplace-suggestions-context="orders-list-empty-footer"] {
.marketplace-suggestion-container {
@ -181,14 +184,17 @@ a.suggestion-dismiss::before {
}
.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-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="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;

View File

@ -53,11 +53,48 @@
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( slug, url, text, isButton ) {
function renderLinkout( context, slug, url, text, isButton ) {
var linkoutButton = document.createElement( 'a' );
linkoutButton.setAttribute( 'href', url );
var utmUrl = addUTMParameters( context, url );
linkoutButton.setAttribute( 'href', utmUrl );
linkoutButton.setAttribute( 'target', 'blank' );
linkoutButton.textContent = text;
@ -115,12 +152,12 @@
}
// Render DOM elements for suggestion call-to-action button or link with dismiss 'x'.
function renderSuggestionCTA( slug, url, linkText, linkIsButton, allowDismiss ) {
function renderSuggestionCTA( context, slug, url, linkText, linkIsButton, allowDismiss ) {
var right = document.createElement( 'div' );
right.classList.add( 'marketplace-suggestion-container-cta' );
if ( url && linkText ) {
var linkoutElement = renderLinkout( slug, url, linkText, linkIsButton );
var linkoutElement = renderLinkout( context, slug, url, linkText, linkIsButton );
right.appendChild( linkoutElement );
}
@ -133,7 +170,7 @@
// Render a "table banner" style suggestion.
// These are used in admin lists, e.g. products list.
function renderTableBanner( slug, iconUrl, title, copy, url, buttonText, allowDismiss ) {
function renderTableBanner( context, slug, iconUrl, title, copy, url, buttonText, allowDismiss ) {
if ( ! title || ! url ) {
return;
}
@ -164,7 +201,7 @@
renderSuggestionContent( slug, title, copy )
);
container.appendChild(
renderSuggestionCTA( slug, url, buttonText, true, allowDismiss )
renderSuggestionCTA( context, slug, url, buttonText, true, allowDismiss )
);
cell.appendChild( container );
@ -175,7 +212,7 @@
// Render a "list item" style suggestion.
// These are used in onboarding style contexts, e.g. products list empty state.
function renderListItem( slug, iconUrl, title, copy, url, linkText, linkIsButton, allowDismiss ) {
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;
@ -188,7 +225,7 @@
renderSuggestionContent( slug, title, copy )
);
container.appendChild(
renderSuggestionCTA( slug, url, linkText, linkIsButton, allowDismiss )
renderSuggestionCTA( context, slug, url, linkText, linkIsButton, allowDismiss )
);
return container;
@ -211,7 +248,7 @@
// hide promos for things the user already has installed
promos = _.filter( promos, function( promo ) {
return ! _.contains( marketplace_suggestions.installed_woo_plugins, promo['hide-if-installed'] );
return ! _.contains( marketplace_suggestions.active_plugins, promo['hide-if-installed'] );
} );
// hide promos that are not applicable based on user's installed extensions
@ -222,7 +259,7 @@
}
// if the user has any of the prerequisites, show the promo
return ( _.intersection( marketplace_suggestions.installed_woo_plugins, promo['show-if-installed'] ).length > 0 );
return ( _.intersection( marketplace_suggestions.active_plugins, promo['show-if-installed'] ).length > 0 );
} );
return promos;
@ -230,18 +267,23 @@
// Show and hide page elements dependent on suggestion state.
function hidePageElementsForSuggestionState( usedSuggestionsContexts ) {
var showingProductsEmptyStateSuggestions = _.contains( usedSuggestionsContexts, 'products-list-empty-body' );
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 ( showingProductsEmptyStateSuggestions ) {
if ( showingEmptyStateSuggestions ) {
$( '#screen-meta-links' ).hide();
$( '#wpfooter' ).hide();
}
// Hide the header & footer, they don't make sense without specific promotion content
if ( ! showingProductsEmptyStateSuggestions ) {
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();
}
var showingProductMetaboxSuggestions = _.contains( usedSuggestionsContexts, 'product-edit-meta-tab-body' );
@ -284,6 +326,7 @@
}
var content = renderListItem(
context,
promos[ i ].slug,
promos[ i ].icon,
promos[ i ].title,
@ -322,6 +365,7 @@
// render first promo
var content = renderTableBanner(
context,
promos[ 0 ].slug,
promos[ 0 ].icon,
promos[ 0 ].title,

View File

@ -424,7 +424,7 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
}
if ( WC_Marketplace_Suggestions::show_suggestions_for_screen( $screen_id ) ) {
$woo_plugin_slugs = WC_Helper::get_local_woo_plugin_slugs();
$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',
@ -437,7 +437,7 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
'marketplace_suggestions',
array(
'dismiss_suggestion_nonce' => wp_create_nonce( 'add_dismissed_marketplace_suggestion' ),
'installed_woo_plugins' => $woo_plugin_slugs,
'active_plugins' => $active_plugin_slugs,
'dismissed_suggestions' => WC_Marketplace_Suggestions::get_dismissed_suggestions(),
'suggestions_data' => WC_Marketplace_Suggestions::get_suggestions_api_data(),
)

View File

@ -88,6 +88,7 @@ class WC_Admin {
// 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

@ -1014,11 +1014,6 @@ class WC_Helper {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$woo_plugins = get_transient( 'wc_helper_installed_woo_plugins' );
if ( false !== $woo_plugins ) {
return $woo_plugins;
}
$plugins = get_plugins();
$woo_plugins = array();
@ -1055,19 +1050,9 @@ class WC_Helper {
$woo_plugins[ $filename ] = $data;
}
set_transient( 'wc_helper_installed_woo_plugins', $woo_plugins, DAY_IN_SECONDS );
return $woo_plugins;
}
/**
* Obtain a list of slugs of locally installed Woo extensions.
* (Cached for 1 day.)
*/
public static function get_local_woo_plugin_slugs() {
$slugs = wp_list_pluck( self::get_local_woo_plugins(), 'slug' );
return array_values( $slugs );
}
/**
* Get locally installed Woo themes.
*/

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

@ -1,9 +1,8 @@
<?php
/**
* REST API Marketplace suggestions API
* Marketplace suggestions
*
* Handles requests for marketplace suggestions data & rendering
* templates for suggestion DOM content.
* Behaviour for displaying in-context suggestions for marketplace extensions.
*
* @package WooCommerce\Classes
* @since 3.6.0
@ -12,7 +11,7 @@
defined( 'ABSPATH' ) || exit;
/**
* REST API Marketplace suggestions API and related logic.
* Marketplace suggestions core behaviour.
*/
class WC_Marketplace_Suggestions {
@ -33,6 +32,7 @@ class WC_Marketplace_Suggestions {
// 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' ) );
}
/**
@ -118,6 +118,15 @@ class WC_Marketplace_Suggestions {
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.
*
@ -179,31 +188,10 @@ class WC_Marketplace_Suggestions {
* @return array of json API data
*/
public static function get_suggestions_api_data() {
$suggestion_data = get_transient( 'wc_marketplace_suggestions' );
if ( false !== $suggestion_data ) {
return $suggestion_data;
}
$suggestion_data_url = 'https://d3t0oesq8995hv.cloudfront.net/add-ons/marketplace-suggestions.json';
$raw_suggestions = wp_remote_get(
$suggestion_data_url,
array( 'user-agent' => 'WooCommerce Marketplace Suggestions' )
);
// Parse the data to check for any errors.
// If it's valid, store structure in transient.
if ( ! is_wp_error( $raw_suggestions ) ) {
$suggestions = json_decode( wp_remote_retrieve_body( $raw_suggestions ) );
if ( $suggestions && is_array( $suggestions ) ) {
set_transient( 'wc_marketplace_suggestions', $suggestions, WEEK_IN_SECONDS );
return $suggestions;
}
}
// Cache empty suggestions data to reduce requests if there are any issues with API.
set_transient( 'wc_marketplace_suggestions', array(), DAY_IN_SECONDS );
return array();
$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://d3t0oesq8995hv.cloudfront.net/add-ons/marketplace-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

@ -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.