* Handle new object for posts and recommendations

* Set breadcrumb parent for woocommerce-coupons to be woocommerce-marketing

* Add main coupon wrapping component

* Render coupon wrapper element below coupon table using wp-admin-scripts

* Finish off implementing category param for data store resolvers

* Create a helper trait for legacy coupons

* Add coupon related titles and descriptions

* Add note for the coupons being moved

* Allow for querying by note name in the notes Data Store

* Revamp coupon moved trait

* Add the new note only if we don't have an unactioned note and perform a redirect to ensure menu updates

* set_icon is deprecated

* Move coupon menu, adding a note for customers

* Translate title and descriptions

* Whitespace

* Account for coupon functionality being disabled

* Hide legacy menu before redirect

* Don’t keep adding the note if customer dismisses it from inbox

* Move behind feature flag

* Add note if feature enabled

* Add filter to override coupon feature

* Tweak option name to refer to wc_admin

To help with finding etc.

* use css variables

* Add the new note only if we don't have an unactioned note

* Switch the filter logic so `false` turns off the feature

This is a bit more intuitive when utilizing the filter

* Remove extraneous string and add trailing new lines

* Use correct posts object in tests

* Revert accidental removal of where_types

* Add coupons category to RecommendedExtensions

* Use 1.1 api to get categorized recommendations

* Add missing text domains

* Fix menu handling to point to woocommerce-marketing

* Only load coupon scripts on the coupon page

* Rework marketing menu logic to register pages more properly

* Use correct wc-admin path for marketing page

* Remove separate feature flag

WC Admin has existing feature flags to load enable/disable the feature

* Only set the coupon parent to marketing when the feature is enabled

* Only load coupon feature if marketing feature is enabled

Co-authored-by: Dan Bitzer <danielbitzer@gmail.com>
Co-authored-by: Jeremy Pry <jeremy.pry@gmail.com>
This commit is contained in:
Jason Conroy 2020-06-16 12:00:41 +09:30 committed by GitHub
parent 77ce9448a3
commit 4cf586c4f1
15 changed files with 569 additions and 40 deletions

View File

@ -107,7 +107,7 @@ describe( 'No posts and loading', () => {
beforeEach( () => {
knowledgeBaseWrapper = shallow(
<KnowledgeBase
posts={ { 'marketing': [] } }
posts={ [] }
isLoading={ true }
category={ 'marketing' }
/>

View File

@ -0,0 +1,42 @@
/**
* External depenencies
*/
import { __ } from '@wordpress/i18n';
/**
* WooCommerce dependencies
*/
import { getSetting } from '@woocommerce/wc-admin-settings';
/**
* Internal dependencies
*/
import './style.scss';
import RecommendedExtensions from '../components/recommended-extensions';
import KnowledgeBase from '../components/knowledge-base';
import '../data';
const CouponsOverview = () => {
const allowMarketplaceSuggestions = getSetting(
'allowMarketplaceSuggestions',
false
);
return (
<div className="woocommerce-marketing-coupons">
{ allowMarketplaceSuggestions && (
<RecommendedExtensions
title={ __( 'Recommended coupon extensions', 'woocommerce-admin' ) }
description={ __( 'Take your coupon marketing to the next level with our recommended coupon extensions.', 'woocommerce-admin' ) }
category="coupons"
/>
) }
<KnowledgeBase
category="coupons"
description={ __( 'Learn the ins and outs of successful coupon marketing from the experts at WooCommerce.', 'woocommerce-admin' ) }
/>
</div>
);
};
export default CouponsOverview;

View File

@ -0,0 +1,30 @@
.woocommerce-marketing-coupons {
padding-top: $gap;
* {
box-sizing: border-box;
}
.woocommerce-card {
border-radius: 0;
}
.woocommerce-card__header {
padding: $gap $gap-large;
border-bottom: 1px solid $core-grey-light-500;
border-radius: 0;
}
.woocommerce-card__title {
font-size: 20px;
line-height: 28px;
font-weight: normal;
}
.woocommerce-card__description {
font-size: 14px;
line-height: 20px;
font-weight: normal;
margin-top: 4px;
}
}

View File

@ -0,0 +1,21 @@
/**
* External dependencies
*/
import { render } from '@wordpress/element';
/**
* Internal dependencies
*/
import CouponsOverview from '../../marketing/coupons';
const postForm = document.getElementById( 'posts-filter' );
if ( postForm ) {
const couponRoot = document.createElement( 'div' );
couponRoot.setAttribute( 'id', 'coupon-root' );
render(
<CouponsOverview />,
postForm.parentNode.appendChild( couponRoot )
);
}

View File

@ -4,6 +4,7 @@
"analytics": true,
"analytics-dashboard": true,
"analytics-dashboard/customizable": true,
"coupons": false,
"devdocs": false,
"marketing": true,
"navigation": false,

View File

@ -4,6 +4,7 @@
"analytics": true,
"analytics-dashboard": true,
"analytics-dashboard/customizable": true,
"coupons": true,
"devdocs": true,
"marketing": true,
"navigation": true,

View File

@ -4,6 +4,7 @@
"analytics": true,
"analytics-dashboard": true,
"analytics-dashboard/customizable": true,
"coupons": true,
"devdocs": false,
"marketing": true,
"navigation": false,

View File

@ -5,6 +5,7 @@
* @package Woocommerce Admin
*/
use Automattic\WooCommerce\Admin\Loader;
use Automattic\WooCommerce\Admin\PageController;
/**
@ -155,6 +156,7 @@ wc_admin_connect_page(
wc_admin_connect_page(
array(
'id' => 'woocommerce-coupons',
'parent' => Loader::is_feature_enabled( 'coupons' ) ? 'woocommerce-marketing' : null,
'screen_id' => 'edit-shop_coupon',
'title' => __( 'Coupons', 'woocommerce-admin' ),
'path' => add_query_arg( 'post_type', 'shop_coupon', $posttype_list_base ),

View File

@ -18,6 +18,7 @@ use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Notes_WooCommerce_Payments;
use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Notes_Install_JP_And_WCS_Plugins;
use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Notes_Draw_Attention;
use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Notes_First_Order;
use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Notes_Coupon_Page_Moved;
use \Automattic\WooCommerce\Admin\RemoteInboxNotifications\RemoteInboxNotificationsEngine;
use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Notes_Home_Screen_Feedback;

View File

@ -0,0 +1,141 @@
<?php
/**
* WooCommerce Marketing > Coupons.
*
* NOTE: DO NOT edit this file in WooCommerce core, this is generated from woocommerce-admin.
*
* @package Woocommerce Admin
*/
namespace Automattic\WooCommerce\Admin\Features;
use Automattic\WooCommerce\Admin\Loader;
use Automattic\WooCommerce\Admin\Notes\WC_Admin_Notes_Coupon_Page_Moved;
use Automattic\WooCommerce\Admin\PageController;
/**
* Contains backend logic for the Coupons feature.
*/
class Coupons {
use CouponsMovedTrait;
/**
* Class instance.
*
* @var Coupons instance
*/
protected static $instance = null;
/**
* Get class instance.
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Hook into WooCommerce.
*/
public function __construct() {
if ( ! is_admin() ) {
return;
}
// If the main marketing feature is disabled, don't modify coupon behavior.
if ( ! Loader::is_feature_enabled( 'marketing' ) ) {
return;
}
// Only support coupon modifications if coupons are enabled.
if ( ! wc_coupons_enabled() ) {
return;
}
( new WC_Admin_Notes_Coupon_Page_Moved() )->init();
add_action( 'admin_enqueue_scripts', array( $this, 'maybe_add_marketing_coupon_script' ) );
add_action( 'woocommerce_register_post_type_shop_coupon', array( $this, 'move_coupons' ) );
add_action( 'admin_head', array( $this, 'fix_coupon_menu_highlight' ), 99 );
add_action( 'admin_menu', array( $this, 'maybe_add_coupon_menu_redirect' ) );
}
/**
* Maybe add menu item back in original spot to help people transition
*/
public function maybe_add_coupon_menu_redirect() {
if ( ! $this->should_display_legacy_menu() ) {
return;
}
add_submenu_page(
'woocommerce',
__( 'Coupons', 'woocommerce-admin' ),
__( 'Coupons', 'woocommerce-admin' ),
'manage_options',
'coupons-moved',
[ $this, 'coupon_menu_moved' ]
);
}
/**
* Call back for transition menu item
*/
public function coupon_menu_moved() {
wp_safe_redirect( $this->get_legacy_coupon_url(), 301 );
exit();
}
/**
* Modify registered post type shop_coupon
*
* @param array $args Array of post type parameters.
*
* @return array the filtered parameters.
*/
public function move_coupons( $args ) {
$args['show_in_menu'] = current_user_can( 'manage_woocommerce' ) ? 'woocommerce-marketing' : true;
return $args;
}
/**
* Undo WC modifications to $parent_file for 'shop_coupon'
*/
public function fix_coupon_menu_highlight() {
global $parent_file, $post_type;
if ( 'shop_coupon' === $post_type ) {
$parent_file = 'woocommerce-marketing'; // phpcs:ignore WordPress.WP.GlobalVariablesOverride
}
}
/**
* Maybe add our wc-admin coupon scripts if viewing coupon pages
*/
public function maybe_add_marketing_coupon_script() {
$curent_screen = PageController::get_instance()->get_current_page();
if ( ! isset( $curent_screen['id'] ) || 'woocommerce-coupons' !== $curent_screen['id'] ) {
return;
}
$rtl = is_rtl() ? '-rtl' : '';
wp_enqueue_style(
'wc-admin-marketing-coupons',
Loader::get_url( "marketing-coupons/style{$rtl}", 'css' ),
array(),
Loader::get_file_version( 'css' )
);
wp_enqueue_script(
'wc-admin-marketing-coupons',
Loader::get_url( 'wp-admin-scripts/marketing-coupons', 'js' ),
array( 'wp-i18n', 'wp-data', 'wp-element', 'moment', 'wp-api-fetch', WC_ADMIN_APP ),
Loader::get_file_version( 'js' ),
true
);
}
}

View File

@ -0,0 +1,106 @@
<?php
/**
* A Trait to help with managing the legacy coupon menu.
*
* @package WooCommerce Admin
*/
namespace Automattic\WooCommerce\Admin\Features;
/**
* CouponsMovedTrait trait.
*
* @package Automattic\WooCommerce\Admin\Features
*/
trait CouponsMovedTrait {
/**
* The GET query key for the legacy menu.
*
* @var string
*/
protected static $query_key = 'legacy_coupon_menu';
/**
* The key for storing an option in the DB.
*
* @var string
*/
protected static $option_key = 'wc_admin_show_legacy_coupon_menu';
/**
* Get the URL for the legacy coupon management.
*
* @return string The unescaped URL for the legacy coupon management page.
*/
protected static function get_legacy_coupon_url() {
return self::get_coupon_url( [ self::$query_key => true ] );
}
/**
* Get the URL for the coupon management page.
*
* @param array $args Additional URL query arguments.
*
* @return string
*/
protected static function get_coupon_url( $args = [] ) {
$args = array_merge(
[
'post_type' => 'shop_coupon',
],
$args
);
return add_query_arg( $args, admin_url( 'edit.php' ) );
}
/**
* Get the new URL for managing coupons.
*
* @param string $page The management page.
*
* @return string
*/
protected static function get_management_url( $page ) {
$path = '';
switch ( $page ) {
case 'coupon':
case 'coupons':
return self::get_coupon_url();
case 'marketing':
$path = self::get_marketing_path();
break;
}
return "wc-admin&path={$path}";
}
/**
* Get the WC Admin path for the marking page.
*
* @return string
*/
protected static function get_marketing_path() {
return '/marketing/overview';
}
/**
* Whether we should display the legacy coupon menu item.
*
* @return bool
*/
protected static function should_display_legacy_menu() {
return (bool) get_option( self::$option_key, 1 );
}
/**
* Set whether we should display the legacy coupon menu item.
*
* @param bool $display Whether the menu should be displayed or not.
*/
protected static function display_legacy_menu( $display = false ) {
update_option( self::$option_key, $display ? 1 : 0 );
}
}

View File

@ -10,11 +10,15 @@ namespace Automattic\WooCommerce\Admin\Features;
use Automattic\WooCommerce\Admin\Marketing\InstalledExtensions;
use Automattic\WooCommerce\Admin\Loader;
use Automattic\WooCommerce\Admin\PageController;
/**
* Contains backend logic for the Marketing feature.
*/
class Marketing {
use CouponsMovedTrait;
/**
* Name of recommended plugins transient.
*
@ -50,14 +54,13 @@ class Marketing {
* Hook into WooCommerce.
*/
public function __construct() {
add_action( 'admin_menu', array( $this, 'add_parent_menu_item' ), 9 );
add_action( 'admin_menu', array( $this, 'register_pages' ) );
add_action( 'admin_head', array( $this, 'modify_menu_structure' ) );
if ( ! is_admin() ) {
return;
}
add_action( 'admin_menu', array( $this, 'register_pages' ), 5 );
add_action( 'admin_menu', array( $this, 'add_parent_menu_item' ), 6 );
add_filter( 'woocommerce_admin_preload_options', array( $this, 'preload_options' ) );
add_filter( 'woocommerce_shared_settings', array( $this, 'component_settings' ), 30 );
}
@ -77,59 +80,76 @@ class Marketing {
'dashicons-megaphone',
58
);
PageController::get_instance()->connect_page(
[
'id' => 'woocommerce-marketing',
'title' => 'Marketing',
'capability' => 'manage_woocommerce',
'path' => 'wc-admin&path=/marketing',
]
);
}
/**
* Registers report pages.
*/
public function register_pages() {
$marketing_pages = array(
array(
'id' => 'woocommerce-marketing-overview',
'title' => __( 'Overview', 'woocommerce-admin' ),
'path' => '/marketing',
),
);
$this->register_overview_page();
$marketing_pages = apply_filters( 'woocommerce_marketing_menu_items', $marketing_pages );
$controller = PageController::get_instance();
$defaults = [
'parent' => 'woocommerce-marketing',
'existing_page' => false,
];
$marketing_pages = apply_filters( 'woocommerce_marketing_menu_items', [] );
foreach ( $marketing_pages as $marketing_page ) {
if ( ! is_null( $marketing_page ) ) {
$marketing_page['parent'] = 'woocommerce-marketing';
wc_admin_register_page( $marketing_page );
if ( ! is_array( $marketing_page ) ) {
continue;
}
$marketing_page = array_merge( $defaults, $marketing_page );
if ( $marketing_page['existing_page'] ) {
$controller->connect_page( $marketing_page );
} else {
$controller->register_page( $marketing_page );
}
}
}
/**
* Modify the Marketing menu structure
* Register the main Marketing page, which is Marketing > Overview.
*
* This is done separately because we need to ensure the page is registered properly and
* that the link is done properly. For some reason the normal page registration process
* gives us the wrong menu link.
*/
public function modify_menu_structure() {
protected function register_overview_page() {
global $submenu;
$marketing_submenu_key = 'woocommerce-marketing';
$overview_key = null;
// First register the page.
PageController::get_instance()->register_page(
[
'id' => 'woocommerce-marketing-overview',
'title' => __( 'Overview', 'woocommerce-admin' ),
'path' => 'wc-admin&path=/marketing',
'parent' => 'woocommerce-marketing',
]
);
// User does not have capabilites to see the submenu.
if ( ! current_user_can( 'manage_woocommerce' ) || empty( $submenu[ $marketing_submenu_key ] ) ) {
// Now fix the path, since register_page() gets it wrong.
if ( ! isset( $submenu['woocommerce-marketing'] ) ) {
return;
}
foreach ( $submenu[ $marketing_submenu_key ] as $submenu_key => $submenu_item ) {
if ( 'wc-admin&path=/marketing' === $submenu_item[2] ) {
$overview_key = $submenu_key;
foreach ( $submenu['woocommerce-marketing'] as &$item ) {
// The "slug" (aka the path) is the third item in the array.
if ( 0 === strpos( $item[2], 'wc-admin' ) ) {
$item[2] = 'admin.php?page=' . $item[2];
}
}
// Remove PHP powered top level page.
unset( $submenu[ $marketing_submenu_key ][0] );
// Move overview menu item to top.
if ( null !== $overview_key ) {
$menu = $submenu[ $marketing_submenu_key ][ $overview_key ];
unset( $submenu[ $marketing_submenu_key ][ $overview_key ] );
array_unshift( $submenu[ $marketing_submenu_key ], $menu );
}
}
/**
@ -170,7 +190,7 @@ class Marketing {
$plugins = get_transient( self::RECOMMENDED_PLUGINS_TRANSIENT );
if ( false === $plugins ) {
$request = wp_remote_get( 'https://woocommerce.com/wp-json/wccom/marketing-tab/1.0/recommendations.json' );
$request = wp_remote_get( 'https://woocommerce.com/wp-json/wccom/marketing-tab/1.1/recommendations.json' );
$plugins = [];
if ( ! is_wp_error( $request ) && 200 === $request['response']['code'] ) {
@ -267,5 +287,4 @@ class Marketing {
return $posts;
}
}

View File

@ -405,16 +405,30 @@ class DataStore extends \WC_Data_Store_WP implements \WC_Object_Data_Store_Inter
if ( isset( $args['is_deleted'] ) ) {
$escaped_is_deleted = esc_sql( $args['is_deleted'] );
}
$where_name_array = [];
if ( isset( $args['name'] ) ) {
foreach ( $args['name'] as $args_name ) {
$args_name = trim( $args_name );
$where_name_array[] = sprintf( "'%s'", esc_sql( $args_name ) );
}
}
$escaped_where_types = implode( ',', $where_type_array );
$escaped_status_types = implode( ',', $where_status_array );
$escaped_where_status = implode( ',', $where_status_array );
$escaped_where_names = implode( ',', $where_name_array );
$where_clauses = '';
if ( ! empty( $escaped_where_types ) ) {
$where_clauses .= " AND type IN ($escaped_where_types)";
}
if ( ! empty( $escaped_status_types ) ) {
$where_clauses .= " AND status IN ($escaped_status_types)";
if ( ! empty( $escaped_where_status ) ) {
$where_clauses .= " AND status IN ($escaped_where_status)";
}
if ( ! empty( $escaped_where_names ) ) {
$where_clauses .= " AND name IN ($escaped_where_names)";
}
$where_clauses .= $escaped_is_deleted ? ' AND is_deleted = 1' : ' AND is_deleted = 0';

View File

@ -0,0 +1,149 @@
<?php
/**
* WooCommerce Admin Coupon Page Moved provider.
*
* Adds a notice when the store manager access the coupons page via the old WooCommere > Coupons menu.
*
* @package WooCommerce Admin
*/
namespace Automattic\WooCommerce\Admin\Notes;
use Automattic\WooCommerce\Admin\Features\CouponsMovedTrait;
use stdClass;
use WC_Data_Store;
defined( 'ABSPATH' ) || exit;
/**
* WC_Admin_Notes_Coupon_Page_Moved class.
*
* @package Automattic\WooCommerce\Admin\Notes
*/
class WC_Admin_Notes_Coupon_Page_Moved {
use NoteTraits, CouponsMovedTrait;
const NOTE_NAME = 'wc-admin-coupon-page-moved';
/**
* Initialize our hooks.
*/
public function init() {
if ( ! wc_coupons_enabled() ) {
return;
}
add_action( 'admin_init', [ $this, 'possibly_add_note' ] );
add_action( 'admin_init', [ $this, 'redirect_to_coupons' ] );
}
/**
* Checks if a note can and should be added.
*
* @return bool
*/
public static function can_be_added() {
if ( ! wc_coupons_enabled() ) {
return false;
}
// Don't add the notice if it's been hidden by the user before.
if ( self::has_dismissed_note() ) {
return false;
}
// If we already have a notice, don't add a new one.
if ( self::has_unactioned_note() ) {
return false;
}
return isset( $_GET[ self::$query_key ] ) && (bool) $_GET[ self::$query_key ]; // phpcs:ignore WordPress.Security.NonceVerification
}
/**
* Get the note object for this class.
*
* @return WC_Admin_Note
*/
public static function get_note() {
$note = new WC_Admin_Note();
$note->set_title( __( 'Coupon management has moved!', 'woocommerce-admin' ) );
$note->set_content( __( 'Coupons can now be managed from Marketing > Coupons. Click the button below to remove the legacy WooCommerce > Coupons menu item.', 'woocommerce-admin' ) );
$note->set_type( WC_Admin_Note::E_WC_ADMIN_NOTE_UPDATE );
$note->set_name( self::NOTE_NAME );
$note->set_content_data( new stdClass() );
$note->set_source( 'woocommerce-admin' );
$note->add_action(
'remove-legacy-coupon-menu',
__( 'Remove legacy coupon menu', 'woocommerce-admin' ),
wc_admin_url( '&action=remove-coupon-menu' ),
WC_Admin_Note::E_WC_ADMIN_NOTE_ACTIONED,
true
);
return $note;
}
/**
* Find notes that have not been actioned.
*
* @return bool
*/
protected static function has_unactioned_note() {
$notes = self::get_data_store()->get_notes(
[
'name' => [ self::NOTE_NAME ],
'status' => [ 'unactioned' ],
'is_deleted' => false,
]
);
return ! empty( $notes );
}
/**
* Whether any notes have been dismissed by the user previously.
*
* @return bool
*/
protected static function has_dismissed_note() {
$notes = self::get_data_store()->get_notes(
[
'name' => [ self::NOTE_NAME ],
'is_deleted' => true,
]
);
return ! empty( $notes );
}
/**
* Get the data store object.
*
* @return DataStore The data store object.
*/
protected static function get_data_store() {
return WC_Data_Store::load( 'admin-note' );
}
/**
* Safe redirect to the coupon page to force page refresh.
*/
public function redirect_to_coupons() {
/* phpcs:disable WordPress.Security.NonceVerification */
if (
! isset( $_GET['page'] ) ||
'wc-admin' !== $_GET['page'] ||
! isset( $_GET['action'] ) ||
'remove-coupon-menu' !== $_GET['action'] ||
! defined( 'WC_ADMIN_PLUGIN_FILE' )
) {
return;
}
/* phpcs:enable */
$this->display_legacy_menu( false );
wp_safe_redirect( self::get_management_url( 'coupons' ) );
exit;
}
}

View File

@ -62,6 +62,7 @@ wcAdminPackages.forEach( ( name ) => {
} );
const wpAdminScripts = [
'marketing-coupons',
'onboarding-homepage-notice',
'onboarding-product-notice',
'onboarding-product-import-notice',