initial commit
This commit is contained in:
68
packages/woocommerce-admin/src/Features/ActivityPanels.php
Normal file
68
packages/woocommerce-admin/src/Features/ActivityPanels.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Activity Panel.
|
||||
* NOTE: DO NOT edit this file in WooCommerce core, this is generated from woocommerce-admin.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Notes\Notes;
|
||||
|
||||
/**
|
||||
* Contains backend logic for the activity panel feature.
|
||||
*/
|
||||
class ActivityPanels {
|
||||
/**
|
||||
* Class instance.
|
||||
*
|
||||
* @var ActivityPanels 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() {
|
||||
add_filter( 'woocommerce_admin_get_user_data_fields', array( $this, 'add_user_data_fields' ) );
|
||||
// Run after Automattic\WooCommerce\Admin\Loader.
|
||||
add_filter( 'woocommerce_components_settings', array( $this, 'component_settings' ), 20 );
|
||||
// New settings injection.
|
||||
add_filter( 'woocommerce_admin_shared_settings', array( $this, 'component_settings' ), 20 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds fields so that we can store activity panel last read and open times.
|
||||
*
|
||||
* @param array $user_data_fields User data fields.
|
||||
* @return array
|
||||
*/
|
||||
public function add_user_data_fields( $user_data_fields ) {
|
||||
return array_merge(
|
||||
$user_data_fields,
|
||||
array(
|
||||
'activity_panel_inbox_last_read',
|
||||
'activity_panel_reviews_last_read',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add alert count to the component settings.
|
||||
*
|
||||
* @param array $settings Component settings.
|
||||
*/
|
||||
public function component_settings( $settings ) {
|
||||
$settings['alertCount'] = Notes::get_notes_count( array( 'error', 'update' ), array( 'unactioned' ) );
|
||||
return $settings;
|
||||
}
|
||||
}
|
325
packages/woocommerce-admin/src/Features/Analytics.php
Normal file
325
packages/woocommerce-admin/src/Features/Analytics.php
Normal file
@ -0,0 +1,325 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Analytics.
|
||||
* NOTE: DO NOT edit this file in WooCommerce core, this is generated from woocommerce-admin.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Loader;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Cache;
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
|
||||
/**
|
||||
* Contains backend logic for the Analytics feature.
|
||||
*/
|
||||
class Analytics {
|
||||
/**
|
||||
* Option name used to toggle this feature.
|
||||
*/
|
||||
const TOGGLE_OPTION_NAME = 'woocommerce_analytics_enabled';
|
||||
/**
|
||||
* Clear cache tool identifier.
|
||||
*/
|
||||
const CACHE_TOOL_ID = 'clear_woocommerce_analytics_cache';
|
||||
|
||||
/**
|
||||
* Class instance.
|
||||
*
|
||||
* @var Analytics 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() {
|
||||
add_filter( 'woocommerce_settings_features', array( $this, 'add_feature_toggle' ) );
|
||||
add_action( 'update_option_' . self::TOGGLE_OPTION_NAME, array( $this, 'reload_page_on_toggle' ), 10, 2 );
|
||||
|
||||
if ( ! Features::is_enabled( 'analytics' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_filter( 'woocommerce_component_settings_preload_endpoints', array( $this, 'add_preload_endpoints' ) );
|
||||
add_filter( 'woocommerce_admin_get_user_data_fields', array( $this, 'add_user_data_fields' ) );
|
||||
add_action( 'admin_menu', array( $this, 'register_pages' ) );
|
||||
add_filter( 'woocommerce_debug_tools', array( $this, 'register_cache_clear_tool' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the feature toggle to the features settings.
|
||||
*
|
||||
* @param array $features Feature sections.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_feature_toggle( $features ) {
|
||||
$description = __(
|
||||
'Enables WooCommerce Analytics',
|
||||
'woocommerce'
|
||||
);
|
||||
|
||||
$features[] = array(
|
||||
'title' => __( 'Analytics', 'woocommerce' ),
|
||||
'desc' => $description,
|
||||
'id' => self::TOGGLE_OPTION_NAME,
|
||||
'type' => 'checkbox',
|
||||
'default' => 'yes',
|
||||
'class' => '',
|
||||
);
|
||||
|
||||
return $features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the page when the option is toggled to make sure all Analytics features are loaded.
|
||||
*
|
||||
* @param string $old_value Old value.
|
||||
* @param string $value New value.
|
||||
*/
|
||||
public static function reload_page_on_toggle( $old_value, $value ) {
|
||||
if ( $old_value === $value ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isset( $_SERVER['REQUEST_URI'] ) ) {
|
||||
wp_safe_redirect( wp_unslash( $_SERVER['REQUEST_URI'] ) );
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload data from the countries endpoint.
|
||||
*
|
||||
* @param array $endpoints Array of preloaded endpoints.
|
||||
* @return array
|
||||
*/
|
||||
public function add_preload_endpoints( $endpoints ) {
|
||||
$endpoints['countries'] = '/wc-analytics/data/countries';
|
||||
$endpoints['performanceIndicators'] = '/wc-analytics/reports/performance-indicators/allowed';
|
||||
$endpoints['leaderboards'] = '/wc-analytics/leaderboards/allowed';
|
||||
return $endpoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds fields so that we can store user preferences for the columns to display on a report.
|
||||
*
|
||||
* @param array $user_data_fields User data fields.
|
||||
* @return array
|
||||
*/
|
||||
public function add_user_data_fields( $user_data_fields ) {
|
||||
return array_merge(
|
||||
$user_data_fields,
|
||||
array(
|
||||
'categories_report_columns',
|
||||
'coupons_report_columns',
|
||||
'customers_report_columns',
|
||||
'orders_report_columns',
|
||||
'products_report_columns',
|
||||
'revenue_report_columns',
|
||||
'taxes_report_columns',
|
||||
'variations_report_columns',
|
||||
'dashboard_sections',
|
||||
'dashboard_chart_type',
|
||||
'dashboard_chart_interval',
|
||||
'dashboard_leaderboard_rows',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the cache clearing tool on the WooCommerce > Status > Tools page.
|
||||
*
|
||||
* @param array $debug_tools Available debug tool registrations.
|
||||
* @return array Filtered debug tool registrations.
|
||||
*/
|
||||
public function register_cache_clear_tool( $debug_tools ) {
|
||||
$settings_url = add_query_arg(
|
||||
array(
|
||||
'page' => 'wc-admin',
|
||||
'path' => '/analytics/settings',
|
||||
),
|
||||
get_admin_url( null, 'admin.php' )
|
||||
);
|
||||
|
||||
$debug_tools[ self::CACHE_TOOL_ID ] = array(
|
||||
'name' => __( 'Clear analytics cache', 'woocommerce' ),
|
||||
'button' => __( 'Clear', 'woocommerce' ),
|
||||
'desc' => sprintf(
|
||||
/* translators: 1: opening link tag, 2: closing tag */
|
||||
__( 'This tool will reset the cached values used in WooCommerce Analytics. If numbers still look off, try %1$sReimporting Historical Data%2$s.', 'woocommerce' ),
|
||||
'<a href="' . esc_url( $settings_url ) . '">',
|
||||
'</a>'
|
||||
),
|
||||
'callback' => array( $this, 'run_clear_cache_tool' ),
|
||||
);
|
||||
|
||||
return $debug_tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers report pages.
|
||||
*/
|
||||
public function register_pages() {
|
||||
$report_pages = self::get_report_pages();
|
||||
foreach ( $report_pages as $report_page ) {
|
||||
if ( ! is_null( $report_page ) ) {
|
||||
wc_admin_register_page( $report_page );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get report pages.
|
||||
*/
|
||||
public static function get_report_pages() {
|
||||
$overview_page = array(
|
||||
'id' => 'woocommerce-analytics',
|
||||
'title' => __( 'Analytics', 'woocommerce' ),
|
||||
'path' => '/analytics/overview',
|
||||
'icon' => 'dashicons-chart-bar',
|
||||
'position' => 56, // After WooCommerce & Product menu items.
|
||||
);
|
||||
|
||||
$report_pages = array(
|
||||
$overview_page,
|
||||
array(
|
||||
'id' => 'woocommerce-analytics-overview',
|
||||
'title' => __( 'Overview', 'woocommerce' ),
|
||||
'parent' => 'woocommerce-analytics',
|
||||
'path' => '/analytics/overview',
|
||||
'nav_args' => array(
|
||||
'order' => 10,
|
||||
'parent' => 'woocommerce-analytics',
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'woocommerce-analytics-products',
|
||||
'title' => __( 'Products', 'woocommerce' ),
|
||||
'parent' => 'woocommerce-analytics',
|
||||
'path' => '/analytics/products',
|
||||
'nav_args' => array(
|
||||
'order' => 20,
|
||||
'parent' => 'woocommerce-analytics',
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'woocommerce-analytics-revenue',
|
||||
'title' => __( 'Revenue', 'woocommerce' ),
|
||||
'parent' => 'woocommerce-analytics',
|
||||
'path' => '/analytics/revenue',
|
||||
'nav_args' => array(
|
||||
'order' => 30,
|
||||
'parent' => 'woocommerce-analytics',
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'woocommerce-analytics-orders',
|
||||
'title' => __( 'Orders', 'woocommerce' ),
|
||||
'parent' => 'woocommerce-analytics',
|
||||
'path' => '/analytics/orders',
|
||||
'nav_args' => array(
|
||||
'order' => 40,
|
||||
'parent' => 'woocommerce-analytics',
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'woocommerce-analytics-variations',
|
||||
'title' => __( 'Variations', 'woocommerce' ),
|
||||
'parent' => 'woocommerce-analytics',
|
||||
'path' => '/analytics/variations',
|
||||
'nav_args' => array(
|
||||
'order' => 50,
|
||||
'parent' => 'woocommerce-analytics',
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'woocommerce-analytics-categories',
|
||||
'title' => __( 'Categories', 'woocommerce' ),
|
||||
'parent' => 'woocommerce-analytics',
|
||||
'path' => '/analytics/categories',
|
||||
'nav_args' => array(
|
||||
'order' => 60,
|
||||
'parent' => 'woocommerce-analytics',
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'woocommerce-analytics-coupons',
|
||||
'title' => __( 'Coupons', 'woocommerce' ),
|
||||
'parent' => 'woocommerce-analytics',
|
||||
'path' => '/analytics/coupons',
|
||||
'nav_args' => array(
|
||||
'order' => 70,
|
||||
'parent' => 'woocommerce-analytics',
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'woocommerce-analytics-taxes',
|
||||
'title' => __( 'Taxes', 'woocommerce' ),
|
||||
'parent' => 'woocommerce-analytics',
|
||||
'path' => '/analytics/taxes',
|
||||
'nav_args' => array(
|
||||
'order' => 80,
|
||||
'parent' => 'woocommerce-analytics',
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'woocommerce-analytics-downloads',
|
||||
'title' => __( 'Downloads', 'woocommerce' ),
|
||||
'parent' => 'woocommerce-analytics',
|
||||
'path' => '/analytics/downloads',
|
||||
'nav_args' => array(
|
||||
'order' => 90,
|
||||
'parent' => 'woocommerce-analytics',
|
||||
),
|
||||
),
|
||||
'yes' === get_option( 'woocommerce_manage_stock' ) ? array(
|
||||
'id' => 'woocommerce-analytics-stock',
|
||||
'title' => __( 'Stock', 'woocommerce' ),
|
||||
'parent' => 'woocommerce-analytics',
|
||||
'path' => '/analytics/stock',
|
||||
'nav_args' => array(
|
||||
'order' => 100,
|
||||
'parent' => 'woocommerce-analytics',
|
||||
),
|
||||
) : null,
|
||||
array(
|
||||
'id' => 'woocommerce-analytics-customers',
|
||||
'title' => __( 'Customers', 'woocommerce' ),
|
||||
'parent' => 'woocommerce',
|
||||
'path' => '/customers',
|
||||
),
|
||||
array(
|
||||
'id' => 'woocommerce-analytics-settings',
|
||||
'title' => __( 'Settings', 'woocommerce' ),
|
||||
'parent' => 'woocommerce-analytics',
|
||||
'path' => '/analytics/settings',
|
||||
'nav_args' => array(
|
||||
'title' => __( 'Analytics', 'woocommerce' ),
|
||||
'parent' => 'woocommerce-settings',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return apply_filters( 'woocommerce_analytics_report_menu_items', $report_pages );
|
||||
}
|
||||
|
||||
/**
|
||||
* "Clear" analytics cache by invalidating it.
|
||||
*/
|
||||
public function run_clear_cache_tool() {
|
||||
Cache::invalidate();
|
||||
|
||||
return __( 'Analytics cache cleared.', 'woocommerce' );
|
||||
}
|
||||
}
|
143
packages/woocommerce-admin/src/Features/Coupons.php
Normal file
143
packages/woocommerce-admin/src/Features/Coupons.php
Normal file
@ -0,0 +1,143 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Marketing > Coupons.
|
||||
*
|
||||
* NOTE: DO NOT edit this file in WooCommerce core, this is generated from woocommerce-admin.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Admin\Loader;
|
||||
use Automattic\WooCommerce\Admin\Notes\CouponPageMoved;
|
||||
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 ( ! Features::is_enabled( 'marketing' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only support coupon modifications if coupons are enabled.
|
||||
if ( ! wc_coupons_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
( new CouponPageMoved() )->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' ),
|
||||
__( 'Coupons', 'woocommerce' ),
|
||||
'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' )
|
||||
);
|
||||
|
||||
$script_assets_filename = Loader::get_script_asset_filename( 'wp-admin-scripts', 'marketing-coupons' );
|
||||
$script_assets = require WC_ADMIN_ABSPATH . WC_ADMIN_DIST_JS_FOLDER . 'wp-admin-scripts/' . $script_assets_filename;
|
||||
|
||||
wp_enqueue_script(
|
||||
'wc-admin-marketing-coupons',
|
||||
Loader::get_url( 'wp-admin-scripts/marketing-coupons', 'js' ),
|
||||
array_merge( array( WC_ADMIN_APP ), $script_assets ['dependencies'] ),
|
||||
Loader::get_file_version( 'js' ),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
104
packages/woocommerce-admin/src/Features/CouponsMovedTrait.php
Normal file
104
packages/woocommerce-admin/src/Features/CouponsMovedTrait.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/**
|
||||
* A Trait to help with managing the legacy coupon menu.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
|
||||
/**
|
||||
* CouponsMovedTrait trait.
|
||||
*/
|
||||
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 ( get_option( self::$option_key, 1 ) && ! Features::is_enabled( 'navigation' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
}
|
||||
}
|
@ -0,0 +1,433 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Customer effort score tracks
|
||||
*
|
||||
* @package WooCommerce\Admin\Features
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Triggers customer effort score on several different actions.
|
||||
*/
|
||||
class CustomerEffortScoreTracks {
|
||||
/**
|
||||
* Option name for the CES Tracks queue.
|
||||
*/
|
||||
const CES_TRACKS_QUEUE_OPTION_NAME = 'woocommerce_ces_tracks_queue';
|
||||
|
||||
/**
|
||||
* Option name for the clear CES Tracks queue for page.
|
||||
*/
|
||||
const CLEAR_CES_TRACKS_QUEUE_FOR_PAGE_OPTION_NAME =
|
||||
'woocommerce_clear_ces_tracks_queue_for_page';
|
||||
|
||||
/**
|
||||
* Option name for the set of actions that have been shown.
|
||||
*/
|
||||
const SHOWN_FOR_ACTIONS_OPTION_NAME = 'woocommerce_ces_shown_for_actions';
|
||||
|
||||
/**
|
||||
* Action name for settings change.
|
||||
*/
|
||||
const SETTINGS_CHANGE_ACTION_NAME = 'settings_change';
|
||||
|
||||
/**
|
||||
* Action name for add product categories.
|
||||
*/
|
||||
const ADD_PRODUCT_CATEGORIES_ACTION_NAME = 'add_product_categories';
|
||||
|
||||
/**
|
||||
* Action name for add product tags.
|
||||
*/
|
||||
const ADD_PRODUCT_TAGS_ACTION_NAME = 'add_product_tags';
|
||||
|
||||
/*
|
||||
* Action name for add product attributes.
|
||||
*/
|
||||
const ADD_PRODUCT_ATTRIBUTES_ACTION_NAME = 'add_product_attributes';
|
||||
|
||||
/**
|
||||
* Action name for import products.
|
||||
*/
|
||||
const IMPORT_PRODUCTS_ACTION_NAME = 'import_products';
|
||||
|
||||
/**
|
||||
* Action name for search.
|
||||
*/
|
||||
const SEARCH_ACTION_NAME = 'ces_search';
|
||||
|
||||
/**
|
||||
* Label for the snackbar that appears when a user submits the survey.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $onsubmit_label;
|
||||
|
||||
/**
|
||||
* Constructor. Sets up filters to hook into WooCommerce.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->enable_survey_enqueing_if_tracking_is_enabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add actions that require woocommerce_allow_tracking.
|
||||
*/
|
||||
private function enable_survey_enqueing_if_tracking_is_enabled() {
|
||||
// Only hook up the action handlers if in wp-admin.
|
||||
if ( ! is_admin() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not hook up the action handlers if a mobile device is used.
|
||||
if ( wp_is_mobile() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only enqueue a survey if tracking is allowed.
|
||||
$allow_tracking = 'yes' === get_option( 'woocommerce_allow_tracking', 'no' );
|
||||
if ( ! $allow_tracking ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'admin_init', array( $this, 'maybe_clear_ces_tracks_queue' ) );
|
||||
add_action( 'woocommerce_update_options', array( $this, 'run_on_update_options' ), 10, 3 );
|
||||
add_action( 'product_cat_add_form', array( $this, 'add_script_track_product_categories' ), 10, 3 );
|
||||
add_action( 'product_tag_add_form', array( $this, 'add_script_track_product_tags' ), 10, 3 );
|
||||
add_action( 'woocommerce_attribute_added', array( $this, 'run_on_add_product_attributes' ), 10, 3 );
|
||||
add_action( 'load-edit.php', array( $this, 'run_on_load_edit_php' ), 10, 3 );
|
||||
add_action( 'product_page_product_importer', array( $this, 'run_on_product_import' ), 10, 3 );
|
||||
|
||||
$this->onsubmit_label = __( 'Thank you for your feedback!', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a generated script for tracking tags added on edit-tags.php page.
|
||||
* CES survey is triggered via direct access to wc/customer-effort-score store
|
||||
* via wp.data.dispatch method.
|
||||
*
|
||||
* Due to lack of options to directly hook ourselves into the ajax post request
|
||||
* initiated by edit-tags.php page, we infer a successful request by observing
|
||||
* an increase of the number of rows in tags table
|
||||
*
|
||||
* @param string $action Action name for the survey.
|
||||
* @param string $label Label for the snackbar.
|
||||
*
|
||||
* @return string Generated JavaScript to append to page.
|
||||
*/
|
||||
private function get_script_track_edit_php( $action, $label ) {
|
||||
return sprintf(
|
||||
"(function( $ ) {
|
||||
'use strict';
|
||||
// Hook on submit button and sets a 500ms interval function
|
||||
// to determine successful add tag or otherwise.
|
||||
$('#addtag #submit').on( 'click', function() {
|
||||
const initialCount = $('.tags tbody > tr').length;
|
||||
const interval = setInterval( function() {
|
||||
if ( $('.tags tbody > tr').length > initialCount ) {
|
||||
// New tag detected.
|
||||
clearInterval( interval );
|
||||
wp.data.dispatch('wc/customer-effort-score').addCesSurvey( '%s', '%s', window.pagenow, window.adminpage, '%s' );
|
||||
} else {
|
||||
// Form is no longer loading, most likely failed.
|
||||
if ( $( '#addtag .submit .spinner.is-active' ).length < 1 ) {
|
||||
clearInterval( interval );
|
||||
}
|
||||
}
|
||||
}, 500 );
|
||||
});
|
||||
})( jQuery );",
|
||||
esc_js( $action ),
|
||||
esc_js( $label ),
|
||||
esc_js( $this->onsubmit_label )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current published product count.
|
||||
*
|
||||
* @return integer The current published product count.
|
||||
*/
|
||||
private function get_product_count() {
|
||||
$query = new \WC_Product_Query(
|
||||
array(
|
||||
'limit' => 1,
|
||||
'paginate' => true,
|
||||
'return' => 'ids',
|
||||
'status' => array( 'publish' ),
|
||||
)
|
||||
);
|
||||
$products = $query->get_products();
|
||||
$product_count = intval( $products->total );
|
||||
|
||||
return $product_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current shop order count.
|
||||
*
|
||||
* @return integer The current shop order count.
|
||||
*/
|
||||
private function get_shop_order_count() {
|
||||
$query = new \WC_Order_Query(
|
||||
array(
|
||||
'limit' => 1,
|
||||
'paginate' => true,
|
||||
'return' => 'ids',
|
||||
)
|
||||
);
|
||||
$shop_orders = $query->get_orders();
|
||||
$shop_order_count = intval( $shop_orders->total );
|
||||
|
||||
return $shop_order_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the action has already been shown.
|
||||
*
|
||||
* @param string $action The action to check.
|
||||
*
|
||||
* @return bool Whether the action has already been shown.
|
||||
*/
|
||||
private function has_been_shown( $action ) {
|
||||
$shown_for_features = get_option( self::SHOWN_FOR_ACTIONS_OPTION_NAME, array() );
|
||||
$has_been_shown = in_array( $action, $shown_for_features, true );
|
||||
|
||||
return $has_been_shown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the item to the CES tracks queue.
|
||||
*
|
||||
* @param array $item The item to enqueue.
|
||||
*/
|
||||
private function enqueue_to_ces_tracks( $item ) {
|
||||
$queue = get_option(
|
||||
self::CES_TRACKS_QUEUE_OPTION_NAME,
|
||||
array()
|
||||
);
|
||||
|
||||
$has_duplicate = array_filter(
|
||||
$queue,
|
||||
function ( $queue_item ) use ( $item ) {
|
||||
return $queue_item['action'] === $item['action'];
|
||||
}
|
||||
);
|
||||
if ( $has_duplicate ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$queue[] = $item;
|
||||
|
||||
update_option(
|
||||
self::CES_TRACKS_QUEUE_OPTION_NAME,
|
||||
$queue
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the CES survey on using search dynamically.
|
||||
*
|
||||
* @param string $search_area Search area such as "product" or "shop_order".
|
||||
* @param string $page_now Value of window.pagenow.
|
||||
* @param string $admin_page Value of window.adminpage.
|
||||
*/
|
||||
public function enqueue_ces_survey_for_search( $search_area, $page_now, $admin_page ) {
|
||||
if ( $this->has_been_shown( self::SEARCH_ACTION_NAME ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->enqueue_to_ces_tracks(
|
||||
array(
|
||||
'action' => self::SEARCH_ACTION_NAME,
|
||||
'label' => __(
|
||||
'How easy was it to use search?',
|
||||
'woocommerce'
|
||||
),
|
||||
'onsubmit_label' => $this->onsubmit_label,
|
||||
'pagenow' => $page_now,
|
||||
'adminpage' => $admin_page,
|
||||
'props' => (object) array(
|
||||
'search_area' => $search_area,
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe clear the CES tracks queue, executed on every page load. If the
|
||||
* clear option is set it clears the queue. In practice, this executes a
|
||||
* page load after the queued CES tracks are displayed on the client, which
|
||||
* sets the clear option.
|
||||
*/
|
||||
public function maybe_clear_ces_tracks_queue() {
|
||||
$clear_ces_tracks_queue_for_page = get_option(
|
||||
self::CLEAR_CES_TRACKS_QUEUE_FOR_PAGE_OPTION_NAME,
|
||||
false
|
||||
);
|
||||
|
||||
if ( ! $clear_ces_tracks_queue_for_page ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$queue = get_option(
|
||||
self::CES_TRACKS_QUEUE_OPTION_NAME,
|
||||
array()
|
||||
);
|
||||
$remaining_items = array_filter(
|
||||
$queue,
|
||||
function ( $item ) use ( $clear_ces_tracks_queue_for_page ) {
|
||||
return $clear_ces_tracks_queue_for_page['pagenow'] !== $item['pagenow']
|
||||
|| $clear_ces_tracks_queue_for_page['adminpage'] !== $item['adminpage'];
|
||||
}
|
||||
);
|
||||
|
||||
update_option(
|
||||
self::CES_TRACKS_QUEUE_OPTION_NAME,
|
||||
array_values( $remaining_items )
|
||||
);
|
||||
update_option( self::CLEAR_CES_TRACKS_QUEUE_FOR_PAGE_OPTION_NAME, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a script to footer to trigger CES on adding product categories.
|
||||
*/
|
||||
public function add_script_track_product_categories() {
|
||||
if ( $this->has_been_shown( self::ADD_PRODUCT_CATEGORIES_ACTION_NAME ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wc_enqueue_js(
|
||||
$this->get_script_track_edit_php(
|
||||
self::ADD_PRODUCT_CATEGORIES_ACTION_NAME,
|
||||
__( 'How easy was it to add product category?', 'woocommerce' )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a script to footer to trigger CES on adding product tags.
|
||||
*/
|
||||
public function add_script_track_product_tags() {
|
||||
if ( $this->has_been_shown( self::ADD_PRODUCT_TAGS_ACTION_NAME ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wc_enqueue_js(
|
||||
$this->get_script_track_edit_php(
|
||||
self::ADD_PRODUCT_TAGS_ACTION_NAME,
|
||||
__( 'How easy was it to add a product tag?', 'woocommerce' )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe enqueue the CES survey on product import, if step is done.
|
||||
*/
|
||||
public function run_on_product_import() {
|
||||
// We're only interested in when the importer completes.
|
||||
if ( empty( $_GET['step'] ) || 'done' !== $_GET['step'] ) { // phpcs:ignore CSRF ok.
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->has_been_shown( self::IMPORT_PRODUCTS_ACTION_NAME ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->enqueue_to_ces_tracks(
|
||||
array(
|
||||
'action' => self::IMPORT_PRODUCTS_ACTION_NAME,
|
||||
'label' => __(
|
||||
'How easy was it to import products?',
|
||||
'woocommerce'
|
||||
),
|
||||
'onsubmit_label' => $this->onsubmit_label,
|
||||
'pagenow' => 'product_page_product_importer',
|
||||
'adminpage' => 'product_page_product_importer',
|
||||
'props' => (object) array(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the CES survey trigger for setting changes.
|
||||
*/
|
||||
public function run_on_update_options() {
|
||||
// $current_tab is set when WC_Admin_Settings::save_settings is called.
|
||||
global $current_tab;
|
||||
global $current_section;
|
||||
|
||||
if ( $this->has_been_shown( self::SETTINGS_CHANGE_ACTION_NAME ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$props = array(
|
||||
'settings_area' => $current_tab,
|
||||
);
|
||||
|
||||
if ( $current_section ) {
|
||||
$props['settings_section'] = $current_section;
|
||||
}
|
||||
|
||||
$this->enqueue_to_ces_tracks(
|
||||
array(
|
||||
'action' => self::SETTINGS_CHANGE_ACTION_NAME,
|
||||
'label' => __(
|
||||
'How easy was it to update your settings?',
|
||||
'woocommerce'
|
||||
),
|
||||
'onsubmit_label' => $this->onsubmit_label,
|
||||
'pagenow' => 'woocommerce_page_wc-settings',
|
||||
'adminpage' => 'woocommerce_page_wc-settings',
|
||||
'props' => (object) $props,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the CES survey on adding new product attributes.
|
||||
*/
|
||||
public function run_on_add_product_attributes() {
|
||||
if ( $this->has_been_shown( self::ADD_PRODUCT_ATTRIBUTES_ACTION_NAME ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->enqueue_to_ces_tracks(
|
||||
array(
|
||||
'action' => self::ADD_PRODUCT_ATTRIBUTES_ACTION_NAME,
|
||||
'label' => __(
|
||||
'How easy was it to add a product attribute?',
|
||||
'woocommerce'
|
||||
),
|
||||
'onsubmit_label' => $this->onsubmit_label,
|
||||
'pagenow' => 'product_page_product_attributes',
|
||||
'adminpage' => 'product_page_product_attributes',
|
||||
'props' => (object) array(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine on initiating CES survey on searching for product or orders.
|
||||
*/
|
||||
public function run_on_load_edit_php() {
|
||||
$allowed_types = array( 'product', 'shop_order' );
|
||||
$post_type = get_current_screen()->post_type;
|
||||
|
||||
// We're only interested for certain post types.
|
||||
if ( ! in_array( $post_type, $allowed_types, true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine whether request is search by "s" GET parameter.
|
||||
if ( empty( $_GET['s'] ) ) { // phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
return;
|
||||
}
|
||||
|
||||
$page_now = 'edit-' . $post_type;
|
||||
$this->enqueue_ces_survey_for_search( $post_type, $page_now, 'edit-php' );
|
||||
}
|
||||
}
|
392
packages/woocommerce-admin/src/Features/Features.php
Normal file
392
packages/woocommerce-admin/src/Features/Features.php
Normal file
@ -0,0 +1,392 @@
|
||||
<?php
|
||||
/**
|
||||
* Features loader for features developed in WooCommerce Admin.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Loader;
|
||||
|
||||
/**
|
||||
* Features Class.
|
||||
*/
|
||||
class Features {
|
||||
/**
|
||||
* Class instance.
|
||||
*
|
||||
* @var Loader instance
|
||||
*/
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* Optional features
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $optional_features = array(
|
||||
'navigation' => array( 'default' => 'no' ),
|
||||
'settings' => array( 'default' => 'no' ),
|
||||
'analytics' => array( 'default' => 'yes' ),
|
||||
'remote-inbox-notifications' => array( 'default' => 'yes' ),
|
||||
);
|
||||
|
||||
/**
|
||||
* Beta features
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $beta_features = array(
|
||||
'navigation',
|
||||
'settings',
|
||||
);
|
||||
|
||||
/**
|
||||
* Get class instance.
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( ! self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
// Load feature before WooCommerce update hooks.
|
||||
add_action( 'init', array( __CLASS__, 'load_features' ), 4 );
|
||||
add_filter( 'woocommerce_get_sections_advanced', array( __CLASS__, 'add_features_section' ) );
|
||||
add_filter( 'woocommerce_get_settings_advanced', array( __CLASS__, 'add_features_settings' ), 10, 2 );
|
||||
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'maybe_load_beta_features_modal' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'load_scripts' ), 15 );
|
||||
add_filter( 'admin_body_class', array( __CLASS__, 'add_admin_body_classes' ) );
|
||||
add_filter( 'update_option_woocommerce_allow_tracking', array( __CLASS__, 'maybe_disable_features' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a build configured array of enabled WooCommerce Admin features/sections, but does not respect optionally disabled features.
|
||||
*
|
||||
* @return array Enabled Woocommerce Admin features/sections.
|
||||
*/
|
||||
public static function get_features() {
|
||||
return apply_filters( 'woocommerce_admin_features', array() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the optional feature options as an associative array that can be toggled on or off.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_optional_feature_options() {
|
||||
$features = [];
|
||||
|
||||
foreach ( array_keys( self::$optional_features ) as $optional_feature_key ) {
|
||||
$feature_class = self::get_feature_class( $optional_feature_key );
|
||||
|
||||
if ( $feature_class ) {
|
||||
$features[ $optional_feature_key ] = $feature_class::TOGGLE_OPTION_NAME;
|
||||
}
|
||||
}
|
||||
return $features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if a specific wc-admin feature exists in the current environment.
|
||||
*
|
||||
* @param string $feature Feature slug.
|
||||
* @return bool Returns true if the feature exists.
|
||||
*/
|
||||
public static function exists( $feature ) {
|
||||
$features = self::get_features();
|
||||
return in_array( $feature, $features, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the feature class as a string.
|
||||
*
|
||||
* @param string $feature Feature name.
|
||||
* @return string|null
|
||||
*/
|
||||
public static function get_feature_class( $feature ) {
|
||||
$feature = str_replace( '-', '', ucwords( strtolower( $feature ), '-' ) );
|
||||
$feature_class = 'Automattic\\WooCommerce\\Admin\\Features\\' . $feature;
|
||||
|
||||
if ( class_exists( $feature_class ) ) {
|
||||
return $feature_class;
|
||||
}
|
||||
|
||||
// Handle features contained in subdirectory.
|
||||
if ( class_exists( $feature_class . '\\Init' ) ) {
|
||||
return $feature_class . '\\Init';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class loader for enabled WooCommerce Admin features/sections.
|
||||
*/
|
||||
public static function load_features() {
|
||||
$features = self::get_features();
|
||||
foreach ( $features as $feature ) {
|
||||
$feature_class = self::get_feature_class( $feature );
|
||||
|
||||
if ( $feature_class ) {
|
||||
new $feature_class();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a build configured array of enabled WooCommerce Admin respecting optionally disabled features.
|
||||
*
|
||||
* @return array Enabled Woocommerce Admin features/sections.
|
||||
*/
|
||||
public static function get_available_features() {
|
||||
$features = self::get_features();
|
||||
$optional_feature_keys = array_keys( self::$optional_features );
|
||||
$optional_features_unavailable = [];
|
||||
|
||||
/**
|
||||
* Filter allowing WooCommerce Admin optional features to be disabled.
|
||||
*
|
||||
* @param bool $disabled False.
|
||||
*/
|
||||
if ( apply_filters( 'woocommerce_admin_disabled', false ) ) {
|
||||
return array_values( array_diff( $features, $optional_feature_keys ) );
|
||||
}
|
||||
|
||||
foreach ( $optional_feature_keys as $optional_feature_key ) {
|
||||
$feature_class = self::get_feature_class( $optional_feature_key );
|
||||
|
||||
if ( $feature_class ) {
|
||||
$default = isset( self::$optional_features[ $optional_feature_key ]['default'] ) ?
|
||||
self::$optional_features[ $optional_feature_key ]['default'] :
|
||||
'no';
|
||||
|
||||
// Check if the feature is currently being enabled, if it is continue.
|
||||
/* phpcs:disable WordPress.Security.NonceVerification */
|
||||
$feature_option = $feature_class::TOGGLE_OPTION_NAME;
|
||||
if ( isset( $_POST[ $feature_option ] ) && '1' === $_POST[ $feature_option ] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( 'yes' !== get_option( $feature_class::TOGGLE_OPTION_NAME, $default ) ) {
|
||||
$optional_features_unavailable[] = $optional_feature_key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values( array_diff( $features, $optional_features_unavailable ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a feature is enabled.
|
||||
*
|
||||
* @param string $feature Feature slug.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_enabled( $feature ) {
|
||||
$available_features = self::get_available_features();
|
||||
return in_array( $feature, $available_features, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable a toggleable optional feature.
|
||||
*
|
||||
* @param string $feature Feature name.
|
||||
* @return bool
|
||||
*/
|
||||
public static function enable( $feature ) {
|
||||
$features = self::get_optional_feature_options();
|
||||
|
||||
if ( isset( $features[ $feature ] ) ) {
|
||||
update_option( $features[ $feature ], 'yes' );
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable a toggleable optional feature.
|
||||
*
|
||||
* @param string $feature Feature name.
|
||||
* @return bool
|
||||
*/
|
||||
public static function disable( $feature ) {
|
||||
$features = self::get_optional_feature_options();
|
||||
|
||||
if ( isset( $features[ $feature ] ) ) {
|
||||
update_option( $features[ $feature ], 'no' );
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable features when opting out of tracking.
|
||||
*
|
||||
* @param string $old_value Old value.
|
||||
* @param string $value New value.
|
||||
*/
|
||||
public static function maybe_disable_features( $old_value, $value ) {
|
||||
if ( 'yes' === $value ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( self::$beta_features as $feature ) {
|
||||
self::disable( $feature );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the Features section to the advanced tab of WooCommerce Settings
|
||||
*
|
||||
* @param array $sections Sections.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_features_section( $sections ) {
|
||||
$features = apply_filters(
|
||||
'woocommerce_settings_features',
|
||||
array()
|
||||
);
|
||||
|
||||
if ( empty( $features ) ) {
|
||||
return $sections;
|
||||
}
|
||||
|
||||
$sections['features'] = __( 'Features', 'woocommerce' );
|
||||
return $sections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the Features settings.
|
||||
*
|
||||
* @param array $settings Settings.
|
||||
* @param string $current_section Current section slug.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_features_settings( $settings, $current_section ) {
|
||||
if ( 'features' !== $current_section ) {
|
||||
return $settings;
|
||||
}
|
||||
|
||||
$features = apply_filters(
|
||||
'woocommerce_settings_features',
|
||||
array()
|
||||
);
|
||||
|
||||
$features_disabled = apply_filters( 'woocommerce_admin_disabled', false );
|
||||
|
||||
if ( ! $features_disabled && empty( $features ) ) {
|
||||
return $settings;
|
||||
}
|
||||
|
||||
$desc = __( 'Start using new features that are being progressively rolled out to improve the store management experience.', 'woocommerce' );
|
||||
$disabled_desc = __( 'WooCommerce features have been disabled.', 'woocommerce' );
|
||||
|
||||
if ( $features_disabled ) {
|
||||
$GLOBALS['hide_save_button'] = true;
|
||||
}
|
||||
|
||||
return array_merge(
|
||||
array(
|
||||
array(
|
||||
'title' => __( 'Features', 'woocommerce' ),
|
||||
'type' => 'title',
|
||||
'desc' => $features_disabled ? $disabled_desc : $desc,
|
||||
'id' => 'features_options',
|
||||
),
|
||||
),
|
||||
$features_disabled ? array() : $features,
|
||||
array(
|
||||
array(
|
||||
'type' => 'sectionend',
|
||||
'id' => 'features_options',
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Conditionally loads the beta features tracking modal.
|
||||
*
|
||||
* @param string $hook Page hook.
|
||||
*/
|
||||
public static function maybe_load_beta_features_modal( $hook ) {
|
||||
if (
|
||||
'woocommerce_page_wc-settings' !== $hook ||
|
||||
! isset( $_GET['tab'] ) || 'advanced' !== $_GET['tab'] || // phpcs:ignore CSRF ok.
|
||||
! isset( $_GET['section'] ) || 'features' !== $_GET['section'] // phpcs:ignore CSRF ok.
|
||||
) {
|
||||
return;
|
||||
}
|
||||
$tracking_enabled = get_option( 'woocommerce_allow_tracking', 'no' );
|
||||
|
||||
if ( empty( self::$beta_features ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 'yes' === $tracking_enabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rtl = is_rtl() ? '.rtl' : '';
|
||||
|
||||
wp_enqueue_style(
|
||||
'wc-admin-beta-features-tracking-modal',
|
||||
Loader::get_url( "beta-features-tracking-modal/style{$rtl}", 'css' ),
|
||||
array( 'wp-components' ),
|
||||
Loader::get_file_version( 'css' )
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'wc-admin-beta-features-tracking-modal',
|
||||
Loader::get_url( 'wp-admin-scripts/beta-features-tracking-modal', 'js' ),
|
||||
array( 'wp-i18n', 'wp-element', WC_ADMIN_APP ),
|
||||
Loader::get_file_version( 'js' ),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the required scripts on the correct pages.
|
||||
*/
|
||||
public static function load_scripts() {
|
||||
if ( ! Loader::is_admin_or_embed_page() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$features = self::get_features();
|
||||
$enabled_features = array();
|
||||
foreach ( $features as $key ) {
|
||||
$enabled_features[ $key ] = self::is_enabled( $key );
|
||||
}
|
||||
wp_add_inline_script( WC_ADMIN_APP, 'window.wcAdminFeatures = ' . wp_json_encode( $enabled_features ), 'before' );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds body classes to the main wp-admin wrapper, allowing us to better target elements in specific scenarios.
|
||||
*
|
||||
* @param string $admin_body_class Body class to add.
|
||||
*/
|
||||
public static function add_admin_body_classes( $admin_body_class = '' ) {
|
||||
if ( ! Loader::is_admin_or_embed_page() ) {
|
||||
return $admin_body_class;
|
||||
}
|
||||
|
||||
$classes = explode( ' ', trim( $admin_body_class ) );
|
||||
|
||||
$features = self::get_features();
|
||||
foreach ( $features as $feature_key ) {
|
||||
$classes[] = sanitize_html_class( 'woocommerce-feature-enabled-' . $feature_key );
|
||||
}
|
||||
|
||||
$admin_body_class = implode( ' ', array_unique( $classes ) );
|
||||
return " $admin_body_class ";
|
||||
}
|
||||
}
|
190
packages/woocommerce-admin/src/Features/Homescreen.php
Normal file
190
packages/woocommerce-admin/src/Features/Homescreen.php
Normal file
@ -0,0 +1,190 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Homescreen.
|
||||
* NOTE: DO NOT edit this file in WooCommerce core, this is generated from woocommerce-admin.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Loader;
|
||||
|
||||
/**
|
||||
* Contains backend logic for the homescreen feature.
|
||||
*/
|
||||
class Homescreen {
|
||||
/**
|
||||
* Menu slug.
|
||||
*/
|
||||
const MENU_SLUG = 'wc-admin';
|
||||
|
||||
/**
|
||||
* Class instance.
|
||||
*
|
||||
* @var Homescreen 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() {
|
||||
add_filter( 'woocommerce_admin_get_user_data_fields', array( $this, 'add_user_data_fields' ) );
|
||||
add_action( 'admin_menu', array( $this, 'register_page' ) );
|
||||
// In WC Core 5.1 $submenu manipulation occurs in admin_menu, not admin_head. See https://github.com/woocommerce/woocommerce/pull/29088.
|
||||
if ( version_compare( WC_VERSION, '5.1', '>=' ) ) {
|
||||
// priority is 20 to run after admin_menu hook for woocommerce runs, so that submenu is populated.
|
||||
add_action( 'admin_menu', array( $this, 'possibly_remove_woocommerce_menu' ) );
|
||||
add_action( 'admin_menu', array( $this, 'update_link_structure' ), 20 );
|
||||
} else {
|
||||
// priority is 20 to run after https://github.com/woocommerce/woocommerce/blob/a55ae325306fc2179149ba9b97e66f32f84fdd9c/includes/admin/class-wc-admin-menus.php#L165.
|
||||
add_action( 'admin_head', array( $this, 'update_link_structure' ), 20 );
|
||||
}
|
||||
add_filter( 'woocommerce_admin_preload_options', array( $this, 'preload_options' ) );
|
||||
|
||||
add_filter( 'woocommerce_admin_shared_settings', array( $this, 'component_settings' ), 20 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds fields so that we can store performance indicators, row settings, and chart type settings for users.
|
||||
*
|
||||
* @param array $user_data_fields User data fields.
|
||||
* @return array
|
||||
*/
|
||||
public function add_user_data_fields( $user_data_fields ) {
|
||||
return array_merge(
|
||||
$user_data_fields,
|
||||
array(
|
||||
'homepage_layout',
|
||||
'homepage_stats',
|
||||
'task_list_tracked_started_tasks',
|
||||
'help_panel_highlight_shown',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers home page.
|
||||
*/
|
||||
public function register_page() {
|
||||
// Register a top-level item for users who cannot view the core WooCommerce menu.
|
||||
if ( ! $this->is_admin_user() ) {
|
||||
wc_admin_register_page(
|
||||
array(
|
||||
'id' => 'woocommerce-home',
|
||||
'title' => __( 'WooCommerce', 'woocommerce' ),
|
||||
'path' => self::MENU_SLUG,
|
||||
'capability' => 'read',
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
wc_admin_register_page(
|
||||
array(
|
||||
'id' => 'woocommerce-home',
|
||||
'title' => __( 'Home', 'woocommerce' ),
|
||||
'parent' => 'woocommerce',
|
||||
'path' => self::MENU_SLUG,
|
||||
'order' => 0,
|
||||
'capability' => 'read',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user can access the top-level WooCommerce item.
|
||||
*/
|
||||
public function is_admin_user() {
|
||||
return current_user_can( 'edit_others_shop_orders' ) || current_user_can( 'manage_woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Possibly remove the WooCommerce menu item if it was purely used to access wc-admin pages.
|
||||
*/
|
||||
public function possibly_remove_woocommerce_menu() {
|
||||
global $menu;
|
||||
|
||||
if ( $this->is_admin_user() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $menu as $key => $menu_item ) {
|
||||
if ( self::MENU_SLUG !== $menu_item[2] || 'read' !== $menu_item[1] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unset( $menu[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the WooCommerce menu structure to make our main dashboard/handler
|
||||
* the top level link for 'WooCommerce'.
|
||||
*/
|
||||
public function update_link_structure() {
|
||||
global $submenu;
|
||||
// User does not have capabilites to see the submenu.
|
||||
if ( ! current_user_can( 'manage_woocommerce' ) || empty( $submenu['woocommerce'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$wc_admin_key = null;
|
||||
foreach ( $submenu['woocommerce'] as $submenu_key => $submenu_item ) {
|
||||
if ( self::MENU_SLUG === $submenu_item[2] ) {
|
||||
$wc_admin_key = $submenu_key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $wc_admin_key ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$menu = $submenu['woocommerce'][ $wc_admin_key ];
|
||||
|
||||
// Move menu item to top of array.
|
||||
unset( $submenu['woocommerce'][ $wc_admin_key ] );
|
||||
array_unshift( $submenu['woocommerce'], $menu );
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload options to prime state of the application.
|
||||
*
|
||||
* @param array $options Array of options to preload.
|
||||
* @return array
|
||||
*/
|
||||
public function preload_options( $options ) {
|
||||
$options[] = 'woocommerce_default_homepage_layout';
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add data to the shared component settings.
|
||||
*
|
||||
* @param array $settings Shared component settings.
|
||||
*/
|
||||
public function component_settings( $settings ) {
|
||||
$allowed_statuses = Loader::get_order_statuses( wc_get_order_statuses() );
|
||||
|
||||
// Remove the Draft Order status (from the Checkout Block).
|
||||
unset( $allowed_statuses['checkout-draft'] );
|
||||
|
||||
$status_counts = array_map( 'wc_orders_count', array_keys( $allowed_statuses ) );
|
||||
$product_counts = wp_count_posts( 'product' );
|
||||
$settings['orderCount'] = array_sum( $status_counts );
|
||||
$settings['publishedProductCount'] = $product_counts->publish;
|
||||
|
||||
return $settings;
|
||||
}
|
||||
}
|
295
packages/woocommerce-admin/src/Features/Marketing.php
Normal file
295
packages/woocommerce-admin/src/Features/Marketing.php
Normal file
@ -0,0 +1,295 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Marketing.
|
||||
* NOTE: DO NOT edit this file in WooCommerce core, this is generated from woocommerce-admin.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\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.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const RECOMMENDED_PLUGINS_TRANSIENT = 'wc_marketing_recommended_plugins';
|
||||
|
||||
/**
|
||||
* Name of knowledge base post transient.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const KNOWLEDGE_BASE_TRANSIENT = 'wc_marketing_knowledge_base';
|
||||
|
||||
/**
|
||||
* Class instance.
|
||||
*
|
||||
* @var Marketing 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;
|
||||
}
|
||||
|
||||
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_admin_shared_settings', array( $this, 'component_settings' ), 30 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add main marketing menu item.
|
||||
*
|
||||
* Uses priority of 9 so other items can easily be added at the default priority (10).
|
||||
*/
|
||||
public function add_parent_menu_item() {
|
||||
if ( ! Features::is_enabled( 'navigation' ) ) {
|
||||
add_menu_page(
|
||||
__( 'Marketing', 'woocommerce' ),
|
||||
__( 'Marketing', 'woocommerce' ),
|
||||
'manage_woocommerce',
|
||||
'woocommerce-marketing',
|
||||
null,
|
||||
'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() {
|
||||
$this->register_overview_page();
|
||||
|
||||
$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_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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
protected function register_overview_page() {
|
||||
global $submenu;
|
||||
|
||||
// First register the page.
|
||||
PageController::get_instance()->register_page(
|
||||
[
|
||||
'id' => 'woocommerce-marketing-overview',
|
||||
'title' => __( 'Overview', 'woocommerce' ),
|
||||
'path' => 'wc-admin&path=/marketing',
|
||||
'parent' => 'woocommerce-marketing',
|
||||
'nav_args' => array(
|
||||
'parent' => 'woocommerce-marketing',
|
||||
'order' => 10,
|
||||
),
|
||||
]
|
||||
);
|
||||
|
||||
// Now fix the path, since register_page() gets it wrong.
|
||||
if ( ! isset( $submenu['woocommerce-marketing'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload options to prime state of the application.
|
||||
*
|
||||
* @param array $options Array of options to preload.
|
||||
* @return array
|
||||
*/
|
||||
public function preload_options( $options ) {
|
||||
$options[] = 'woocommerce_marketing_overview_welcome_hidden';
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add settings for marketing feature.
|
||||
*
|
||||
* @param array $settings Component settings.
|
||||
* @return array
|
||||
*/
|
||||
public function component_settings( $settings ) {
|
||||
// Bail early if not on a wc-admin powered page.
|
||||
if ( ! Loader::is_admin_page() ) {
|
||||
return $settings;
|
||||
}
|
||||
|
||||
$settings['marketing']['installedExtensions'] = InstalledExtensions::get_data();
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load recommended plugins from WooCommerce.com
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_recommended_plugins() {
|
||||
$plugins = get_transient( self::RECOMMENDED_PLUGINS_TRANSIENT );
|
||||
|
||||
if ( false === $plugins ) {
|
||||
$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'] ) {
|
||||
$plugins = json_decode( $request['body'], true );
|
||||
}
|
||||
|
||||
set_transient(
|
||||
self::RECOMMENDED_PLUGINS_TRANSIENT,
|
||||
$plugins,
|
||||
// Expire transient in 15 minutes if remote get failed.
|
||||
// Cache an empty result to avoid repeated failed requests.
|
||||
empty( $plugins ) ? 900 : 3 * DAY_IN_SECONDS
|
||||
);
|
||||
}
|
||||
|
||||
return array_values( $plugins );
|
||||
}
|
||||
|
||||
/**
|
||||
* Load knowledge base posts from WooCommerce.com
|
||||
*
|
||||
* @param string $category Category of posts to retrieve.
|
||||
* @return array
|
||||
*/
|
||||
public function get_knowledge_base_posts( $category ) {
|
||||
|
||||
$kb_transient = self::KNOWLEDGE_BASE_TRANSIENT;
|
||||
|
||||
$categories = array(
|
||||
'marketing' => 1744,
|
||||
'coupons' => 25202,
|
||||
);
|
||||
|
||||
// Default to marketing category (if no category set on the kb component).
|
||||
if ( ! empty( $category ) && array_key_exists( $category, $categories ) ) {
|
||||
$category_id = $categories[ $category ];
|
||||
$kb_transient = $kb_transient . '_' . strtolower( $category );
|
||||
} else {
|
||||
$category_id = $categories['marketing'];
|
||||
}
|
||||
|
||||
$posts = get_transient( $kb_transient );
|
||||
|
||||
if ( false === $posts ) {
|
||||
$request_url = add_query_arg(
|
||||
array(
|
||||
'categories' => $category_id,
|
||||
'page' => 1,
|
||||
'per_page' => 8,
|
||||
'_embed' => 1,
|
||||
),
|
||||
'https://woocommerce.com/wp-json/wp/v2/posts?utm_medium=product'
|
||||
);
|
||||
|
||||
$request = wp_remote_get( $request_url );
|
||||
$posts = [];
|
||||
|
||||
if ( ! is_wp_error( $request ) && 200 === $request['response']['code'] ) {
|
||||
$raw_posts = json_decode( $request['body'], true );
|
||||
|
||||
foreach ( $raw_posts as $raw_post ) {
|
||||
$post = [
|
||||
'title' => html_entity_decode( $raw_post['title']['rendered'] ),
|
||||
'date' => $raw_post['date_gmt'],
|
||||
'link' => $raw_post['link'],
|
||||
'author_name' => isset( $raw_post['author_name'] ) ? html_entity_decode( $raw_post['author_name'] ) : '',
|
||||
'author_avatar' => isset( $raw_post['author_avatar_url'] ) ? $raw_post['author_avatar_url'] : '',
|
||||
];
|
||||
|
||||
$featured_media = $raw_post['_embedded']['wp:featuredmedia'];
|
||||
|
||||
if ( count( $featured_media ) > 0 ) {
|
||||
$image = current( $featured_media );
|
||||
$post['image'] = add_query_arg(
|
||||
array(
|
||||
'resize' => '650,340',
|
||||
'crop' => 1,
|
||||
),
|
||||
$image['source_url']
|
||||
);
|
||||
}
|
||||
|
||||
$posts[] = $post;
|
||||
}
|
||||
}
|
||||
|
||||
set_transient(
|
||||
$kb_transient,
|
||||
$posts,
|
||||
// Expire transient in 15 minutes if remote get failed.
|
||||
empty( $posts ) ? 900 : DAY_IN_SECONDS
|
||||
);
|
||||
}
|
||||
|
||||
return $posts;
|
||||
}
|
||||
}
|
49
packages/woocommerce-admin/src/Features/MobileAppBanner.php
Normal file
49
packages/woocommerce-admin/src/Features/MobileAppBanner.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Determine if the mobile app banner shows on Android devices
|
||||
*/
|
||||
class MobileAppBanner {
|
||||
/**
|
||||
* Class instance.
|
||||
*
|
||||
* @var Analytics 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() {
|
||||
add_filter( 'woocommerce_admin_get_user_data_fields', array( $this, 'add_user_data_fields' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds fields so that we can store user preferences for the mobile app banner
|
||||
*
|
||||
* @param array $user_data_fields User data fields.
|
||||
* @return array
|
||||
*/
|
||||
public function add_user_data_fields( $user_data_fields ) {
|
||||
return array_merge(
|
||||
$user_data_fields,
|
||||
array(
|
||||
'android_app_banner_dismissed',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
427
packages/woocommerce-admin/src/Features/Navigation/CoreMenu.php
Normal file
427
packages/woocommerce-admin/src/Features/Navigation/CoreMenu.php
Normal file
@ -0,0 +1,427 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Navigation Core Menu
|
||||
*
|
||||
* @package Woocommerce Admin
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\Navigation;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Admin\Features\Navigation\Menu;
|
||||
use Automattic\WooCommerce\Admin\Features\Navigation\Screen;
|
||||
|
||||
/**
|
||||
* CoreMenu class. Handles registering Core menu items.
|
||||
*/
|
||||
class CoreMenu {
|
||||
/**
|
||||
* Class instance.
|
||||
*
|
||||
* @var Menu instance
|
||||
*/
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* Get class instance.
|
||||
*/
|
||||
final public static function instance() {
|
||||
if ( ! static::$instance ) {
|
||||
static::$instance = new static();
|
||||
}
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*/
|
||||
public function init() {
|
||||
add_action( 'admin_menu', array( $this, 'register_post_types' ) );
|
||||
// Add this after we've finished migrating menu items to avoid hiding these items.
|
||||
add_action( 'admin_menu', array( $this, 'add_dashboard_menu_items' ), PHP_INT_MAX );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add registered admin settings as menu items.
|
||||
*/
|
||||
public static function get_setting_items() {
|
||||
// Let the Settings feature add pages to the navigation if enabled.
|
||||
if ( Features::is_enabled( 'settings' ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Calling this method adds pages to the below tabs filter on non-settings pages.
|
||||
\WC_Admin_Settings::get_settings_pages();
|
||||
$tabs = apply_filters( 'woocommerce_settings_tabs_array', array() );
|
||||
|
||||
$menu_items = array();
|
||||
$order = 0;
|
||||
foreach ( $tabs as $key => $setting ) {
|
||||
$order += 10;
|
||||
$menu_items[] = (
|
||||
array(
|
||||
'parent' => 'woocommerce-settings',
|
||||
'title' => $setting,
|
||||
'capability' => 'manage_woocommerce',
|
||||
'id' => 'settings-' . $key,
|
||||
'url' => 'admin.php?page=wc-settings&tab=' . $key,
|
||||
'order' => $order,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $menu_items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all menu categories.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_categories() {
|
||||
$analytics_enabled = Features::is_enabled( 'analytics' );
|
||||
return array(
|
||||
array(
|
||||
'title' => __( 'Orders', 'woocommerce' ),
|
||||
'id' => 'woocommerce-orders',
|
||||
'order' => 10,
|
||||
),
|
||||
array(
|
||||
'title' => __( 'Products', 'woocommerce' ),
|
||||
'id' => 'woocommerce-products',
|
||||
'order' => 20,
|
||||
),
|
||||
$analytics_enabled ?
|
||||
array(
|
||||
'title' => __( 'Analytics', 'woocommerce' ),
|
||||
'id' => 'woocommerce-analytics',
|
||||
'order' => 30,
|
||||
) : null,
|
||||
$analytics_enabled ?
|
||||
array(
|
||||
'title' => __( 'Reports', 'woocommerce' ),
|
||||
'id' => 'woocommerce-reports',
|
||||
'parent' => 'woocommerce-analytics',
|
||||
'order' => 200,
|
||||
) : null,
|
||||
array(
|
||||
'title' => __( 'Marketing', 'woocommerce' ),
|
||||
'id' => 'woocommerce-marketing',
|
||||
'order' => 40,
|
||||
),
|
||||
array(
|
||||
'title' => __( 'Marketplace', 'woocommerce' ),
|
||||
'id' => 'woocommerce-marketplace',
|
||||
'menuId' => 'secondary',
|
||||
'order' => 10,
|
||||
),
|
||||
array(
|
||||
'title' => __( 'Settings', 'woocommerce' ),
|
||||
'id' => 'woocommerce-settings',
|
||||
'menuId' => 'secondary',
|
||||
'order' => 20,
|
||||
'url' => 'admin.php?page=wc-settings',
|
||||
),
|
||||
array(
|
||||
'title' => __( 'Tools', 'woocommerce' ),
|
||||
'id' => 'woocommerce-tools',
|
||||
'menuId' => 'secondary',
|
||||
'order' => 30,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all menu items.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_items() {
|
||||
$order_items = Menu::get_post_type_items( 'shop_order', array( 'parent' => 'woocommerce-orders' ) );
|
||||
$product_items = Menu::get_post_type_items( 'product', array( 'parent' => 'woocommerce-products' ) );
|
||||
$product_tag_items = Menu::get_taxonomy_items(
|
||||
'product_tag',
|
||||
array(
|
||||
'parent' => 'woocommerce-products',
|
||||
'order' => 30,
|
||||
)
|
||||
);
|
||||
$product_cat_items = Menu::get_taxonomy_items(
|
||||
'product_cat',
|
||||
array(
|
||||
'parent' => 'woocommerce-products',
|
||||
'order' => 20,
|
||||
)
|
||||
);
|
||||
|
||||
$coupon_items = Menu::get_post_type_items( 'shop_coupon', array( 'parent' => 'woocommerce-marketing' ) );
|
||||
$setting_items = self::get_setting_items();
|
||||
$wca_items = array();
|
||||
$wca_pages = \Automattic\WooCommerce\Admin\PageController::get_instance()->get_pages();
|
||||
|
||||
foreach ( $wca_pages as $page ) {
|
||||
if ( ! isset( $page['nav_args'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = isset( $page['path'] ) ? $page['path'] : null;
|
||||
$item = array_merge(
|
||||
array(
|
||||
'id' => $page['id'],
|
||||
'url' => $path,
|
||||
'title' => $page['title'][0],
|
||||
'capability' => isset( $page['capability'] ) ? $page['capability'] : 'manage_woocommerce',
|
||||
),
|
||||
$page['nav_args']
|
||||
);
|
||||
|
||||
// Don't allow top-level items to be added to the primary menu.
|
||||
if ( ! isset( $item['parent'] ) || 'woocommerce' === $item['parent'] ) {
|
||||
$item['menuId'] = 'plugins';
|
||||
}
|
||||
|
||||
$wca_items[] = $item;
|
||||
}
|
||||
|
||||
$home_item = array();
|
||||
if ( defined( '\Automattic\WooCommerce\Admin\Features\Homescreen::MENU_SLUG' ) ) {
|
||||
$home_item = array(
|
||||
'id' => 'woocommerce-home',
|
||||
'title' => __( 'Home', 'woocommerce' ),
|
||||
'url' => \Automattic\WooCommerce\Admin\Features\Homescreen::MENU_SLUG,
|
||||
'order' => 0,
|
||||
'matchExpression' => 'page=wc-admin((?!path=).)*$',
|
||||
);
|
||||
}
|
||||
|
||||
$customers_item = array();
|
||||
if ( Features::is_enabled( 'analytics' ) ) {
|
||||
$customers_item = array(
|
||||
'id' => 'woocommerce-analytics-customers',
|
||||
'title' => __( 'Customers', 'woocommerce' ),
|
||||
'url' => 'wc-admin&path=/customers',
|
||||
'order' => 50,
|
||||
);
|
||||
}
|
||||
|
||||
return array_merge(
|
||||
array(
|
||||
$home_item,
|
||||
$customers_item,
|
||||
$order_items['all'],
|
||||
$order_items['new'],
|
||||
$product_items['all'],
|
||||
$product_cat_items['default'],
|
||||
$product_tag_items['default'],
|
||||
array(
|
||||
'id' => 'woocommerce-product-attributes',
|
||||
'title' => __( 'Attributes', 'woocommerce' ),
|
||||
'url' => 'edit.php?post_type=product&page=product_attributes',
|
||||
'capability' => 'manage_product_terms',
|
||||
'order' => 40,
|
||||
'parent' => 'woocommerce-products',
|
||||
'matchExpression' => 'edit.php(?=.*[?|&]page=product_attributes(&|$|#))|edit-tags.php(?=.*[?|&]taxonomy=pa_)(?=.*[?|&]post_type=product(&|$|#))',
|
||||
),
|
||||
array_merge( $product_items['new'], array( 'order' => 50 ) ),
|
||||
$coupon_items['default'],
|
||||
),
|
||||
// Marketplace category.
|
||||
self::get_marketplace_items(),
|
||||
// Tools category.
|
||||
self::get_tool_items(),
|
||||
// WooCommerce Admin items.
|
||||
$wca_items,
|
||||
// Settings category.
|
||||
$setting_items,
|
||||
// Legacy report items.
|
||||
self::get_legacy_report_items()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get marketplace menu items.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_marketplace_items() {
|
||||
return array(
|
||||
array(
|
||||
'parent' => 'woocommerce-marketplace',
|
||||
'title' => __( 'Browse', 'woocommerce' ),
|
||||
'capability' => 'manage_woocommerce',
|
||||
'id' => 'marketplace-browse',
|
||||
'url' => 'admin.php?page=wc-addons',
|
||||
'migrate' => false,
|
||||
'order' => 0,
|
||||
),
|
||||
array(
|
||||
'parent' => 'woocommerce-marketplace',
|
||||
'title' => __( 'My Subscriptions', 'woocommerce' ),
|
||||
'capability' => 'manage_woocommerce',
|
||||
'id' => 'marketplace-my-subscriptions',
|
||||
'url' => 'admin.php?page=wc-addons§ion=helper',
|
||||
'migrate' => false,
|
||||
'order' => 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get items for tools category.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_tool_items() {
|
||||
$tabs = array(
|
||||
'status' => __( 'System status', 'woocommerce' ),
|
||||
'tools' => __( 'Utilities', 'woocommerce' ),
|
||||
'logs' => __( 'Logs', 'woocommerce' ),
|
||||
);
|
||||
$tabs = apply_filters( 'woocommerce_admin_status_tabs', $tabs );
|
||||
|
||||
$order = 1;
|
||||
$items = array(
|
||||
array(
|
||||
'parent' => 'woocommerce-tools',
|
||||
'title' => __( 'Import / Export', 'woocommerce' ),
|
||||
'capability' => 'import',
|
||||
'id' => 'tools-import-export',
|
||||
'url' => 'import.php',
|
||||
'migrate' => false,
|
||||
'order' => 0,
|
||||
),
|
||||
);
|
||||
|
||||
foreach ( $tabs as $key => $tab ) {
|
||||
$items[] = array(
|
||||
'parent' => 'woocommerce-tools',
|
||||
'title' => $tab,
|
||||
'capability' => 'manage_woocommerce',
|
||||
'id' => 'tools-' . $key,
|
||||
'url' => 'wc-status&tab=' . $key,
|
||||
'order' => $order,
|
||||
);
|
||||
$order++;
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get legacy report items.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_legacy_report_items() {
|
||||
$reports = \WC_Admin_Reports::get_reports();
|
||||
$menu_items = array();
|
||||
|
||||
$order = 0;
|
||||
foreach ( $reports as $key => $report ) {
|
||||
$menu_items[] = array(
|
||||
'parent' => 'woocommerce-reports',
|
||||
'title' => $report['title'],
|
||||
'capability' => 'view_woocommerce_reports',
|
||||
'id' => $key,
|
||||
'url' => 'wc-reports&tab=' . $key,
|
||||
'order' => $order,
|
||||
);
|
||||
$order++;
|
||||
}
|
||||
|
||||
return $menu_items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all core post types.
|
||||
*/
|
||||
public function register_post_types() {
|
||||
Screen::register_post_type( 'shop_order' );
|
||||
Screen::register_post_type( 'product' );
|
||||
Screen::register_post_type( 'shop_coupon' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the dashboard items to the WP menu to create a quick-access flyout menu.
|
||||
*/
|
||||
public function add_dashboard_menu_items() {
|
||||
global $submenu, $menu;
|
||||
$mapped_items = Menu::get_mapped_menu_items();
|
||||
$top_level = $mapped_items['woocommerce'];
|
||||
|
||||
// phpcs:disable
|
||||
if ( ! isset( $submenu['woocommerce'] ) || empty( $top_level ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$menuIds = array(
|
||||
'primary',
|
||||
'secondary',
|
||||
'favorites',
|
||||
);
|
||||
|
||||
foreach ( $menuIds as $menuId ) {
|
||||
foreach( $top_level[ $menuId ] as $item ) {
|
||||
// Skip specific categories.
|
||||
if (
|
||||
in_array(
|
||||
$item['id'],
|
||||
array(
|
||||
'woocommerce-tools',
|
||||
),
|
||||
true
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use the link from the first item if it's a category.
|
||||
if ( ! isset( $item['url'] ) ) {
|
||||
$categoryMenuId = $menuId === 'favorites' ? 'plugins' : $menuId;
|
||||
$category_items = $mapped_items[ $item['id'] ][ $categoryMenuId ];
|
||||
|
||||
if ( ! empty( $category_items ) ) {
|
||||
$first_item = $category_items[0];
|
||||
|
||||
|
||||
$submenu['woocommerce'][] = array(
|
||||
$item['title'],
|
||||
$first_item['capability'],
|
||||
isset( $first_item['url'] ) ? $first_item['url'] : null,
|
||||
$item['title'],
|
||||
);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Show top-level items.
|
||||
$submenu['woocommerce'][] = array(
|
||||
$item['title'],
|
||||
$item['capability'],
|
||||
isset( $item['url'] ) ? $item['url'] : null,
|
||||
$item['title'],
|
||||
);
|
||||
}
|
||||
}
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
/**
|
||||
* Get items excluded from WooCommerce menu migration.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_excluded_items() {
|
||||
$excluded_items = array(
|
||||
'woocommerce',
|
||||
'wc-reports',
|
||||
'wc-settings',
|
||||
'wc-status',
|
||||
'wc-addons',
|
||||
'wc-addons§ion=helper',
|
||||
);
|
||||
|
||||
return apply_filters( 'woocommerce_navigation_core_excluded_items', $excluded_items );
|
||||
}
|
||||
}
|
131
packages/woocommerce-admin/src/Features/Navigation/Favorites.php
Normal file
131
packages/woocommerce-admin/src/Features/Navigation/Favorites.php
Normal file
@ -0,0 +1,131 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Navigation Favorite
|
||||
*
|
||||
* @package Woocommerce Navigation
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\Navigation;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Loader;
|
||||
|
||||
/**
|
||||
* Contains logic for the WooCommerce Navigation menu.
|
||||
*/
|
||||
class Favorites {
|
||||
|
||||
/**
|
||||
* Array index of menu capability.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const META_NAME = 'navigation_favorites';
|
||||
|
||||
/**
|
||||
* Get class instance.
|
||||
*/
|
||||
final public static function instance() {
|
||||
if ( ! static::$instance ) {
|
||||
static::$instance = new static();
|
||||
}
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set given favorites string to the user meta data.
|
||||
*
|
||||
* @param string|number $user_id User id.
|
||||
* @param array $favorites Array of favorite values to set.
|
||||
*/
|
||||
private static function set_meta_value( $user_id, $favorites ) {
|
||||
Loader::update_user_data_field( $user_id, self::META_NAME, wp_json_encode( (array) $favorites ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add item to favorites
|
||||
*
|
||||
* @param string $item_id Identifier of item to add.
|
||||
* @param string|number $user_id Identifier of user to add to.
|
||||
* @return WP_Error|Boolean Throws exception if item already exists.
|
||||
*/
|
||||
public static function add_item( $item_id, $user_id = null ) {
|
||||
$user = $user_id ?? get_current_user_id();
|
||||
|
||||
if ( ! $user || ! $item_id ) {
|
||||
return new \WP_Error(
|
||||
'woocommerce_favorites_invalid_request',
|
||||
__( 'Sorry, invalid request', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
|
||||
$all_favorites = self::get_all( $user );
|
||||
|
||||
if ( in_array( $item_id, $all_favorites, true ) ) {
|
||||
return new \WP_Error(
|
||||
'woocommerce_favorites_already_exists',
|
||||
__( 'Favorite already exists', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
|
||||
$all_favorites[] = $item_id;
|
||||
|
||||
self::set_meta_value( $user, $all_favorites );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove item from favorites
|
||||
*
|
||||
* @param string $item_id Identifier of item to remove.
|
||||
* @param string|number $user_id Identifier of user to remove from.
|
||||
* @return \WP_Error|Boolean Throws exception if item does not exist.
|
||||
*/
|
||||
public static function remove_item( $item_id, $user_id = null ) {
|
||||
$user = $user_id ?? get_current_user_id();
|
||||
|
||||
if ( ! $user || ! $item_id ) {
|
||||
return new \WP_Error(
|
||||
'woocommerce_favorites_invalid_request',
|
||||
__( 'Sorry, invalid request', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
|
||||
$all_favorites = self::get_all( $user );
|
||||
|
||||
if ( ! in_array( $item_id, $all_favorites, true ) ) {
|
||||
return new \WP_Error(
|
||||
'woocommerce_favorites_does_not_exist',
|
||||
__( 'Favorite item not found', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
|
||||
$remaining = array_values( array_diff( $all_favorites, [ $item_id ] ) );
|
||||
|
||||
self::set_meta_value( $user, $remaining );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered favorites.
|
||||
*
|
||||
* @param string|number $user_id Identifier of user to query.
|
||||
* @return WP_Error|Array
|
||||
*/
|
||||
public static function get_all( $user_id = null ) {
|
||||
$user = $user_id ?? get_current_user_id();
|
||||
|
||||
if ( ! $user ) {
|
||||
return new \WP_Error(
|
||||
'woocommerce_favorites_invalid_request',
|
||||
__( 'Sorry, invalid request', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
|
||||
$response = Loader::get_user_data_field( $user, self::META_NAME );
|
||||
|
||||
return $response ? json_decode( $response, true ) : array();
|
||||
}
|
||||
|
||||
}
|
161
packages/woocommerce-admin/src/Features/Navigation/Init.php
Normal file
161
packages/woocommerce-admin/src/Features/Navigation/Init.php
Normal file
@ -0,0 +1,161 @@
|
||||
<?php
|
||||
/**
|
||||
* Navigation Experience
|
||||
*
|
||||
* @package Woocommerce Admin
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\Navigation;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Loader;
|
||||
use Automattic\WooCommerce\Admin\Survey;
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Admin\Features\Navigation\Screen;
|
||||
use Automattic\WooCommerce\Admin\Features\Navigation\Menu;
|
||||
use Automattic\WooCommerce\Admin\Features\Navigation\CoreMenu;
|
||||
|
||||
/**
|
||||
* Contains logic for the Navigation
|
||||
*/
|
||||
class Init {
|
||||
/**
|
||||
* Option name used to toggle this feature.
|
||||
*/
|
||||
const TOGGLE_OPTION_NAME = 'woocommerce_navigation_enabled';
|
||||
|
||||
/**
|
||||
* Hook into WooCommerce.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_filter( 'woocommerce_settings_features', array( $this, 'add_feature_toggle' ) );
|
||||
add_action( 'update_option_' . self::TOGGLE_OPTION_NAME, array( $this, 'reload_page_on_toggle' ), 10, 2 );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'maybe_enqueue_opt_out_scripts' ) );
|
||||
|
||||
if ( Features::is_enabled( 'navigation' ) ) {
|
||||
Menu::instance()->init();
|
||||
CoreMenu::instance()->init();
|
||||
Screen::instance()->init();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the feature toggle to the features settings.
|
||||
*
|
||||
* @param array $features Feature sections.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_feature_toggle( $features ) {
|
||||
$description = __(
|
||||
'Adds the new WooCommerce navigation experience to the dashboard',
|
||||
'woocommerce'
|
||||
);
|
||||
$update_text = '';
|
||||
$needs_update = version_compare( get_bloginfo( 'version' ), '5.6', '<' );
|
||||
if ( $needs_update && current_user_can( 'update_core' ) && current_user_can( 'update_php' ) ) {
|
||||
$update_text = sprintf(
|
||||
/* translators: 1: line break tag, 2: open link to WordPress update link, 3: close link tag. */
|
||||
__( '%1$s %2$sUpdate WordPress to enable the new navigation%3$s', 'woocommerce' ),
|
||||
'<br/>',
|
||||
'<a href="' . self_admin_url( 'update-core.php' ) . '" target="_blank">',
|
||||
'</a>'
|
||||
);
|
||||
}
|
||||
|
||||
$features[] = array(
|
||||
'title' => __( 'Navigation', 'woocommerce' ),
|
||||
'desc' => $description . $update_text,
|
||||
'id' => self::TOGGLE_OPTION_NAME,
|
||||
'type' => 'checkbox',
|
||||
'class' => $needs_update ? 'disabled' : '',
|
||||
);
|
||||
|
||||
return $features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if sufficient versions are present to support Navigation feature
|
||||
*/
|
||||
public function is_nav_compatible() {
|
||||
include_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
|
||||
$gutenberg_minimum_version = '9.0.0'; // https://github.com/WordPress/gutenberg/releases/tag/v9.0.0.
|
||||
$wp_minimum_version = '5.6';
|
||||
$has_gutenberg = is_plugin_active( 'gutenberg/gutenberg.php' );
|
||||
$gutenberg_version = $has_gutenberg ? get_plugin_data( WP_PLUGIN_DIR . '/gutenberg/gutenberg.php' )['Version'] : false;
|
||||
|
||||
if ( $gutenberg_version && version_compare( $gutenberg_version, $gutenberg_minimum_version, '>=' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get unmodified $wp_version.
|
||||
include ABSPATH . WPINC . '/version.php';
|
||||
|
||||
// Strip '-src' from the version string. Messes up version_compare().
|
||||
$wp_version = str_replace( '-src', '', $wp_version );
|
||||
|
||||
if ( version_compare( $wp_version, $wp_minimum_version, '>=' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the page when the option is toggled to make sure all nav features are loaded.
|
||||
*
|
||||
* @param string $old_value Old value.
|
||||
* @param string $value New value.
|
||||
*/
|
||||
public static function reload_page_on_toggle( $old_value, $value ) {
|
||||
if ( $old_value === $value ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 'yes' !== $value ) {
|
||||
update_option( 'woocommerce_navigation_show_opt_out', 'yes' );
|
||||
}
|
||||
|
||||
if ( isset( $_SERVER['REQUEST_URI'] ) ) {
|
||||
wp_safe_redirect( wp_unslash( $_SERVER['REQUEST_URI'] ) );
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the opt out scripts.
|
||||
*/
|
||||
public function maybe_enqueue_opt_out_scripts() {
|
||||
if ( 'yes' !== get_option( 'woocommerce_navigation_show_opt_out', 'no' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rtl = is_rtl() ? '.rtl' : '';
|
||||
wp_enqueue_style(
|
||||
'wc-admin-navigation-opt-out',
|
||||
Loader::get_url( "navigation-opt-out/style{$rtl}", 'css' ),
|
||||
array( 'wp-components' ),
|
||||
Loader::get_file_version( 'css' )
|
||||
);
|
||||
|
||||
$script_assets_filename = Loader::get_script_asset_filename( 'wp-admin-scripts', 'navigation-opt-out' );
|
||||
$script_assets = require WC_ADMIN_ABSPATH . WC_ADMIN_DIST_JS_FOLDER . 'wp-admin-scripts/' . $script_assets_filename;
|
||||
|
||||
wp_enqueue_script(
|
||||
'wc-admin-navigation-opt-out',
|
||||
Loader::get_url( 'wp-admin-scripts/navigation-opt-out', 'js' ),
|
||||
array_merge( array( WC_ADMIN_APP ), $script_assets ['dependencies'] ),
|
||||
Loader::get_file_version( 'js' ),
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'wc-admin-navigation-opt-out',
|
||||
'surveyData',
|
||||
array(
|
||||
'url' => Survey::get_url( '/new-navigation-opt-out' ),
|
||||
)
|
||||
);
|
||||
|
||||
delete_option( 'woocommerce_navigation_show_opt_out' );
|
||||
}
|
||||
}
|
796
packages/woocommerce-admin/src/Features/Navigation/Menu.php
Normal file
796
packages/woocommerce-admin/src/Features/Navigation/Menu.php
Normal file
@ -0,0 +1,796 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Navigation Menu
|
||||
*
|
||||
* @package Woocommerce Navigation
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\Navigation;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Navigation\Favorites;
|
||||
use Automattic\WooCommerce\Admin\Features\Navigation\Screen;
|
||||
use Automattic\WooCommerce\Admin\Features\Navigation\CoreMenu;
|
||||
|
||||
/**
|
||||
* Contains logic for the WooCommerce Navigation menu.
|
||||
*/
|
||||
class Menu {
|
||||
/**
|
||||
* Class instance.
|
||||
*
|
||||
* @var Menu instance
|
||||
*/
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* Array index of menu capability.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const CAPABILITY = 1;
|
||||
|
||||
/**
|
||||
* Array index of menu callback.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const CALLBACK = 2;
|
||||
|
||||
/**
|
||||
* Array index of menu callback.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const SLUG = 3;
|
||||
|
||||
/**
|
||||
* Array index of menu CSS class string.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const CSS_CLASSES = 4;
|
||||
|
||||
/**
|
||||
* Array of usable menu IDs.
|
||||
*/
|
||||
const MENU_IDS = array(
|
||||
'primary',
|
||||
'favorites',
|
||||
'plugins',
|
||||
'secondary',
|
||||
);
|
||||
|
||||
/**
|
||||
* Store menu items.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $menu_items = array();
|
||||
|
||||
/**
|
||||
* Store categories with menu item IDs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $categories = array(
|
||||
'woocommerce' => array(),
|
||||
);
|
||||
|
||||
/**
|
||||
* Registered callbacks or URLs with migration boolean as key value pairs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $callbacks = array();
|
||||
|
||||
/**
|
||||
* Get class instance.
|
||||
*/
|
||||
final public static function instance() {
|
||||
if ( ! static::$instance ) {
|
||||
static::$instance = new static();
|
||||
}
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*/
|
||||
public function init() {
|
||||
add_action( 'admin_menu', array( $this, 'add_core_items' ), 100 );
|
||||
add_filter( 'admin_enqueue_scripts', array( $this, 'enqueue_data' ), 20 );
|
||||
|
||||
add_filter( 'admin_menu', array( $this, 'migrate_core_child_items' ), PHP_INT_MAX - 1 );
|
||||
add_filter( 'admin_menu', array( $this, 'migrate_menu_items' ), PHP_INT_MAX - 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a WordPress menu callback to a URL.
|
||||
*
|
||||
* @param string $callback Menu callback.
|
||||
* @return string
|
||||
*/
|
||||
public static function get_callback_url( $callback ) {
|
||||
// Return the full URL.
|
||||
if ( strpos( $callback, 'http' ) === 0 ) {
|
||||
return $callback;
|
||||
}
|
||||
|
||||
$pos = strpos( $callback, '?' );
|
||||
$file = $pos > 0 ? substr( $callback, 0, $pos ) : $callback;
|
||||
if ( file_exists( ABSPATH . "/wp-admin/$file" ) ) {
|
||||
return $callback;
|
||||
}
|
||||
return 'admin.php?page=' . $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent key if one exists.
|
||||
*
|
||||
* @param string $callback Callback or URL.
|
||||
* @return string|null
|
||||
*/
|
||||
public static function get_parent_key( $callback ) {
|
||||
global $submenu;
|
||||
|
||||
if ( ! $submenu ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// This is already a parent item.
|
||||
if ( isset( $submenu[ $callback ] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ( $submenu as $key => $menu ) {
|
||||
foreach ( $menu as $item ) {
|
||||
if ( $item[ self::CALLBACK ] === $callback ) {
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a top level menu item to the navigation.
|
||||
*
|
||||
* @param array $args Array containing the necessary arguments.
|
||||
* $args = array(
|
||||
* 'id' => (string) The unique ID of the menu item. Required.
|
||||
* 'title' => (string) Title of the menu item. Required.
|
||||
* 'url' => (string) URL or callback to be used. Required.
|
||||
* 'order' => (int) Menu item order.
|
||||
* 'migrate' => (bool) Whether or not to hide the item in the wp admin menu.
|
||||
* 'menuId' => (string) The ID of the menu to add the category to.
|
||||
* ).
|
||||
*/
|
||||
private static function add_category( $args ) {
|
||||
if ( ! isset( $args['id'] ) || isset( self::$menu_items[ $args['id'] ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$defaults = array(
|
||||
'id' => '',
|
||||
'title' => '',
|
||||
'order' => 100,
|
||||
'migrate' => true,
|
||||
'menuId' => 'primary',
|
||||
'isCategory' => true,
|
||||
);
|
||||
$menu_item = wp_parse_args( $args, $defaults );
|
||||
$menu_item['title'] = wp_strip_all_tags( wp_specialchars_decode( $menu_item['title'] ) );
|
||||
unset( $menu_item['url'] );
|
||||
unset( $menu_item['capability'] );
|
||||
|
||||
if ( ! isset( $menu_item['parent'] ) ) {
|
||||
$menu_item['parent'] = 'woocommerce';
|
||||
$menu_item['backButtonLabel'] = __(
|
||||
'WooCommerce Home',
|
||||
'woocommerce'
|
||||
);
|
||||
}
|
||||
|
||||
self::$menu_items[ $menu_item['id'] ] = $menu_item;
|
||||
self::$categories[ $menu_item['id'] ] = array();
|
||||
self::$categories[ $menu_item['parent'] ][] = $menu_item['id'];
|
||||
|
||||
if ( isset( $args['url'] ) ) {
|
||||
self::$callbacks[ $args['url'] ] = $menu_item['migrate'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a child menu item to the navigation.
|
||||
*
|
||||
* @param array $args Array containing the necessary arguments.
|
||||
* $args = array(
|
||||
* 'id' => (string) The unique ID of the menu item. Required.
|
||||
* 'title' => (string) Title of the menu item. Required.
|
||||
* 'parent' => (string) Parent menu item ID.
|
||||
* 'capability' => (string) Capability to view this menu item.
|
||||
* 'url' => (string) URL or callback to be used. Required.
|
||||
* 'order' => (int) Menu item order.
|
||||
* 'migrate' => (bool) Whether or not to hide the item in the wp admin menu.
|
||||
* 'menuId' => (string) The ID of the menu to add the item to.
|
||||
* 'matchExpression' => (string) A regular expression used to identify if the menu item is active.
|
||||
* ).
|
||||
*/
|
||||
private static function add_item( $args ) {
|
||||
if ( ! isset( $args['id'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isset( self::$menu_items[ $args['id'] ] ) ) {
|
||||
error_log( // phpcs:ignore
|
||||
sprintf(
|
||||
/* translators: 1: Duplicate menu item path. */
|
||||
esc_html__( 'You have attempted to register a duplicate item with WooCommerce Navigation: %1$s', 'woocommerce' ),
|
||||
'`' . $args['id'] . '`'
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
$defaults = array(
|
||||
'id' => '',
|
||||
'title' => '',
|
||||
'capability' => 'manage_woocommerce',
|
||||
'url' => '',
|
||||
'order' => 100,
|
||||
'migrate' => true,
|
||||
'menuId' => 'primary',
|
||||
);
|
||||
$menu_item = wp_parse_args( $args, $defaults );
|
||||
$menu_item['title'] = wp_strip_all_tags( wp_specialchars_decode( $menu_item['title'] ) );
|
||||
$menu_item['url'] = self::get_callback_url( $menu_item['url'] );
|
||||
|
||||
if ( ! isset( $menu_item['parent'] ) ) {
|
||||
$menu_item['parent'] = 'woocommerce';
|
||||
}
|
||||
|
||||
$menu_item['menuId'] = self::get_item_menu_id( $menu_item );
|
||||
|
||||
self::$menu_items[ $menu_item['id'] ] = $menu_item;
|
||||
self::$categories[ $menu_item['parent'] ][] = $menu_item['id'];
|
||||
|
||||
if ( isset( $args['url'] ) ) {
|
||||
self::$callbacks[ $args['url'] ] = $menu_item['migrate'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an item's menu ID from its parent.
|
||||
*
|
||||
* @param array $item Item args.
|
||||
* @return string
|
||||
*/
|
||||
public static function get_item_menu_id( $item ) {
|
||||
$favorites = Favorites::get_all();
|
||||
if ( is_array( $favorites ) && ! empty( $favorites ) && in_array( $item['id'], $favorites, true ) ) {
|
||||
return 'favorites';
|
||||
}
|
||||
|
||||
if ( isset( $item['parent'] ) && isset( self::$menu_items[ $item['parent'] ] ) ) {
|
||||
$menu_id = self::$menu_items[ $item['parent'] ]['menuId'];
|
||||
return 'favorites' === $menu_id
|
||||
? 'plugins'
|
||||
: $menu_id;
|
||||
}
|
||||
|
||||
return $item['menuId'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a plugin category.
|
||||
*
|
||||
* @param array $args Array containing the necessary arguments.
|
||||
* $args = array(
|
||||
* 'id' => (string) The unique ID of the menu item. Required.
|
||||
* 'title' => (string) Title of the menu item. Required.
|
||||
* 'url' => (string) URL or callback to be used. Required.
|
||||
* 'migrate' => (bool) Whether or not to hide the item in the wp admin menu.
|
||||
* 'order' => (int) Menu item order.
|
||||
* ).
|
||||
*/
|
||||
public static function add_plugin_category( $args ) {
|
||||
$category_args = array_merge(
|
||||
$args,
|
||||
array(
|
||||
'menuId' => 'plugins',
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! isset( $category_args['parent'] ) ) {
|
||||
unset( $category_args['order'] );
|
||||
}
|
||||
|
||||
$menu_id = self::get_item_menu_id( $category_args );
|
||||
if ( ! in_array( $menu_id, array( 'plugins', 'favorites' ), true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$category_args['menuId'] = $menu_id;
|
||||
|
||||
self::add_category( $category_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a plugin item.
|
||||
*
|
||||
* @param array $args Array containing the necessary arguments.
|
||||
* $args = array(
|
||||
* 'id' => (string) The unique ID of the menu item. Required.
|
||||
* 'title' => (string) Title of the menu item. Required.
|
||||
* 'parent' => (string) Parent menu item ID.
|
||||
* 'capability' => (string) Capability to view this menu item.
|
||||
* 'url' => (string) URL or callback to be used. Required.
|
||||
* 'migrate' => (bool) Whether or not to hide the item in the wp admin menu.
|
||||
* 'order' => (int) Menu item order.
|
||||
* 'matchExpression' => (string) A regular expression used to identify if the menu item is active.
|
||||
* ).
|
||||
*/
|
||||
public static function add_plugin_item( $args ) {
|
||||
if ( ! isset( $args['parent'] ) ) {
|
||||
unset( $args['order'] );
|
||||
}
|
||||
|
||||
$item_args = array_merge(
|
||||
$args,
|
||||
array(
|
||||
'menuId' => 'plugins',
|
||||
)
|
||||
);
|
||||
|
||||
$menu_id = self::get_item_menu_id( $item_args );
|
||||
|
||||
if ( 'plugins' !== $menu_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::add_item( $item_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a plugin setting item.
|
||||
*
|
||||
* @param array $args Array containing the necessary arguments.
|
||||
* $args = array(
|
||||
* 'id' => (string) The unique ID of the menu item. Required.
|
||||
* 'title' => (string) Title of the menu item. Required.
|
||||
* 'capability' => (string) Capability to view this menu item.
|
||||
* 'url' => (string) URL or callback to be used. Required.
|
||||
* 'migrate' => (bool) Whether or not to hide the item in the wp admin menu.
|
||||
* ).
|
||||
*/
|
||||
public static function add_setting_item( $args ) {
|
||||
unset( $args['order'] );
|
||||
|
||||
if ( isset( $args['parent'] ) || isset( $args['menuId'] ) ) {
|
||||
error_log( // phpcs:ignore
|
||||
sprintf(
|
||||
/* translators: 1: Duplicate menu item path. */
|
||||
esc_html__( 'The item ID %1$s attempted to register using an invalid option. The arguments `menuId` and `parent` are not allowed for add_setting_item()', 'woocommerce' ),
|
||||
'`' . $args['id'] . '`'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$item_args = array_merge(
|
||||
$args,
|
||||
array(
|
||||
'menuId' => 'secondary',
|
||||
'parent' => 'woocommerce-settings',
|
||||
)
|
||||
);
|
||||
|
||||
self::add_item( $item_args );
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get menu item templates for a given post type.
|
||||
*
|
||||
* @param string $post_type Post type to add.
|
||||
* @param array $menu_args Arguments merged with the returned menu items.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_post_type_items( $post_type, $menu_args = array() ) {
|
||||
$post_type_object = get_post_type_object( $post_type );
|
||||
|
||||
if ( ! $post_type_object || ! $post_type_object->show_in_menu ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parent = isset( $menu_args['parent'] ) ? $menu_args['parent'] . '-' : '';
|
||||
$match_expression = isset( $_GET['post'] ) && get_post_type( intval( $_GET['post'] ) ) === $post_type // phpcs:ignore WordPress.Security.NonceVerification
|
||||
? '(edit.php|post.php)'
|
||||
: null;
|
||||
|
||||
return array(
|
||||
'default' => array_merge(
|
||||
array(
|
||||
'title' => esc_attr( $post_type_object->labels->menu_name ),
|
||||
'capability' => $post_type_object->cap->edit_posts,
|
||||
'id' => $parent . $post_type,
|
||||
'url' => "edit.php?post_type={$post_type}",
|
||||
'matchExpression' => $match_expression,
|
||||
),
|
||||
$menu_args
|
||||
),
|
||||
'all' => array_merge(
|
||||
array(
|
||||
'title' => esc_attr( $post_type_object->labels->all_items ),
|
||||
'capability' => $post_type_object->cap->edit_posts,
|
||||
'id' => "{$parent}{$post_type}-all-items",
|
||||
'url' => "edit.php?post_type={$post_type}",
|
||||
'order' => 10,
|
||||
'matchExpression' => $match_expression,
|
||||
),
|
||||
$menu_args
|
||||
),
|
||||
'new' => array_merge(
|
||||
array(
|
||||
'title' => esc_attr( $post_type_object->labels->add_new ),
|
||||
'capability' => $post_type_object->cap->create_posts,
|
||||
'id' => "{$parent}{$post_type}-add-new",
|
||||
'url' => "post-new.php?post_type={$post_type}",
|
||||
'order' => 20,
|
||||
),
|
||||
$menu_args
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get menu item templates for a given taxonomy.
|
||||
*
|
||||
* @param string $taxonomy Taxonomy to add.
|
||||
* @param array $menu_args Arguments merged with the returned menu items.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_taxonomy_items( $taxonomy, $menu_args = array() ) {
|
||||
$taxonomy_object = get_taxonomy( $taxonomy );
|
||||
|
||||
if ( ! $taxonomy_object || ! $taxonomy_object->show_in_menu ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parent = isset( $menu_args['parent'] ) ? $menu_args['parent'] . '-' : '';
|
||||
$product_type_query = ! empty( $taxonomy_object->object_type )
|
||||
? "&post_type={$taxonomy_object->object_type[0]}"
|
||||
: '';
|
||||
$match_expression = 'term.php'; // Match term.php pages.
|
||||
$match_expression .= "(?=.*[?|&]taxonomy=${taxonomy}(&|$|#))"; // Lookahead to match a taxonomy URL param.
|
||||
$match_expression .= '|'; // Or.
|
||||
$match_expression .= 'edit-tags.php'; // Match edit-tags.php pages.
|
||||
$match_expression .= "(?=.*[?|&]taxonomy=${taxonomy}(&|$|#))"; // Lookahead to match a taxonomy URL param.
|
||||
|
||||
return array(
|
||||
'default' => array_merge(
|
||||
array(
|
||||
'title' => esc_attr( $taxonomy_object->labels->menu_name ),
|
||||
'capability' => $taxonomy_object->cap->edit_terms,
|
||||
'id' => $parent . $taxonomy,
|
||||
'url' => "edit-tags.php?taxonomy={$taxonomy}{$product_type_query}",
|
||||
'matchExpression' => $match_expression,
|
||||
),
|
||||
$menu_args
|
||||
),
|
||||
'all' => array_merge(
|
||||
array(
|
||||
'title' => esc_attr( $taxonomy_object->labels->all_items ),
|
||||
'capability' => $taxonomy_object->cap->edit_terms,
|
||||
'id' => "{$parent}{$taxonomy}-all-items",
|
||||
'url' => "edit-tags.php?taxonomy={$taxonomy}{$product_type_query}",
|
||||
'matchExpression' => $match_expression,
|
||||
'order' => 10,
|
||||
),
|
||||
$menu_args
|
||||
),
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add core menu items.
|
||||
*/
|
||||
public function add_core_items() {
|
||||
$categories = CoreMenu::get_categories();
|
||||
foreach ( $categories as $category ) {
|
||||
self::add_category( $category );
|
||||
}
|
||||
|
||||
$items = CoreMenu::get_items();
|
||||
foreach ( $items as $item ) {
|
||||
if ( isset( $item['is_category'] ) && $item['is_category'] ) {
|
||||
self::add_category( $item );
|
||||
} else {
|
||||
self::add_item( $item );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an item or taxonomy.
|
||||
*
|
||||
* @param array $menu_item Menu item.
|
||||
*/
|
||||
public function add_item_and_taxonomy( $menu_item ) {
|
||||
if ( in_array( $menu_item[2], CoreMenu::get_excluded_items(), true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$menu_item[2] = htmlspecialchars_decode( $menu_item[2] );
|
||||
|
||||
// Don't add already added items.
|
||||
$callbacks = self::get_callbacks();
|
||||
if ( array_key_exists( $menu_item[2], $callbacks ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't add these Product submenus because they are added elsewhere.
|
||||
if ( in_array( $menu_item[2], array( 'product_importer', 'product_exporter', 'product_attributes' ), true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::add_plugin_item(
|
||||
array(
|
||||
'title' => $menu_item[0],
|
||||
'capability' => $menu_item[1],
|
||||
'id' => sanitize_title( $menu_item[0] ),
|
||||
'url' => $menu_item[2],
|
||||
)
|
||||
);
|
||||
|
||||
// Determine if migrated items are a taxonomy or post_type. If they are, register them.
|
||||
$parsed_url = wp_parse_url( $menu_item[2] );
|
||||
$query_string = isset( $parsed_url['query'] ) ? $parsed_url['query'] : false;
|
||||
|
||||
if ( $query_string ) {
|
||||
$query = array();
|
||||
parse_str( $query_string, $query );
|
||||
|
||||
if ( isset( $query['taxonomy'] ) ) {
|
||||
Screen::register_taxonomy( $query['taxonomy'] );
|
||||
} elseif ( isset( $query['post_type'] ) ) {
|
||||
Screen::register_post_type( $query['post_type'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate any remaining WooCommerce child items.
|
||||
*
|
||||
* @param array $menu Menu items.
|
||||
* @return array
|
||||
*/
|
||||
public function migrate_core_child_items( $menu ) {
|
||||
global $submenu;
|
||||
|
||||
if ( ! isset( $submenu['woocommerce'] ) && ! isset( $submenu['edit.php?post_type=product'] ) ) {
|
||||
return $menu;
|
||||
}
|
||||
|
||||
$main_items = isset( $submenu['woocommerce'] ) ? $submenu['woocommerce'] : array();
|
||||
$product_items = isset( $submenu['edit.php?post_type=product'] ) ? $submenu['edit.php?post_type=product'] : array();
|
||||
|
||||
foreach ( $main_items as $key => $menu_item ) {
|
||||
self::add_item_and_taxonomy( $menu_item );
|
||||
// phpcs:disable
|
||||
if ( ! isset( $menu_item[ self::CSS_CLASSES ] ) ) {
|
||||
$submenu['woocommerce'][ $key ][] .= ' hide-if-js';
|
||||
} else if ( strpos( $submenu['woocommerce'][ $key ][ self::CSS_CLASSES ], 'hide-if-js' ) !== false ) {
|
||||
continue;
|
||||
} else {
|
||||
$submenu['woocommerce'][ $key ][ self::CSS_CLASSES ] .= ' hide-if-js';
|
||||
}
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
foreach ( $product_items as $key => $menu_item ) {
|
||||
self::add_item_and_taxonomy( $menu_item );
|
||||
}
|
||||
|
||||
return $menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a menu item's callback is registered in the menu.
|
||||
*
|
||||
* @param array $menu_item Menu item args.
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_callback( $menu_item ) {
|
||||
if ( ! $menu_item || ! isset( $menu_item[ self::CALLBACK ] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$callback = $menu_item[ self::CALLBACK ];
|
||||
|
||||
if (
|
||||
isset( self::$callbacks[ $callback ] ) &&
|
||||
self::$callbacks[ $callback ]
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
isset( self::$callbacks[ self::get_callback_url( $callback ) ] ) &&
|
||||
self::$callbacks[ self::get_callback_url( $callback ) ]
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides all WP admin menus items and adds screen IDs to check for new items.
|
||||
*/
|
||||
public static function migrate_menu_items() {
|
||||
global $menu, $submenu;
|
||||
|
||||
foreach ( $menu as $key => $menu_item ) {
|
||||
if ( self::has_callback( $menu_item ) ) {
|
||||
// Disable phpcs since we need to override submenu classes.
|
||||
// Note that `phpcs:ignore WordPress.Variables.GlobalVariables.OverrideProhibited` does not work to disable this check.
|
||||
// phpcs:disable
|
||||
$menu[ $key ][ self::CSS_CLASSES ] .= ' hide-if-js';
|
||||
// phps:enable
|
||||
continue;
|
||||
}
|
||||
|
||||
// WordPress core menus make the parent item the same URL as the first child.
|
||||
$has_children = isset( $submenu[ $menu_item[ self::CALLBACK ] ] ) && isset( $submenu[ $menu_item[ self::CALLBACK ] ][0] );
|
||||
$first_child = $has_children ? $submenu[ $menu_item[ self::CALLBACK ] ][0] : null;
|
||||
if ( 'woocommerce' !== $menu_item[2] && self::has_callback( $first_child ) ) {
|
||||
// Disable phpcs since we need to override submenu classes.
|
||||
// Note that `phpcs:ignore WordPress.Variables.GlobalVariables.OverrideProhibited` does not work to disable this check.
|
||||
// phpcs:disable
|
||||
$menu[ $key ][ self::CSS_CLASSES ] .= ' hide-if-js';
|
||||
// phps:enable
|
||||
}
|
||||
}
|
||||
|
||||
// Remove excluded submenu items
|
||||
if ( isset( $submenu['woocommerce'] ) ) {
|
||||
foreach ( $submenu['woocommerce'] as $key => $submenu_item ) {
|
||||
if ( in_array( $submenu_item[ self::CALLBACK ], CoreMenu::get_excluded_items(), true ) ) {
|
||||
if ( isset( $submenu['woocommerce'][ $key ][ self::CSS_CLASSES ] ) ) {
|
||||
$submenu['woocommerce'][ $key ][ self::CSS_CLASSES ] .= ' hide-if-js';
|
||||
} else {
|
||||
$submenu['woocommerce'][ $key ][] = 'hide-if-js';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $submenu as $parent_key => $parent ) {
|
||||
foreach ( $parent as $key => $menu_item ) {
|
||||
if ( self::has_callback( $menu_item ) ) {
|
||||
// Disable phpcs since we need to override submenu classes.
|
||||
// Note that `phpcs:ignore WordPress.Variables.GlobalVariables.OverrideProhibited` does not work to disable this check.
|
||||
// phpcs:disable
|
||||
if ( ! isset( $menu_item[ self::SLUG ] ) ) {
|
||||
$submenu[ $parent_key ][ $key ][] = '';
|
||||
}
|
||||
if ( ! isset( $menu_item[ self::CSS_CLASSES ] ) ) {
|
||||
$submenu[ $parent_key ][ $key ][] .= ' hide-if-js';
|
||||
} else {
|
||||
$submenu[ $parent_key ][ $key ][ self::CSS_CLASSES ] .= ' hide-if-js';
|
||||
}
|
||||
// phps:enable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( array_keys( self::$callbacks ) as $callback ) {
|
||||
Screen::add_screen( $callback );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback to identify and hide pages in the WP menu.
|
||||
*/
|
||||
public static function hide_wp_menu_item( $callback ) {
|
||||
self::$callbacks[ $callback ] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get registered menu items.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_items() {
|
||||
return apply_filters( 'woocommerce_navigation_menu_items', self::$menu_items );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get registered menu items.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_category_items( $category ) {
|
||||
if ( ! isset( self::$categories[ $category ] ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$menu_item_ids = self::$categories[ $category ];
|
||||
|
||||
$category_menu_items = array();
|
||||
foreach ( $menu_item_ids as $id ) {
|
||||
if ( isset( self::$menu_items[ $id ] ) ) {
|
||||
$category_menu_items[] = self::$menu_items[ $id ];
|
||||
}
|
||||
}
|
||||
|
||||
return apply_filters( 'woocommerce_navigation_menu_category_items', $category_menu_items );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get registered callbacks.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_callbacks() {
|
||||
return apply_filters( 'woocommerce_navigation_callbacks', self::$callbacks );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the menu item data mapped by category and menu ID.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_mapped_menu_items() {
|
||||
$menu_items = self::get_items();
|
||||
$mapped_items = array();
|
||||
|
||||
// Sort the items by order and title.
|
||||
$order = array_column( $menu_items, 'order' );
|
||||
$title = array_column( $menu_items, 'title' );
|
||||
array_multisort( $order, SORT_ASC, $title, SORT_ASC, $menu_items );
|
||||
|
||||
foreach ( $menu_items as $id => $menu_item ) {
|
||||
$category_id = $menu_item[ 'parent' ];
|
||||
$menu_id = $menu_item[ 'menuId' ];
|
||||
if ( ! isset( $mapped_items[ $category_id ] ) ) {
|
||||
$mapped_items[ $category_id ] = array();
|
||||
foreach ( self::MENU_IDS as $available_menu_id ) {
|
||||
$mapped_items[ $category_id ][ $available_menu_id ] = array();
|
||||
}
|
||||
}
|
||||
|
||||
// Incorrect menu ID.
|
||||
if ( ! isset( $mapped_items[ $category_id ][ $menu_id ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove the item if the user cannot access it.
|
||||
if ( isset( $menu_item[ 'capability' ] ) && ! current_user_can( $menu_item[ 'capability' ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mapped_items[ $category_id ][ $menu_id ][] = $menu_item;
|
||||
}
|
||||
|
||||
return $mapped_items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the menu to the page output.
|
||||
*
|
||||
* @param array $menu Menu items.
|
||||
* @return array
|
||||
*/
|
||||
public function enqueue_data( $menu ) {
|
||||
$data = array(
|
||||
'menuItems' => array_values( self::get_items() ),
|
||||
'rootBackUrl' => get_dashboard_url(),
|
||||
);
|
||||
|
||||
wp_add_inline_script( WC_ADMIN_APP, 'window.wcNavigation = ' . wp_json_encode( $data ), 'before' );
|
||||
}
|
||||
}
|
240
packages/woocommerce-admin/src/Features/Navigation/Screen.php
Normal file
240
packages/woocommerce-admin/src/Features/Navigation/Screen.php
Normal file
@ -0,0 +1,240 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Navigation Screen
|
||||
*
|
||||
* @package Woocommerce Navigation
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\Navigation;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Navigation\Menu;
|
||||
|
||||
/**
|
||||
* Contains logic for the WooCommerce Navigation menu.
|
||||
*/
|
||||
class Screen {
|
||||
/**
|
||||
* Class instance.
|
||||
*
|
||||
* @var Screen instance
|
||||
*/
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* Screen IDs of registered pages.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $screen_ids = array();
|
||||
|
||||
/**
|
||||
* Registered post types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $post_types = array();
|
||||
|
||||
/**
|
||||
* Registered taxonomies.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $taxonomies = array();
|
||||
|
||||
/**
|
||||
* Get class instance.
|
||||
*/
|
||||
final public static function instance() {
|
||||
if ( ! static::$instance ) {
|
||||
static::$instance = new static();
|
||||
}
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*/
|
||||
public function init() {
|
||||
add_filter( 'admin_body_class', array( $this, 'add_body_class' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of filtered screen ids.
|
||||
*/
|
||||
public static function get_screen_ids() {
|
||||
return apply_filters( 'woocommerce_navigation_screen_ids', self::$screen_ids );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of registered post types.
|
||||
*/
|
||||
public static function get_post_types() {
|
||||
return apply_filters( 'woocommerce_navigation_post_types', self::$post_types );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of registered post types.
|
||||
*/
|
||||
public static function get_taxonomies() {
|
||||
return apply_filters( 'woocommerce_navigation_taxonomies', self::$taxonomies );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we're on a WooCommerce page
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_woocommerce_page() {
|
||||
global $pagenow;
|
||||
|
||||
// Get taxonomy if on a taxonomy screen.
|
||||
$taxonomy = '';
|
||||
if ( in_array( $pagenow, array( 'edit-tags.php', 'term.php' ), true ) ) {
|
||||
if ( isset( $_GET['taxonomy'] ) ) { // phpcs:ignore CSRF ok.
|
||||
$taxonomy = sanitize_text_field( wp_unslash( $_GET['taxonomy'] ) ); // phpcs:ignore CSRF ok.
|
||||
}
|
||||
}
|
||||
$taxonomies = self::get_taxonomies();
|
||||
|
||||
// Get post type if on a post screen.
|
||||
$post_type = '';
|
||||
if ( in_array( $pagenow, array( 'edit.php', 'post.php', 'post-new.php' ), true ) ) {
|
||||
if ( isset( $_GET['post'] ) ) { // phpcs:ignore CSRF ok.
|
||||
$post_type = get_post_type( (int) $_GET['post'] ); // phpcs:ignore CSRF ok.
|
||||
} elseif ( isset( $_GET['post_type'] ) ) { // phpcs:ignore CSRF ok.
|
||||
$post_type = sanitize_text_field( wp_unslash( $_GET['post_type'] ) ); // phpcs:ignore CSRF ok.
|
||||
}
|
||||
}
|
||||
$post_types = self::get_post_types();
|
||||
|
||||
// Get current screen ID.
|
||||
$current_screen = get_current_screen();
|
||||
$screen_ids = self::get_screen_ids();
|
||||
$current_screen_id = $current_screen ? $current_screen->id : null;
|
||||
|
||||
if (
|
||||
in_array( $post_type, $post_types, true ) ||
|
||||
in_array( $taxonomy, $taxonomies, true ) ||
|
||||
self::is_woocommerce_core_taxonomy( $taxonomy ) ||
|
||||
in_array( $current_screen_id, $screen_ids, true )
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given taxonomy is a WooCommerce core related taxonomy.
|
||||
*
|
||||
* @param string $taxonomy Taxonomy.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_woocommerce_core_taxonomy( $taxonomy ) {
|
||||
if ( in_array( $taxonomy, array( 'product_cat', 'product_tag' ), true ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( 'pa_' === substr( $taxonomy, 0, 3 ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add navigation classes to body.
|
||||
*
|
||||
* @param string $classes Classes.
|
||||
* @return string
|
||||
*/
|
||||
public function add_body_class( $classes ) {
|
||||
if ( self::is_woocommerce_page() ) {
|
||||
$classes .= ' has-woocommerce-navigation';
|
||||
|
||||
/**
|
||||
* Adds the ability to skip disabling of the WP toolbar.
|
||||
*
|
||||
* @param boolean $bool WP Toolbar disabled.
|
||||
*/
|
||||
if ( apply_filters( 'woocommerce_navigation_wp_toolbar_disabled', true ) ) {
|
||||
$classes .= ' is-wp-toolbar-disabled';
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a screen ID to the list of screens that use the navigtion.
|
||||
* Finds the parent if none is given to grab the correct screen ID.
|
||||
*
|
||||
* @param string $callback Callback or URL for page.
|
||||
* @param string|null $parent Parent screen ID.
|
||||
*/
|
||||
public static function add_screen( $callback, $parent = null ) {
|
||||
global $submenu;
|
||||
|
||||
$plugin_page = self::get_plugin_page( $callback );
|
||||
|
||||
if ( ! $parent ) {
|
||||
$parent = Menu::get_parent_key( $callback );
|
||||
}
|
||||
|
||||
$screen_id = get_plugin_page_hookname( $plugin_page, $parent );
|
||||
|
||||
// This screen has already been added.
|
||||
if ( in_array( $screen_id, self::$screen_ids, true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$screen_ids[] = $screen_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plugin page slug.
|
||||
*
|
||||
* @param string $callback Callback.
|
||||
* @return string
|
||||
*/
|
||||
public static function get_plugin_page( $callback ) {
|
||||
$url = Menu::get_callback_url( $callback );
|
||||
$parts = wp_parse_url( $url );
|
||||
|
||||
if ( ! isset( $parts['query'] ) ) {
|
||||
return $callback;
|
||||
}
|
||||
|
||||
parse_str( $parts['query'], $query );
|
||||
|
||||
if ( ! isset( $query['page'] ) ) {
|
||||
return $callback;
|
||||
}
|
||||
|
||||
$plugin_page = wp_unslash( $query['page'] );
|
||||
$plugin_page = plugin_basename( $plugin_page );
|
||||
return $plugin_page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register post type for use in WooCommerce Navigation screens.
|
||||
*
|
||||
* @param string $post_type Post type to add.
|
||||
*/
|
||||
public static function register_post_type( $post_type ) {
|
||||
if ( ! in_array( $post_type, self::$post_types, true ) ) {
|
||||
self::$post_types[] = $post_type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register taxonomy for use in WooCommerce Navigation screens.
|
||||
*
|
||||
* @param string $taxonomy Taxonomy to add.
|
||||
*/
|
||||
public static function register_taxonomy( $taxonomy ) {
|
||||
if ( ! in_array( $taxonomy, self::$taxonomies, true ) ) {
|
||||
self::$taxonomies[] = $taxonomy;
|
||||
}
|
||||
}
|
||||
}
|
1031
packages/woocommerce-admin/src/Features/Onboarding.php
Normal file
1031
packages/woocommerce-admin/src/Features/Onboarding.php
Normal file
File diff suppressed because it is too large
Load Diff
446
packages/woocommerce-admin/src/Features/OnboardingTasks/Init.php
Normal file
446
packages/woocommerce-admin/src/Features/OnboardingTasks/Init.php
Normal file
@ -0,0 +1,446 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Onboarding Tasks
|
||||
* NOTE: DO NOT edit this file in WooCommerce core, this is generated from woocommerce-admin.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks;
|
||||
|
||||
use \Automattic\WooCommerce\Admin\Loader;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Taxes\Stats\DataStore as TaxDataStore;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\Tax;
|
||||
|
||||
/**
|
||||
* Contains the logic for completing onboarding tasks.
|
||||
*/
|
||||
class Init {
|
||||
/**
|
||||
* Class instance.
|
||||
*
|
||||
* @var OnboardingTasks instance
|
||||
*/
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* Name of the active task transient.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ACTIVE_TASK_TRANSIENT = 'wc_onboarding_active_task';
|
||||
|
||||
/**
|
||||
* Get class instance.
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( ! self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
// This hook needs to run when options are updated via REST.
|
||||
add_action( 'add_option_woocommerce_task_list_complete', array( $this, 'track_completion' ), 10, 2 );
|
||||
add_action( 'add_option_woocommerce_extended_task_list_complete', array( $this, 'track_extended_completion' ), 10, 2 );
|
||||
add_action( 'add_option_woocommerce_task_list_tracked_completed_tasks', array( $this, 'track_task_completion' ), 10, 2 );
|
||||
add_action( 'update_option_woocommerce_task_list_tracked_completed_tasks', array( $this, 'track_task_completion' ), 10, 2 );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'update_option_extended_task_list' ), 15 );
|
||||
add_filter( 'pre_option_woocommerce_task_list_hidden', array( $this, 'get_deprecated_options' ), 10, 2 );
|
||||
add_filter( 'pre_option_woocommerce_extended_task_list_hidden', array( $this, 'get_deprecated_options' ), 10, 2 );
|
||||
add_action( 'pre_update_option_woocommerce_task_list_hidden', array( $this, 'update_deprecated_options' ), 10, 3 );
|
||||
add_action( 'pre_update_option_woocommerce_extended_task_list_hidden', array( $this, 'update_deprecated_options' ), 10, 3 );
|
||||
TaskLists::init();
|
||||
|
||||
if ( ! is_admin() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'add_media_scripts' ) );
|
||||
// Old settings injection.
|
||||
// Run after Onboarding.
|
||||
add_filter( 'woocommerce_components_settings', array( __CLASS__, 'component_settings' ), 30 );
|
||||
// New settings injection.
|
||||
add_filter( 'woocommerce_admin_shared_settings', array( $this, 'component_settings' ), 30 );
|
||||
|
||||
add_action( 'admin_init', array( $this, 'set_active_task' ), 5 );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'add_onboarding_product_notice_admin_script' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'add_onboarding_homepage_notice_admin_script' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'add_onboarding_tax_notice_admin_script' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'add_onboarding_product_import_notice_admin_script' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue scripts and styles.
|
||||
*/
|
||||
public function add_media_scripts() {
|
||||
wp_enqueue_media();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get task item data for settings filter.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_settings() {
|
||||
$settings = array();
|
||||
$wc_pay_is_connected = false;
|
||||
if ( class_exists( '\WC_Payments' ) ) {
|
||||
$wc_payments_gateway = \WC_Payments::get_gateway();
|
||||
$wc_pay_is_connected = method_exists( $wc_payments_gateway, 'is_connected' )
|
||||
? $wc_payments_gateway->is_connected()
|
||||
: false;
|
||||
}
|
||||
|
||||
$gateways = WC()->payment_gateways->get_available_payment_gateways();
|
||||
$enabled_gateways = array_filter(
|
||||
$gateways,
|
||||
function( $gateway ) {
|
||||
return 'yes' === $gateway->enabled;
|
||||
}
|
||||
);
|
||||
|
||||
// @todo We may want to consider caching some of these and use to check against
|
||||
// task completion along with cache busting for active tasks.
|
||||
$settings['automatedTaxSupportedCountries'] = Tax::get_automated_tax_supported_countries();
|
||||
$settings['hasHomepage'] = self::check_task_completion( 'homepage' ) || 'classic' === get_option( 'classic-editor-replace' );
|
||||
$settings['hasPaymentGateway'] = ! empty( $enabled_gateways );
|
||||
$settings['enabledPaymentGateways'] = array_keys( $enabled_gateways );
|
||||
$settings['hasPhysicalProducts'] = count(
|
||||
wc_get_products(
|
||||
array(
|
||||
'virtual' => false,
|
||||
'limit' => 1,
|
||||
)
|
||||
)
|
||||
) > 0;
|
||||
$settings['hasProducts'] = self::check_task_completion( 'products' );
|
||||
$settings['isAppearanceComplete'] = get_option( 'woocommerce_task_list_appearance_complete' );
|
||||
$settings['isTaxComplete'] = self::check_task_completion( 'tax' );
|
||||
$settings['shippingZonesCount'] = count( \WC_Shipping_Zones::get_zones() );
|
||||
$settings['stylesheet'] = get_option( 'stylesheet' );
|
||||
$settings['taxJarActivated'] = class_exists( 'WC_Taxjar' );
|
||||
$settings['themeMods'] = get_theme_mods();
|
||||
$settings['wcPayIsConnected'] = $wc_pay_is_connected;
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add task items to component settings.
|
||||
*
|
||||
* @param array $settings Component settings.
|
||||
* @return array
|
||||
*/
|
||||
public function component_settings( $settings ) {
|
||||
// Bail early if not on a wc-admin powered page, or task list shouldn't be shown.
|
||||
if (
|
||||
! \Automattic\WooCommerce\Admin\Loader::is_admin_page() ||
|
||||
! count( TaskLists::get_visible() )
|
||||
) {
|
||||
return $settings;
|
||||
}
|
||||
|
||||
// If onboarding isn't enabled this will throw warnings.
|
||||
if ( ! isset( $settings['onboarding'] ) ) {
|
||||
$settings['onboarding'] = array();
|
||||
}
|
||||
|
||||
$settings['onboarding'] = array_merge(
|
||||
$settings['onboarding'],
|
||||
array(
|
||||
'tasksStatus' => self::get_settings(),
|
||||
)
|
||||
);
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily store the active task to persist across page loads when neccessary (such as publishing a product). Most tasks do not need to do this.
|
||||
*/
|
||||
public static function set_active_task() {
|
||||
if ( isset( $_GET[ self::ACTIVE_TASK_TRANSIENT ] ) ) { // phpcs:ignore csrf ok.
|
||||
$task = sanitize_title_with_dashes( wp_unslash( $_GET[ self::ACTIVE_TASK_TRANSIENT ] ) ); // phpcs:ignore csrf ok.
|
||||
|
||||
if ( self::check_task_completion( $task ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
set_transient(
|
||||
self::ACTIVE_TASK_TRANSIENT,
|
||||
$task,
|
||||
DAY_IN_SECONDS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the active task.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_active_task() {
|
||||
return get_transient( self::ACTIVE_TASK_TRANSIENT );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for active task completion, and clears the transient.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_active_task_complete() {
|
||||
$active_task = self::get_active_task();
|
||||
|
||||
if ( ! $active_task ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( self::check_task_completion( $active_task ) ) {
|
||||
delete_transient( self::ACTIVE_TASK_TRANSIENT );
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for task completion of a given task.
|
||||
*
|
||||
* @param string $task Name of task.
|
||||
* @return bool
|
||||
*/
|
||||
public static function check_task_completion( $task ) {
|
||||
switch ( $task ) {
|
||||
case 'products':
|
||||
$products = wp_count_posts( 'product' );
|
||||
return (int) $products->publish > 0;
|
||||
case 'homepage':
|
||||
$homepage_id = get_option( 'woocommerce_onboarding_homepage_post_id', false );
|
||||
if ( ! $homepage_id ) {
|
||||
return false;
|
||||
}
|
||||
$post = get_post( $homepage_id );
|
||||
$completed = $post && 'publish' === $post->post_status;
|
||||
return $completed;
|
||||
case 'tax':
|
||||
return 'yes' === get_option( 'wc_connect_taxes_enabled' ) ||
|
||||
count( TaxDataStore::get_taxes( array() ) ) > 0 ||
|
||||
false !== get_option( 'woocommerce_no_sales_tax' );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks into the product page to add a notice to return to the task list if a product was added.
|
||||
*
|
||||
* @param string $hook Page hook.
|
||||
*/
|
||||
public static function add_onboarding_product_notice_admin_script( $hook ) {
|
||||
global $post;
|
||||
if (
|
||||
'post.php' !== $hook ||
|
||||
'product' !== $post->post_type ||
|
||||
'products' !== self::get_active_task() ||
|
||||
! self::is_active_task_complete()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$script_assets_filename = Loader::get_script_asset_filename( 'wp-admin-scripts', 'onboarding-product-notice' );
|
||||
$script_assets = require WC_ADMIN_ABSPATH . WC_ADMIN_DIST_JS_FOLDER . 'wp-admin-scripts/' . $script_assets_filename;
|
||||
|
||||
wp_enqueue_script(
|
||||
'onboarding-product-notice',
|
||||
Loader::get_url( 'wp-admin-scripts/onboarding-product-notice', 'js' ),
|
||||
array_merge( array( WC_ADMIN_APP ), $script_assets ['dependencies'] ),
|
||||
WC_ADMIN_VERSION_NUMBER,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks into the post page to display a different success notice and sets the active page as the site's home page if visted from onboarding.
|
||||
*
|
||||
* @param string $hook Page hook.
|
||||
*/
|
||||
public static function add_onboarding_homepage_notice_admin_script( $hook ) {
|
||||
global $post;
|
||||
if ( 'post.php' === $hook && 'page' === $post->post_type && isset( $_GET[ self::ACTIVE_TASK_TRANSIENT ] ) && 'homepage' === $_GET[ self::ACTIVE_TASK_TRANSIENT ] ) { // phpcs:ignore csrf ok.
|
||||
$script_assets_filename = Loader::get_script_asset_filename( 'wp-admin-scripts', 'onboarding-homepage-notice' );
|
||||
$script_assets = require WC_ADMIN_ABSPATH . WC_ADMIN_DIST_JS_FOLDER . 'wp-admin-scripts/' . $script_assets_filename;
|
||||
|
||||
wp_enqueue_script(
|
||||
'onboarding-homepage-notice',
|
||||
Loader::get_url( 'wp-admin-scripts/onboarding-homepage-notice', 'js' ),
|
||||
array_merge( array( WC_ADMIN_APP ), $script_assets ['dependencies'] ),
|
||||
WC_ADMIN_VERSION_NUMBER,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a notice to return to the task list when the save button is clicked on tax settings pages.
|
||||
*/
|
||||
public static function add_onboarding_tax_notice_admin_script() {
|
||||
$page = isset( $_GET['page'] ) ? $_GET['page'] : ''; // phpcs:ignore csrf ok, sanitization ok.
|
||||
$tab = isset( $_GET['tab'] ) ? $_GET['tab'] : ''; // phpcs:ignore csrf ok, sanitization ok.
|
||||
|
||||
if (
|
||||
'wc-settings' === $page &&
|
||||
'tax' === $tab &&
|
||||
'tax' === self::get_active_task() &&
|
||||
! self::is_active_task_complete()
|
||||
) {
|
||||
$script_assets_filename = Loader::get_script_asset_filename( 'wp-admin-scripts', 'onboarding-tax-notice' );
|
||||
$script_assets = require WC_ADMIN_ABSPATH . WC_ADMIN_DIST_JS_FOLDER . 'wp-admin-scripts/' . $script_assets_filename;
|
||||
|
||||
wp_enqueue_script(
|
||||
'onboarding-tax-notice',
|
||||
Loader::get_url( 'wp-admin-scripts/onboarding-tax-notice', 'js' ),
|
||||
array_merge( array( WC_ADMIN_APP ), $script_assets ['dependencies'] ),
|
||||
WC_ADMIN_VERSION_NUMBER,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a notice to return to the task list when the product importeris done running.
|
||||
*
|
||||
* @param string $hook Page hook.
|
||||
*/
|
||||
public function add_onboarding_product_import_notice_admin_script( $hook ) {
|
||||
$step = isset( $_GET['step'] ) ? $_GET['step'] : ''; // phpcs:ignore csrf ok, sanitization ok.
|
||||
if ( 'product_page_product_importer' === $hook && 'done' === $step && 'product-import' === self::get_active_task() ) {
|
||||
delete_transient( self::ACTIVE_TASK_TRANSIENT );
|
||||
|
||||
$script_assets_filename = Loader::get_script_asset_filename( 'wp-admin-scripts', 'onboarding-product-import-notice' );
|
||||
$script_assets = require WC_ADMIN_ABSPATH . WC_ADMIN_DIST_JS_FOLDER . 'wp-admin-scripts/' . $script_assets_filename;
|
||||
|
||||
wp_enqueue_script(
|
||||
'onboarding-product-import-notice',
|
||||
Loader::get_url( 'wp-admin-scripts/onboarding-product-import-notice', 'js' ),
|
||||
array_merge( array( WC_ADMIN_APP ), $script_assets ['dependencies'] ),
|
||||
WC_ADMIN_VERSION_NUMBER,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Records an event when all tasks are completed in the task list.
|
||||
*
|
||||
* @param mixed $old_value Old value.
|
||||
* @param mixed $new_value New value.
|
||||
*/
|
||||
public static function track_completion( $old_value, $new_value ) {
|
||||
if ( $new_value ) {
|
||||
wc_admin_record_tracks_event( 'tasklist_tasks_completed' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Records an event when all tasks are completed in the extended task list.
|
||||
*
|
||||
* @param mixed $old_value Old value.
|
||||
* @param mixed $new_value New value.
|
||||
*/
|
||||
public static function track_extended_completion( $old_value, $new_value ) {
|
||||
if ( $new_value ) {
|
||||
wc_admin_record_tracks_event( 'extended_tasklist_tasks_completed' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Records an event for individual task completion.
|
||||
*
|
||||
* @param mixed $old_value Old value.
|
||||
* @param mixed $new_value New value.
|
||||
*/
|
||||
public static function track_task_completion( $old_value, $new_value ) {
|
||||
$old_value = is_array( $old_value ) ? $old_value : array();
|
||||
$new_value = is_array( $new_value ) ? $new_value : array();
|
||||
$untracked_tasks = array_diff( $new_value, $old_value );
|
||||
|
||||
foreach ( $untracked_tasks as $task ) {
|
||||
wc_admin_record_tracks_event( 'tasklist_task_completed', array( 'task_name' => $task ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update registered extended task list items.
|
||||
*/
|
||||
public static function update_option_extended_task_list() {
|
||||
if (
|
||||
! \Automattic\WooCommerce\Admin\Loader::is_admin_page() ||
|
||||
! count( TaskLists::get_visible() )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
$extended_tasks_list_items = get_option( 'woocommerce_extended_task_list_items', array() );
|
||||
$registered_extended_tasks_list_items = apply_filters( 'woocommerce_get_registered_extended_tasks', array() );
|
||||
if ( $registered_extended_tasks_list_items !== $extended_tasks_list_items ) {
|
||||
update_option( 'woocommerce_extended_task_list_items', $registered_extended_tasks_list_items );
|
||||
$extended_list = TaskLists::get_list( 'extended' );
|
||||
if ( ! $extended_list ) {
|
||||
return;
|
||||
}
|
||||
$extended_list->show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the values from the correct source when attempting to retrieve deprecated options.
|
||||
*
|
||||
* @param string $pre_option Pre option value.
|
||||
* @param string $option Option name.
|
||||
* @return string
|
||||
*/
|
||||
public function get_deprecated_options( $pre_option, $option ) {
|
||||
if ( 'yes' === get_transient( 'wc_admin_installing' ) ) {
|
||||
return $pre_option;
|
||||
};
|
||||
|
||||
$hidden = get_option( 'woocommerce_task_list_hidden_lists', array() );
|
||||
switch ( $option ) {
|
||||
case 'woocommerce_task_list_hidden':
|
||||
return in_array( 'setup', $hidden, true ) ? 'yes' : 'no';
|
||||
case 'woocommerce_extended_task_list_hidden':
|
||||
return in_array( 'extended', $hidden, true ) ? 'yes' : 'no';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the new option names when deprecated options are updated.
|
||||
* This is a temporary fallback until we can fully remove the old task list components.
|
||||
*
|
||||
* @param string $value New value.
|
||||
* @param string $old_value Old value.
|
||||
* @param string $option Option name.
|
||||
* @return string
|
||||
*/
|
||||
public function update_deprecated_options( $value, $old_value, $option ) {
|
||||
switch ( $option ) {
|
||||
case 'woocommerce_task_list_hidden':
|
||||
$task_list = TaskLists::get_list( 'setup' );
|
||||
if ( ! $task_list ) {
|
||||
return;
|
||||
}
|
||||
$update = 'yes' === $value ? $task_list->hide() : $task_list->show();
|
||||
delete_option( 'woocommerce_task_list_hidden' );
|
||||
return false;
|
||||
case 'woocommerce_extended_task_list_hidden':
|
||||
$task_list = TaskLists::get_list( 'extended' );
|
||||
if ( ! $task_list ) {
|
||||
return;
|
||||
}
|
||||
$update = 'yes' === $value ? $task_list->hide() : $task_list->show();
|
||||
delete_option( 'woocommerce_extended_task_list_hidden' );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
296
packages/woocommerce-admin/src/Features/OnboardingTasks/Task.php
Normal file
296
packages/woocommerce-admin/src/Features/OnboardingTasks/Task.php
Normal file
@ -0,0 +1,296 @@
|
||||
<?php
|
||||
/**
|
||||
* Handles task related methods.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks;
|
||||
|
||||
/**
|
||||
* Task class.
|
||||
*/
|
||||
class Task {
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id = '';
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $title = '';
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $content = '';
|
||||
|
||||
/**
|
||||
* Action label.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $action_label = '';
|
||||
|
||||
/**
|
||||
* Action URL.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $action_url = null;
|
||||
|
||||
/**
|
||||
* Task completion.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $is_complete = false;
|
||||
|
||||
/**
|
||||
* Viewing capability.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $can_view = true;
|
||||
|
||||
/**
|
||||
* Time string.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $time = null;
|
||||
|
||||
/**
|
||||
* Dismissability.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $is_dismissable = false;
|
||||
|
||||
/**
|
||||
* Snoozeability.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $is_snoozeable = false;
|
||||
|
||||
/**
|
||||
* Snoozeability.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $snoozed_until = null;
|
||||
|
||||
/**
|
||||
* Name of the dismiss option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DISMISSED_OPTION = 'woocommerce_task_list_dismissed_tasks';
|
||||
|
||||
/**
|
||||
* Name of the snooze option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SNOOZED_OPTION = 'woocommerce_task_list_remind_me_later_tasks';
|
||||
|
||||
/**
|
||||
* Duration to milisecond mapping.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $duration_to_ms = array(
|
||||
'day' => DAY_IN_SECONDS * 1000,
|
||||
'hour' => HOUR_IN_SECONDS * 1000,
|
||||
'week' => WEEK_IN_SECONDS * 1000,
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $data Task list data.
|
||||
*/
|
||||
public function __construct( $data = array() ) {
|
||||
$defaults = array(
|
||||
'id' => null,
|
||||
'title' => '',
|
||||
'content' => '',
|
||||
'action_label' => __( "Let's go", 'woocommerce' ),
|
||||
'action_url' => null,
|
||||
'is_complete' => false,
|
||||
'can_view' => true,
|
||||
'time' => null,
|
||||
'is_dismissable' => false,
|
||||
'is_snoozeable' => false,
|
||||
'snoozed_until' => null,
|
||||
);
|
||||
|
||||
$data = wp_parse_args( $data, $defaults );
|
||||
|
||||
$this->id = (string) $data['id'];
|
||||
$this->title = (string) $data['title'];
|
||||
$this->content = (string) $data['content'];
|
||||
$this->action_label = (string) $data['action_label'];
|
||||
$this->action_url = (string) $data['action_url'];
|
||||
$this->is_complete = (bool) $data['is_complete'];
|
||||
$this->can_view = (bool) $data['can_view'];
|
||||
$this->time = (string) $data['time'];
|
||||
$this->is_dismissable = (bool) $data['is_dismissable'];
|
||||
$this->is_snoozeable = (bool) $data['is_snoozeable'];
|
||||
|
||||
$snoozed_tasks = get_option( self::SNOOZED_OPTION, array() );
|
||||
if ( isset( $snoozed_tasks[ $this->id ] ) ) {
|
||||
$this->snoozed_until = $snoozed_tasks[ $this->id ];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bool for task dismissal.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_dismissed() {
|
||||
if ( ! $this->is_dismissable ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$dismissed = get_option( self::DISMISSED_OPTION, array() );
|
||||
|
||||
return in_array( $this->id, $dismissed, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss the task.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function dismiss() {
|
||||
if ( ! $this->is_dismissable ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$dismissed = get_option( self::DISMISSED_OPTION, array() );
|
||||
$dismissed[] = $this->id;
|
||||
$update = update_option( self::DISMISSED_OPTION, array_unique( $dismissed ) );
|
||||
|
||||
if ( $update ) {
|
||||
wc_admin_record_tracks_event( 'tasklist_dismiss_task', array( 'task_name' => $this->id ) );
|
||||
}
|
||||
|
||||
return $update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo task dismissal.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function undo_dismiss() {
|
||||
$dismissed = get_option( self::DISMISSED_OPTION, array() );
|
||||
$dismissed = array_diff( $dismissed, array( $this->id ) );
|
||||
$update = update_option( self::DISMISSED_OPTION, $dismissed );
|
||||
|
||||
if ( $update ) {
|
||||
wc_admin_record_tracks_event( 'tasklist_undo_dismiss_task', array( 'task_name' => $this->id ) );
|
||||
}
|
||||
|
||||
return $update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bool for task snoozed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_snoozed() {
|
||||
if ( ! $this->is_snoozeable ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$snoozed = get_option( self::SNOOZED_OPTION, array() );
|
||||
|
||||
return isset( $snoozed[ $this->id ] ) && $snoozed[ $this->id ] > ( time() * 1000 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Snooze the task.
|
||||
*
|
||||
* @param string $duration Duration to snooze. day|hour|week.
|
||||
* @return bool
|
||||
*/
|
||||
public function snooze( $duration = 'day' ) {
|
||||
if ( ! $this->is_snoozeable ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$snoozed = get_option( self::SNOOZED_OPTION, array() );
|
||||
$snoozed_until = $this->duration_to_ms[ $duration ] + ( time() * 1000 );
|
||||
$snoozed[ $this->id ] = $snoozed_until;
|
||||
$update = update_option( self::SNOOZED_OPTION, $snoozed );
|
||||
|
||||
if ( $update ) {
|
||||
if ( $update ) {
|
||||
wc_admin_record_tracks_event( 'tasklist_remindmelater_task', array( 'task_name' => $this->id ) );
|
||||
$this->snoozed_until = $snoozed_until;
|
||||
}
|
||||
}
|
||||
|
||||
return $update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo task snooze.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function undo_snooze() {
|
||||
$snoozed = get_option( self::SNOOZED_OPTION, array() );
|
||||
unset( $snoozed[ $this->id ] );
|
||||
$update = update_option( self::SNOOZED_OPTION, $snoozed );
|
||||
|
||||
if ( $update ) {
|
||||
wc_admin_record_tracks_event( 'tasklist_undo_remindmelater_task', array( 'task_name' => $this->id ) );
|
||||
}
|
||||
|
||||
return $update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bool for task visibility.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_visible() {
|
||||
return $this->can_view && ! $this->is_snoozed() && ! $this->is_dismissed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the task as JSON.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_json() {
|
||||
return array(
|
||||
'id' => $this->id,
|
||||
'title' => $this->title,
|
||||
'content' => $this->content,
|
||||
'actionLabel' => $this->action_label,
|
||||
'actionUrl' => $this->action_url,
|
||||
'isComplete' => $this->is_complete,
|
||||
'isVisible' => $this->is_visible(),
|
||||
'time' => $this->time,
|
||||
'isDismissed' => $this->is_dismissed(),
|
||||
'isDismissable' => $this->is_dismissable,
|
||||
'isSnoozed' => $this->is_snoozed(),
|
||||
'isSnoozeable' => $this->is_snoozeable,
|
||||
'snoozedUntil' => $this->snoozed_until,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
<?php
|
||||
/**
|
||||
* Handles storage and retrieval of a task list
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
|
||||
/**
|
||||
* Task List class.
|
||||
*/
|
||||
class TaskList {
|
||||
/**
|
||||
* Option name hidden task lists.
|
||||
*/
|
||||
const HIDDEN_OPTION = 'woocommerce_task_list_hidden_lists';
|
||||
|
||||
/**
|
||||
* Option name completed task lists.
|
||||
*/
|
||||
const COMPLETED_OPTION = 'woocommerce_task_list_completed_lists';
|
||||
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id = '';
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $title = '';
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $tasks = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $data Task list data.
|
||||
*/
|
||||
public function __construct( $data = array() ) {
|
||||
$defaults = array(
|
||||
'id' => null,
|
||||
'title' => '',
|
||||
'tasks' => array(),
|
||||
);
|
||||
|
||||
$data = wp_parse_args( $data, $defaults );
|
||||
|
||||
$this->id = $data['id'];
|
||||
$this->title = $data['title'];
|
||||
$this->tasks = $data['tasks'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the task list is hidden.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_hidden() {
|
||||
$hidden = get_option( self::HIDDEN_OPTION, array() );
|
||||
return in_array( $this->id, $hidden, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the task list.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hide() {
|
||||
$hidden = get_option( self::HIDDEN_OPTION, array() );
|
||||
$hidden[] = $this->id;
|
||||
return update_option( self::HIDDEN_OPTION, array_unique( $hidden ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo hiding of the task list.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function show() {
|
||||
$hidden = get_option( self::HIDDEN_OPTION, array() );
|
||||
$hidden = array_diff( $hidden, array( $this->id ) );
|
||||
return update_option( self::HIDDEN_OPTION, $hidden );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the task list is complete.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
$complete = get_option( self::COMPLETED_OPTION, array() );
|
||||
return in_array( $this->id, $complete, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add task to the task list.
|
||||
*
|
||||
* @param array $args Task properties.
|
||||
*/
|
||||
public function add_task( $args ) {
|
||||
$this->tasks[] = new Task( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list for use in JSON.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_json() {
|
||||
return array(
|
||||
'id' => $this->id,
|
||||
'title' => $this->title,
|
||||
'isHidden' => $this->is_hidden(),
|
||||
'isComplete' => $this->is_complete(),
|
||||
'tasks' => array_map(
|
||||
function( $task ) {
|
||||
return $task->get_json();
|
||||
},
|
||||
$this->tasks
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,204 @@
|
||||
<?php
|
||||
/**
|
||||
* Handles storage and retrieval of task lists
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\Appearance;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\Marketing;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\Payments;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\Products;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\Purchase;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\Shipping;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\StoreDetails;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\Tax;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\WooCommercePayments;
|
||||
use Automattic\WooCommerce\Admin\Loader;
|
||||
|
||||
/**
|
||||
* Task Lists class.
|
||||
*/
|
||||
class TaskLists {
|
||||
/**
|
||||
* Class instance.
|
||||
*
|
||||
* @var TaskLists instance
|
||||
*/
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* An array of all registered lists.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $lists = array();
|
||||
|
||||
/**
|
||||
* Get class instance.
|
||||
*/
|
||||
final public static function instance() {
|
||||
if ( ! static::$instance ) {
|
||||
static::$instance = new static();
|
||||
}
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the task lists.
|
||||
*/
|
||||
public static function init() {
|
||||
self::init_default_lists();
|
||||
add_action( 'rest_api_init', array( __CLASS__, 'maybe_add_default_tasks' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize default lists.
|
||||
*/
|
||||
public static function init_default_lists() {
|
||||
self::add_list(
|
||||
array(
|
||||
'id' => 'setup',
|
||||
'title' => __( 'Get ready to start selling', 'woocommerce' ),
|
||||
)
|
||||
);
|
||||
|
||||
self::add_list(
|
||||
array(
|
||||
'id' => 'extended',
|
||||
'title' => __( 'Things to do next', 'woocommerce' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a task list.
|
||||
*
|
||||
* @param array $args Task list properties.
|
||||
* @return WP_Error|Task
|
||||
*/
|
||||
public static function add_list( $args ) {
|
||||
if ( isset( self::$lists[ $args['id'] ] ) ) {
|
||||
return new \WP_Error(
|
||||
'woocommerce_task_list_exists',
|
||||
__( 'Task list ID already exists', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
|
||||
self::$lists[ $args['id'] ] = new TaskList( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add task to a given task list.
|
||||
*
|
||||
* @param string $list_id List ID to add the task to.
|
||||
* @param array $args Task properties.
|
||||
* @return WP_Error|Task
|
||||
*/
|
||||
public static function add_task( $list_id, $args ) {
|
||||
if ( ! isset( self::$lists[ $list_id ] ) ) {
|
||||
return new \WP_Error(
|
||||
'woocommerce_task_list_invalid_list',
|
||||
__( 'Task list ID does not exist', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
|
||||
self::$lists[ $list_id ]->add_task( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add default task lists.
|
||||
*/
|
||||
public static function maybe_add_default_tasks() {
|
||||
global $wp;
|
||||
|
||||
if ( substr( $wp->request, 0, 34 ) !== 'wp-json/wc-admin/onboarding/tasks' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! apply_filters( 'woocommerce_admin_onboarding_tasks_add_default_tasks', true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::add_task( 'setup', StoreDetails::get_task() );
|
||||
self::add_task( 'setup', Purchase::get_task() );
|
||||
self::add_task( 'setup', Products::get_task() );
|
||||
self::add_task( 'setup', WooCommercePayments::get_task() );
|
||||
self::add_task( 'setup', Payments::get_task() );
|
||||
self::add_task( 'setup', Tax::get_task() );
|
||||
self::add_task( 'setup', Shipping::get_task() );
|
||||
self::add_task( 'setup', Marketing::get_task() );
|
||||
self::add_task( 'setup', Appearance::get_task() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all task lists.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_lists() {
|
||||
return self::$lists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get visible task lists.
|
||||
*/
|
||||
public static function get_visible() {
|
||||
return array_filter(
|
||||
self::get_lists(),
|
||||
function ( $task_list ) {
|
||||
return ! $task_list->is_hidden();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve a task list by ID.
|
||||
*
|
||||
* @param String $id Task list ID.
|
||||
*
|
||||
* @return TaskList|null
|
||||
*/
|
||||
public static function get_list( $id ) {
|
||||
foreach ( self::get_lists() as $task_list ) {
|
||||
if ( $task_list->id === $id ) {
|
||||
return $task_list;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve single task.
|
||||
*
|
||||
* @param String $id Task ID.
|
||||
* @param String $task_list_id Task list ID.
|
||||
*
|
||||
* @return Object
|
||||
*/
|
||||
public static function get_task( $id, $task_list_id = null ) {
|
||||
$task_list = $task_list_id ? self::get_task_list_by_id( $task_list_id ) : null;
|
||||
|
||||
if ( $task_list_id && ! $task_list ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$tasks_to_search = $task_list ? $task_list['tasks'] : array_reduce(
|
||||
self::get_lists(),
|
||||
function ( $all, $curr ) {
|
||||
return array_merge( $all, $curr['tasks'] );
|
||||
},
|
||||
array()
|
||||
);
|
||||
|
||||
foreach ( $tasks_to_search as $task ) {
|
||||
if ( $id === $task['id'] ) {
|
||||
return $task;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
/**
|
||||
* Appearance Task
|
||||
*/
|
||||
class Appearance {
|
||||
/**
|
||||
* Get the task arguments.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_task() {
|
||||
return array(
|
||||
'id' => 'appearance',
|
||||
'title' => __( 'Personalize my store', 'woocommerce' ),
|
||||
'content' => __(
|
||||
'Add your logo, create a homepage, and start designing your store.',
|
||||
'woocommerce'
|
||||
),
|
||||
'action_label' => __( "Let's go", 'woocommerce' ),
|
||||
'is_complete' => get_option( 'woocommerce_task_list_appearance_complete' ),
|
||||
'can_view' => true,
|
||||
'time' => __( '2 minutes', 'woocommerce' ),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Admin\Features\RemoteFreeExtensions\Init as RemoteFreeExtensions;
|
||||
|
||||
/**
|
||||
* Marketing Task
|
||||
*/
|
||||
class Marketing {
|
||||
/**
|
||||
* Get the task arguments.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_task() {
|
||||
return array(
|
||||
'id' => 'marketing',
|
||||
'title' => __( 'Set up marketing tools', 'woocommerce' ),
|
||||
'content' => __(
|
||||
'Add recommended marketing tools to reach new customers and grow your business',
|
||||
'woocommerce'
|
||||
),
|
||||
'is_complete' => self::has_installed_extensions(),
|
||||
'can_view' => Features::is_enabled( 'remote-free-extensions' ) && count( self::get_bundles() ) > 0,
|
||||
'time' => __( '1 minute', 'woocommerce' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the marketing bundles.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_bundles() {
|
||||
return RemoteFreeExtensions::get_extensions(
|
||||
array(
|
||||
'reach',
|
||||
'grow',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store has installed marketing extensions.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_installed_extensions() {
|
||||
$bundles = self::get_bundles();
|
||||
|
||||
return array_reduce(
|
||||
$bundles,
|
||||
function( $has_installed, $bundle ) {
|
||||
if ( $has_installed ) {
|
||||
return true;
|
||||
}
|
||||
foreach ( $bundle['plugins'] as $plugin ) {
|
||||
if ( $plugin->is_installed ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\WooCommercePayments;
|
||||
|
||||
/**
|
||||
* Payments Task
|
||||
*/
|
||||
class Payments {
|
||||
/**
|
||||
* Get the task arguments.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_task() {
|
||||
return array(
|
||||
'id' => 'payments',
|
||||
'title' => __( 'Set up payments', 'woocommerce' ),
|
||||
'content' => __(
|
||||
'Choose payment providers and enable payment methods at checkout.',
|
||||
'woocommerce'
|
||||
),
|
||||
'is_complete' => self::has_gateways(),
|
||||
'can_view' => Features::is_enabled( 'payment-gateway-suggestions' ) &&
|
||||
(
|
||||
! WooCommercePayments::is_requested() ||
|
||||
! WooCommercePayments::is_installed() ||
|
||||
! WooCommercePayments::is_supported()
|
||||
),
|
||||
'time' => __( '2 minutes', 'woocommerce' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store has any enabled gateways.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_gateways() {
|
||||
$gateways = WC()->payment_gateways->get_available_payment_gateways();
|
||||
$enabled_gateways = array_filter(
|
||||
$gateways,
|
||||
function( $gateway ) {
|
||||
return 'yes' === $gateway->enabled;
|
||||
}
|
||||
);
|
||||
|
||||
return ! empty( $enabled_gateways );
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
/**
|
||||
* Products Task
|
||||
*/
|
||||
class Products {
|
||||
/**
|
||||
* Get the task arguments.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_task() {
|
||||
return array(
|
||||
'id' => 'products',
|
||||
'title' => __( 'Add my products', 'woocommerce' ),
|
||||
'content' => __(
|
||||
'Start by adding the first product to your store. You can add your products manually, via CSV, or import them from another service.',
|
||||
'woocommerce'
|
||||
),
|
||||
'is_complete' => self::has_products(),
|
||||
'can_view' => true,
|
||||
'time' => __( '1 minute per product', 'woocommerce' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store has any published products.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_products() {
|
||||
$product_query = new \WC_Product_Query(
|
||||
array(
|
||||
'limit' => 1,
|
||||
'return' => 'ids',
|
||||
'status' => array( 'publish' ),
|
||||
)
|
||||
);
|
||||
$products = $product_query->get_products();
|
||||
|
||||
return 0 !== count( $products );
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Onboarding;
|
||||
use Automattic\WooCommerce\Admin\PluginsHelper;
|
||||
|
||||
/**
|
||||
* Purchase Task
|
||||
*/
|
||||
class Purchase {
|
||||
/**
|
||||
* Get the task arguments.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_task() {
|
||||
$products = self::get_products();
|
||||
|
||||
return array(
|
||||
'id' => 'purchase',
|
||||
'title' => count( $products['remaining'] ) === 1
|
||||
? sprintf(
|
||||
/* translators: %1$s: list of product names comma separated, %2%s the last product name */
|
||||
__(
|
||||
'Add %s to my store',
|
||||
'woocommerce'
|
||||
),
|
||||
$products['remaining'][0]
|
||||
)
|
||||
: __(
|
||||
'Add paid extensions to my store',
|
||||
'woocommerce'
|
||||
),
|
||||
'content' => count( $products['remaining'] ) === 1
|
||||
? $products['purchaseable'][0]['description']
|
||||
: sprintf(
|
||||
/* translators: %1$s: list of product names comma separated, %2%s the last product name */
|
||||
__(
|
||||
'Good choice! You chose to add %1$s and %2$s to your store.',
|
||||
'woocommerce'
|
||||
),
|
||||
implode( ', ', array_slice( $products['remaining'], 0, -1 ) ) . ( count( $products['remaining'] ) > 2 ? ',' : '' ),
|
||||
end( $products['remaining'] )
|
||||
),
|
||||
'action_label' => __( 'Purchase & install now', 'woocommerce' ),
|
||||
'is_complete' => count( $products['remaining'] ) === 0,
|
||||
'can_view' => count( $products['purchaseable'] ) > 0,
|
||||
'time' => __( '2 minutes', 'woocommerce' ),
|
||||
'is_dismissable' => true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get purchaseable and remaining products.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_products() {
|
||||
$profiler_data = get_option( Onboarding::PROFILE_DATA_OPTION, array() );
|
||||
$installed = PluginsHelper::get_installed_plugin_slugs();
|
||||
$product_types = isset( $profiler_data['product_types'] ) ? $profiler_data['product_types'] : array();
|
||||
$allowed = Onboarding::get_allowed_product_types();
|
||||
$purchaseable = array();
|
||||
$remaining = array();
|
||||
foreach ( $product_types as $type ) {
|
||||
if ( ! isset( $allowed[ $type ]['slug'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$purchaseable[] = $allowed[ $type ];
|
||||
|
||||
if ( ! in_array( $allowed[ $type ]['slug'], $installed, true ) ) {
|
||||
$remaining[] = $allowed[ $type ]['label'];
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'purchaseable' => $purchaseable,
|
||||
'remaining' => $remaining,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Onboarding;
|
||||
|
||||
/**
|
||||
* Shipping Task
|
||||
*/
|
||||
class Shipping {
|
||||
/**
|
||||
* Get the task arguments.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_task() {
|
||||
return array(
|
||||
'id' => 'shipping',
|
||||
'title' => __( 'Set up shipping', 'woocommerce' ),
|
||||
'content' => __(
|
||||
"Set your store location and where you'll ship to.",
|
||||
'woocommerce'
|
||||
),
|
||||
'action_url' => self::has_shipping_zones()
|
||||
? admin_url( 'admin.php?page=wc-settings&tab=shipping' )
|
||||
: null,
|
||||
'action_label' => __( "Let's go", 'woocommerce' ),
|
||||
'is_complete' => self::has_shipping_zones(),
|
||||
'can_view' => self::has_physical_products(),
|
||||
'time' => __( '1 minute', 'woocommerce' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store has any shipping zones.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_shipping_zones() {
|
||||
return count( \WC_Shipping_Zones::get_zones() ) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store has physical products.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_physical_products() {
|
||||
$profiler_data = get_option( Onboarding::PROFILE_DATA_OPTION, array() );
|
||||
$product_types = isset( $profiler_data['product_types'] ) ? $profiler_data['product_types'] : array();
|
||||
|
||||
return in_array( 'physical', $product_types, true ) ||
|
||||
count(
|
||||
wc_get_products(
|
||||
array(
|
||||
'virtual' => false,
|
||||
'limit' => 1,
|
||||
)
|
||||
)
|
||||
) > 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Onboarding;
|
||||
|
||||
/**
|
||||
* Store Details Task
|
||||
*/
|
||||
class StoreDetails {
|
||||
/**
|
||||
* Get the task arguments.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_task() {
|
||||
$profiler_data = get_option( Onboarding::PROFILE_DATA_OPTION, array() );
|
||||
|
||||
return array(
|
||||
'id' => 'store_details',
|
||||
'title' => __( 'Store details', 'woocommerce' ),
|
||||
'content' => __(
|
||||
'Your store address is required to set the origin country for shipping, currencies, and payment options.',
|
||||
'woocommerce'
|
||||
),
|
||||
'action_label' => __( "Let's go", 'woocommerce' ),
|
||||
'action_url' => '/setup-wizard',
|
||||
'is_complete' => isset( $profiler_data['completed'] ) && true === $profiler_data['completed'],
|
||||
'can_view' => true,
|
||||
'time' => __( '4 minutes', 'woocommerce' ),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Taxes\Stats\DataStore as TaxDataStore;
|
||||
|
||||
/**
|
||||
* Tax Task
|
||||
*/
|
||||
class Tax {
|
||||
/**
|
||||
* Get the task arguments.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_task() {
|
||||
return array(
|
||||
'id' => 'tax',
|
||||
'title' => __( 'Set up tax', 'woocommerce' ),
|
||||
'content' => self::can_use_automated_taxes()
|
||||
? __(
|
||||
'Good news! WooCommerce Services and Jetpack can automate your sales tax calculations for you.',
|
||||
'woocommerce'
|
||||
)
|
||||
: __(
|
||||
'Set your store location and configure tax rate settings.',
|
||||
'woocommerce'
|
||||
),
|
||||
'action_label' => self::can_use_automated_taxes()
|
||||
? __( 'Yes please', 'woocommerce' )
|
||||
: __( "Let's go", 'woocommerce' ),
|
||||
'is_complete' => get_option( 'wc_connect_taxes_enabled' ) ||
|
||||
count( TaxDataStore::get_taxes( array() ) ) > 0 ||
|
||||
false !== get_option( 'woocommerce_no_sales_tax' ),
|
||||
'is_visible' => true,
|
||||
'time' => __( '1 minute', 'woocommerce' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store has any enabled gateways.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function can_use_automated_taxes() {
|
||||
if ( ! class_exists( 'WC_Taxjar' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array( WC()->countries->get_base_country(), self::get_automated_tax_supported_countries(), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of countries that support automated tax.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_automated_tax_supported_countries() {
|
||||
// https://developers.taxjar.com/api/reference/#countries .
|
||||
$tax_supported_countries = array_merge(
|
||||
array( 'US', 'CA', 'AU' ),
|
||||
WC()->countries->get_european_union_countries()
|
||||
);
|
||||
|
||||
return $tax_supported_countries;
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Onboarding;
|
||||
use Automattic\WooCommerce\Admin\PluginsHelper;
|
||||
|
||||
/**
|
||||
* WooCommercePayments Task
|
||||
*/
|
||||
class WooCommercePayments {
|
||||
/**
|
||||
* Get the task arguments.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_task() {
|
||||
return array(
|
||||
'id' => 'woocommerce-payments',
|
||||
'title' => __( 'Get paid with WooCommerce Payments', 'woocommerce' ),
|
||||
'content' => __(
|
||||
"You're only one step away from getting paid. Verify your business details to start managing transactions with WooCommerce Payments.",
|
||||
'woocommerce'
|
||||
),
|
||||
'action_label' => __( 'Finish setup', 'woocommerce' ),
|
||||
'expanded' => true,
|
||||
'is_complete' => self::is_connected(),
|
||||
'can_view' => self::is_requested() &&
|
||||
self::is_installed() &&
|
||||
self::is_supported(),
|
||||
'time' => __( '2 minutes', 'woocommerce' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the plugin was requested during onboarding.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_requested() {
|
||||
$profiler_data = get_option( Onboarding::PROFILE_DATA_OPTION, array() );
|
||||
$business_extensions = isset( $profiler_data['business_extensions'] ) ? $profiler_data['business_extensions'] : array();
|
||||
return in_array( 'woocommerce-payments', $business_extensions, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the plugin is installed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_installed() {
|
||||
$installed_plugins = PluginsHelper::get_installed_plugin_slugs();
|
||||
return in_array( 'woocommerce-payments', $installed_plugins, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if WooCommerce Payments is connected.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_connected() {
|
||||
if ( class_exists( '\WC_Payments' ) ) {
|
||||
$wc_payments_gateway = \WC_Payments::get_gateway();
|
||||
return method_exists( $wc_payments_gateway, 'is_connected' )
|
||||
? $wc_payments_gateway->is_connected()
|
||||
: false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store is in a supported country.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_supported() {
|
||||
return in_array(
|
||||
WC()->countries->get_base_country(),
|
||||
array(
|
||||
'US',
|
||||
'PR',
|
||||
'AU',
|
||||
'CA',
|
||||
'DE',
|
||||
'ES',
|
||||
'FR',
|
||||
'GB',
|
||||
'IE',
|
||||
'IT',
|
||||
'NZ',
|
||||
),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
<?php
|
||||
/**
|
||||
* Handles polling and storage of specs
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Specs data source poller class.
|
||||
* This handles polling specs from JSON endpoints.
|
||||
*/
|
||||
class DataSourcePoller {
|
||||
/**
|
||||
* Name of data sources filter.
|
||||
*/
|
||||
const FILTER_NAME = 'woocommerce_admin_payment_gateway_suggestions_data_sources';
|
||||
|
||||
/**
|
||||
* Default data sources array.
|
||||
*/
|
||||
const DATA_SOURCES = array(
|
||||
'https://woocommerce.com/wp-json/wccom/payment-gateway-suggestions/1.0/suggestions.json',
|
||||
);
|
||||
|
||||
/**
|
||||
* The logger instance.
|
||||
*
|
||||
* @var WC_Logger|null
|
||||
*/
|
||||
protected static $logger = null;
|
||||
|
||||
/**
|
||||
* Get the logger instance.
|
||||
*
|
||||
* @return WC_Logger
|
||||
*/
|
||||
private static function get_logger() {
|
||||
if ( is_null( self::$logger ) ) {
|
||||
self::$logger = wc_get_logger();
|
||||
}
|
||||
|
||||
return self::$logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the data sources for specs and persists those specs.
|
||||
*
|
||||
* @return bool Whether any specs were read.
|
||||
*/
|
||||
public static function read_specs_from_data_sources() {
|
||||
$specs = array();
|
||||
$data_sources = apply_filters( self::FILTER_NAME, self::DATA_SOURCES );
|
||||
|
||||
// Note that this merges the specs from the data sources based on the
|
||||
// id - last one wins.
|
||||
foreach ( $data_sources as $url ) {
|
||||
$specs_from_data_source = self::read_data_source( $url );
|
||||
self::merge_specs( $specs_from_data_source, $specs, $url );
|
||||
}
|
||||
|
||||
return $specs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a single data source and return the read specs
|
||||
*
|
||||
* @param string $url The URL to read the specs from.
|
||||
*
|
||||
* @return array The specs that have been read from the data source.
|
||||
*/
|
||||
private static function read_data_source( $url ) {
|
||||
$logger_context = array( 'source' => $url );
|
||||
$logger = self::get_logger();
|
||||
$response = wp_remote_get(
|
||||
add_query_arg(
|
||||
'_locale',
|
||||
get_user_locale(),
|
||||
$url
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) || ! isset( $response['body'] ) ) {
|
||||
$logger->error(
|
||||
'Error getting remote payment method data feed',
|
||||
$logger_context
|
||||
);
|
||||
// phpcs:ignore
|
||||
$logger->error( print_r( $response, true ), $logger_context );
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
$body = $response['body'];
|
||||
$specs = json_decode( $body );
|
||||
|
||||
if ( null === $specs ) {
|
||||
$logger->error(
|
||||
'Empty response in remote payment method data feed',
|
||||
$logger_context
|
||||
);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
if ( ! is_array( $specs ) ) {
|
||||
$logger->error(
|
||||
'Remote payment method data feed is not an array',
|
||||
$logger_context
|
||||
);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
return $specs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the specs.
|
||||
*
|
||||
* @param Array $specs_to_merge_in The specs to merge in to $specs.
|
||||
* @param Array $specs The list of specs being merged into.
|
||||
* @param string $url The url of the feed being merged in (for error reporting).
|
||||
*/
|
||||
private static function merge_specs( $specs_to_merge_in, &$specs, $url ) {
|
||||
foreach ( $specs_to_merge_in as $spec ) {
|
||||
if ( ! self::validate_spec( $spec, $url ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$id = $spec->id;
|
||||
$specs[ $id ] = $spec;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the spec.
|
||||
*
|
||||
* @param object $spec The spec to validate.
|
||||
* @param string $url The url of the feed that provided the spec.
|
||||
*
|
||||
* @return bool The result of the validation.
|
||||
*/
|
||||
private static function validate_spec( $spec, $url ) {
|
||||
$logger = self::get_logger();
|
||||
$logger_context = array( 'source' => $url );
|
||||
|
||||
if ( ! isset( $spec->id ) ) {
|
||||
$logger->error(
|
||||
'Spec is invalid because the id is missing in feed',
|
||||
$logger_context
|
||||
);
|
||||
// phpcs:ignore
|
||||
$logger->error( print_r( $spec, true ), $logger_context );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,388 @@
|
||||
<?php
|
||||
/**
|
||||
* Gets a list of fallback methods if remote fetching is disabled.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Init as OnboardingTasks;
|
||||
|
||||
/**
|
||||
* Default Payment Gateways
|
||||
*/
|
||||
class DefaultPaymentGateways {
|
||||
|
||||
/**
|
||||
* Get default specs.
|
||||
*
|
||||
* @return array Default specs.
|
||||
*/
|
||||
public static function get_all() {
|
||||
return array(
|
||||
array(
|
||||
'id' => 'payfast',
|
||||
'title' => __( 'PayFast', 'woocommerce' ),
|
||||
'content' => __( 'The PayFast extension for WooCommerce enables you to accept payments by Credit Card and EFT via one of South Africa’s most popular payment gateways. No setup fees or monthly subscription costs. Selecting this extension will configure your store to use South African rands as the selected currency.', 'woocommerce' ),
|
||||
'image' => WC()->plugin_url() . '/assets/images/payfast.png',
|
||||
'plugins' => array( 'woocommerce-payfast-gateway' ),
|
||||
'is_visible' => array(
|
||||
(object) array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'ZA',
|
||||
'operation' => '=',
|
||||
),
|
||||
self::get_rules_for_cbd( false ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'stripe',
|
||||
'title' => __( ' Stripe', 'woocommerce' ),
|
||||
'content' => __( 'Accept debit and credit cards in 135+ currencies, methods such as Alipay, and one-touch checkout with Apple Pay.', 'woocommerce' ),
|
||||
'image' => WC()->plugin_url() . '/assets/images/stripe.png',
|
||||
'plugins' => array( 'woocommerce-gateway-stripe' ),
|
||||
'is_visible' => array(
|
||||
// https://stripe.com/global.
|
||||
self::get_rules_for_countries(
|
||||
array(
|
||||
'AU',
|
||||
'AT',
|
||||
'BE',
|
||||
'BG',
|
||||
'BR',
|
||||
'CA',
|
||||
'CY',
|
||||
'CZ',
|
||||
'DK',
|
||||
'EE',
|
||||
'FI',
|
||||
'FR',
|
||||
'DE',
|
||||
'GR',
|
||||
'HK',
|
||||
'IN',
|
||||
'IE',
|
||||
'IT',
|
||||
'JP',
|
||||
'LV',
|
||||
'LT',
|
||||
'LU',
|
||||
'MY',
|
||||
'MT',
|
||||
'MX',
|
||||
'NL',
|
||||
'NZ',
|
||||
'NO',
|
||||
'PL',
|
||||
'PT',
|
||||
'RO',
|
||||
'SG',
|
||||
'SK',
|
||||
'SI',
|
||||
'ES',
|
||||
'SE',
|
||||
'CH',
|
||||
'GB',
|
||||
'US',
|
||||
'PR',
|
||||
)
|
||||
),
|
||||
self::get_rules_for_cbd( false ),
|
||||
),
|
||||
'recommendation_priority' => 3,
|
||||
),
|
||||
array(
|
||||
'id' => 'paystack',
|
||||
'title' => __( 'Paystack', 'woocommerce' ),
|
||||
'content' => __( 'Paystack helps African merchants accept one-time and recurring payments online with a modern, safe, and secure payment gateway.', 'woocommerce' ),
|
||||
'image' => plugins_url( 'images/onboarding/paystack.png', WC_ADMIN_PLUGIN_FILE ),
|
||||
'plugins' => array( 'woo-paystack' ),
|
||||
'is_visible' => array(
|
||||
self::get_rules_for_countries( array( 'ZA', 'GH', 'NG' ) ),
|
||||
self::get_rules_for_cbd( false ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'kco',
|
||||
'title' => __( 'Klarna Checkout', 'woocommerce' ),
|
||||
'content' => __( 'Choose the payment that you want, pay now, pay later or slice it. No credit card numbers, no passwords, no worries.', 'woocommerce' ),
|
||||
'image' => WC()->plugin_url() . '/assets/images/klarna-black.png',
|
||||
'plugins' => array( 'klarna-checkout-for-woocommerce' ),
|
||||
'is_visible' => array(
|
||||
self::get_rules_for_countries( array( 'SE', 'FI', 'NO' ) ),
|
||||
self::get_rules_for_cbd( false ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'klarna_payments',
|
||||
'title' => __( 'Klarna Payments', 'woocommerce' ),
|
||||
'content' => __( 'Choose the payment that you want, pay now, pay later or slice it. No credit card numbers, no passwords, no worries.', 'woocommerce' ),
|
||||
'image' => WC()->plugin_url() . '/assets/images/klarna-black.png',
|
||||
'plugins' => array( 'klarna-payments-for-woocommerce' ),
|
||||
'is_visible' => array(
|
||||
self::get_rules_for_countries(
|
||||
array(
|
||||
'DK',
|
||||
'DE',
|
||||
'AT',
|
||||
'NL',
|
||||
'CH',
|
||||
'BE',
|
||||
'SP',
|
||||
'PL',
|
||||
'FR',
|
||||
'IT',
|
||||
'GB',
|
||||
)
|
||||
),
|
||||
self::get_rules_for_cbd( false ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'mollie_wc_gateway_banktransfer',
|
||||
'title' => __( 'Mollie', 'woocommerce' ),
|
||||
'content' => __( 'Effortless payments by Mollie: Offer global and local payment methods, get onboarded in minutes, and supported in your language.', 'woocommerce' ),
|
||||
'image' => plugins_url( 'images/onboarding/mollie.svg', WC_ADMIN_PLUGIN_FILE ),
|
||||
'plugins' => array( 'mollie-payments-for-woocommerce' ),
|
||||
'is_visible' => array(
|
||||
self::get_rules_for_countries(
|
||||
array(
|
||||
'FR',
|
||||
'DE',
|
||||
'GB',
|
||||
'AT',
|
||||
'CH',
|
||||
'ES',
|
||||
'IT',
|
||||
'PL',
|
||||
'FI',
|
||||
'NL',
|
||||
'BE',
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'woo-mercado-pago-custom',
|
||||
'title' => __( 'Mercado Pago Checkout Pro & Custom', 'woocommerce' ),
|
||||
'content' => __( 'Accept credit and debit cards, offline (cash or bank transfer) and logged-in payments with money in Mercado Pago. Safe and secure payments with the leading payment processor in LATAM.', 'woocommerce' ),
|
||||
'image' => plugins_url( 'images/onboarding/mercadopago.png', WC_ADMIN_PLUGIN_FILE ),
|
||||
'plugins' => array( 'woocommerce-mercadopago' ),
|
||||
'is_visible' => array(
|
||||
self::get_rules_for_countries( array( 'AR', 'BR', 'CL', 'CO', 'MX', 'PE', 'UY' ) ),
|
||||
),
|
||||
'recommendation_priority' => 2,
|
||||
'is_local_partner' => true,
|
||||
),
|
||||
array(
|
||||
'id' => 'ppcp-gateway',
|
||||
'title' => __( 'PayPal Payments', 'woocommerce' ),
|
||||
'content' => __( "Safe and secure payments using credit cards or your customer's PayPal account.", 'woocommerce' ),
|
||||
'image' => WC()->plugin_url() . '/assets/images/paypal.png',
|
||||
'plugins' => array( 'woocommerce-paypal-payments' ),
|
||||
'is_visible' => array(
|
||||
(object) array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'IN',
|
||||
'operation' => '!=',
|
||||
),
|
||||
self::get_rules_for_cbd( false ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'cod',
|
||||
'title' => __( 'Cash on delivery', 'woocommerce' ),
|
||||
'content' => __( 'Take payments in cash upon delivery.', 'woocommerce' ),
|
||||
'image' => plugins_url( 'images/onboarding/cod.svg', WC_ADMIN_PLUGIN_FILE ),
|
||||
'is_visible' => array(
|
||||
self::get_rules_for_cbd( false ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'bacs',
|
||||
'title' => __( 'Direct bank transfer', 'woocommerce' ),
|
||||
'content' => __( 'Take payments via bank transfer.', 'woocommerce' ),
|
||||
'image' => plugins_url( 'images/onboarding/bacs.svg', WC_ADMIN_PLUGIN_FILE ),
|
||||
'is_visible' => array(
|
||||
self::get_rules_for_cbd( false ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'woocommerce_payments',
|
||||
'title' => __( 'WooCommerce Payments', 'woocommerce' ),
|
||||
'content' => __(
|
||||
'Manage transactions without leaving your WordPress Dashboard. Only with WooCommerce Payments.',
|
||||
'woocommerce'
|
||||
),
|
||||
'image' => plugins_url( 'images/onboarding/wcpay.svg', WC_ADMIN_PLUGIN_FILE ),
|
||||
'plugins' => array( 'woocommerce-payments' ),
|
||||
'description' => 'Try the new way to get paid. Securely accept credit and debit cards on your site. Manage transactions without leaving your WordPress dashboard. Only with WooCommerce Payments.',
|
||||
'is_visible' => array(
|
||||
self::get_rules_for_cbd( false ),
|
||||
self::get_rules_for_countries( self::get_wcpay_countries() ),
|
||||
),
|
||||
'recommendation_priority' => 1,
|
||||
),
|
||||
array(
|
||||
'id' => 'razorpay',
|
||||
'title' => __( 'Razorpay', 'woocommerce' ),
|
||||
'content' => __( 'The official Razorpay extension for WooCommerce allows you to accept credit cards, debit cards, netbanking, wallet, and UPI payments.', 'woocommerce' ),
|
||||
'image' => plugins_url( 'images/onboarding/razorpay.svg', WC_ADMIN_PLUGIN_FILE ),
|
||||
'plugins' => array( 'woo-razorpay' ),
|
||||
'is_visible' => array(
|
||||
(object) array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'IN',
|
||||
'operation' => '=',
|
||||
),
|
||||
self::get_rules_for_cbd( false ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'payubiz',
|
||||
'title' => __( 'PayU for WooCommerce', 'woocommerce' ),
|
||||
'content' => __( 'Enable PayU’s exclusive plugin for WooCommerce to start accepting payments in 100+ payment methods available in India including credit cards, debit cards, UPI, & more!', 'woocommerce' ),
|
||||
'image' => plugins_url( 'images/onboarding/payu.svg', WC_ADMIN_PLUGIN_FILE ),
|
||||
'plugins' => array( 'payu-india' ),
|
||||
'is_visible' => array(
|
||||
(object) array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'IN',
|
||||
'operation' => '=',
|
||||
),
|
||||
self::get_rules_for_cbd( false ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'eway',
|
||||
'title' => __( 'Eway', 'woocommerce' ),
|
||||
'content' => __( 'The Eway extension for WooCommerce allows you to take credit card payments directly on your store without redirecting your customers to a third party site to make payment.', 'woocommerce' ),
|
||||
'image' => plugins_url( 'images/onboarding/eway.png', WC_ADMIN_PLUGIN_FILE ),
|
||||
'plugins' => array( 'woocommerce-gateway-eway' ),
|
||||
'is_visible' => array(
|
||||
self::get_rules_for_countries( array( 'AU', 'NZ' ) ),
|
||||
self::get_rules_for_cbd( false ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'square_credit_card',
|
||||
'title' => __( 'Square', 'woocommerce' ),
|
||||
'content' => __( 'Securely accept credit and debit cards with one low rate, no surprise fees (custom rates available). Sell online and in store and track sales and inventory in one place.', 'woocommerce' ),
|
||||
'image' => WC()->plugin_url() . '/assets/images/square-black.png',
|
||||
'plugins' => array( 'woocommerce-square' ),
|
||||
'is_visible' => array(
|
||||
(object) array(
|
||||
'type' => 'or',
|
||||
'operands' => (object) array(
|
||||
array(
|
||||
self::get_rules_for_countries( array( 'US' ) ),
|
||||
self::get_rules_for_cbd( true ),
|
||||
),
|
||||
array(
|
||||
self::get_rules_for_countries( array( 'US', 'CA', 'JP', 'GB', 'AU', 'IE', 'FR' ) ),
|
||||
self::get_rules_for_selling_venues( array( 'brick-mortar', 'brick-mortar-other' ) ),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array of countries supported by WCPay depending on feature flag.
|
||||
*
|
||||
* @return array Array of countries.
|
||||
*/
|
||||
public static function get_wcpay_countries() {
|
||||
return array( 'US', 'PR', 'AU', 'CA', 'DE', 'ES', 'FR', 'GB', 'IE', 'IT', 'NZ', 'AT', 'BE', 'NL', 'PL', 'PT', 'CH', 'HK', 'SG' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rules that match the store base location to one of the provided countries.
|
||||
*
|
||||
* @param array $countries Array of countries to match.
|
||||
* @return object Rules to match.
|
||||
*/
|
||||
public static function get_rules_for_countries( $countries ) {
|
||||
$rules = array();
|
||||
|
||||
foreach ( $countries as $country ) {
|
||||
$rules[] = (object) array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => $country,
|
||||
'operation' => '=',
|
||||
);
|
||||
}
|
||||
|
||||
return (object) array(
|
||||
'type' => 'or',
|
||||
'operands' => $rules,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rules that match the store's selling venues.
|
||||
*
|
||||
* @param array $selling_venues Array of venues to match.
|
||||
* @return object Rules to match.
|
||||
*/
|
||||
public static function get_rules_for_selling_venues( $selling_venues ) {
|
||||
$rules = array();
|
||||
|
||||
foreach ( $selling_venues as $venue ) {
|
||||
$rules[] = (object) array(
|
||||
'type' => 'option',
|
||||
'transformers' => array(
|
||||
(object) array(
|
||||
'use' => 'dot_notation',
|
||||
'arguments' => (object) array(
|
||||
'path' => 'selling_venues',
|
||||
),
|
||||
),
|
||||
),
|
||||
'option_name' => 'woocommerce_onboarding_profile',
|
||||
'operation' => '=',
|
||||
'value' => $venue,
|
||||
'default' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
return (object) array(
|
||||
'type' => 'or',
|
||||
'operands' => $rules,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default rules for CBD based on given argument.
|
||||
*
|
||||
* @param bool $should_have Whether or not the store should have CBD as an industry (true) or not (false).
|
||||
* @return array Rules to match.
|
||||
*/
|
||||
public static function get_rules_for_cbd( $should_have ) {
|
||||
return (object) array(
|
||||
'type' => 'option',
|
||||
'transformers' => array(
|
||||
(object) array(
|
||||
'use' => 'dot_notation',
|
||||
'arguments' => (object) array(
|
||||
'path' => 'industry',
|
||||
),
|
||||
),
|
||||
(object) array(
|
||||
'use' => 'array_column',
|
||||
'arguments' => (object) array(
|
||||
'key' => 'slug',
|
||||
),
|
||||
),
|
||||
),
|
||||
'option_name' => 'woocommerce_onboarding_profile',
|
||||
'operation' => $should_have ? 'contains' : '!contains',
|
||||
'value' => 'cbd-other-hemp-derived-products',
|
||||
'default' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* Evaluates the spec and returns a status.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\RemoteInboxNotifications\RuleEvaluator;
|
||||
|
||||
/**
|
||||
* Evaluates the spec and returns the evaluated suggestion.
|
||||
*/
|
||||
class EvaluateSuggestion {
|
||||
/**
|
||||
* Evaluates the spec and returns the suggestion.
|
||||
*
|
||||
* @param object|array $spec The suggestion to evaluate.
|
||||
* @return object The evaluated suggestion.
|
||||
*/
|
||||
public static function evaluate( $spec ) {
|
||||
$rule_evaluator = new RuleEvaluator();
|
||||
$suggestion = is_array( $spec ) ? (object) $spec : clone $spec;
|
||||
|
||||
if ( isset( $suggestion->is_visible ) ) {
|
||||
$is_visible = $rule_evaluator->evaluate( $suggestion->is_visible );
|
||||
$suggestion->is_visible = $is_visible;
|
||||
}
|
||||
|
||||
return $suggestion;
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/**
|
||||
* Handles running payment gateway suggestion specs
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\RemoteInboxNotifications\SpecRunner;
|
||||
use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\DefaultPaymentGateways;
|
||||
use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\PaymentGatewaysController;
|
||||
|
||||
/**
|
||||
* Remote Payment Methods engine.
|
||||
* This goes through the specs and gets eligible payment gateways.
|
||||
*/
|
||||
class Init {
|
||||
const SPECS_TRANSIENT_NAME = 'woocommerce_admin_payment_gateway_suggestions_specs';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'change_locale', array( __CLASS__, 'delete_specs_transient' ) );
|
||||
PaymentGatewaysController::init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Go through the specs and run them.
|
||||
*/
|
||||
public static function get_suggestions() {
|
||||
$suggestions = array();
|
||||
$specs = self::get_specs();
|
||||
|
||||
foreach ( $specs as $spec ) {
|
||||
$suggestion = EvaluateSuggestion::evaluate( $spec );
|
||||
$suggestions[] = $suggestion;
|
||||
}
|
||||
|
||||
return array_values(
|
||||
array_filter(
|
||||
$suggestions,
|
||||
function( $suggestion ) {
|
||||
return ! property_exists( $suggestion, 'is_visible' ) || $suggestion->is_visible;
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the specs transient.
|
||||
*/
|
||||
public static function delete_specs_transient() {
|
||||
delete_transient( self::SPECS_TRANSIENT_NAME );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specs or fetch remotely if they don't exist.
|
||||
*/
|
||||
public static function get_specs() {
|
||||
$specs = get_transient( self::SPECS_TRANSIENT_NAME );
|
||||
|
||||
// Fetch specs if they don't yet exist.
|
||||
if ( false === $specs || ! is_array( $specs ) || 0 === count( $specs ) ) {
|
||||
if ( 'no' === get_option( 'woocommerce_show_marketplace_suggestions', 'yes' ) ) {
|
||||
return DefaultPaymentGateways::get_all();
|
||||
}
|
||||
|
||||
$specs = DataSourcePoller::read_specs_from_data_sources();
|
||||
|
||||
// Fall back to default specs if polling failed.
|
||||
if ( ! $specs ) {
|
||||
return DefaultPaymentGateways::get_all();
|
||||
}
|
||||
|
||||
set_transient( self::SPECS_TRANSIENT_NAME, $specs, 7 * DAY_IN_SECONDS );
|
||||
}
|
||||
|
||||
return $specs;
|
||||
}
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
<?php
|
||||
/**
|
||||
* Logic for extending WC_REST_Payment_Gateways_Controller.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\TransientNotices;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* PaymentGateway class
|
||||
*/
|
||||
class PaymentGatewaysController {
|
||||
|
||||
/**
|
||||
* Initialize payment gateway changes.
|
||||
*/
|
||||
public static function init() {
|
||||
add_filter( 'woocommerce_rest_prepare_payment_gateway', array( __CLASS__, 'extend_response' ), 10, 3 );
|
||||
add_filter( 'admin_init', array( __CLASS__, 'possibly_do_connection_return_action' ) );
|
||||
add_action( 'woocommerce_admin_payment_gateway_connection_return', array( __CLASS__, 'handle_successfull_connection' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add necessary fields to REST API response.
|
||||
*
|
||||
* @param WP_REST_Response $response Response data.
|
||||
* @param WC_Payment_Gateway $gateway Payment gateway object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public static function extend_response( $response, $gateway, $request ) {
|
||||
$data = $response->get_data();
|
||||
|
||||
$data['needs_setup'] = $gateway->needs_setup();
|
||||
$data['post_install_scripts'] = self::get_post_install_scripts( $gateway );
|
||||
$data['settings_url'] = method_exists( $gateway, 'get_settings_url' )
|
||||
? $gateway->get_settings_url()
|
||||
: admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=' . strtolower( $gateway->id ) );
|
||||
|
||||
$return_url = wc_admin_url( '&task=payments&connection-return=' . strtolower( $gateway->id ) );
|
||||
$data['connection_url'] = method_exists( $gateway, 'get_connection_url' )
|
||||
? $gateway->get_connection_url( $return_url )
|
||||
: null;
|
||||
|
||||
$data['setup_help_text'] = method_exists( $gateway, 'get_setup_help_text' )
|
||||
? $gateway->get_setup_help_text()
|
||||
: null;
|
||||
|
||||
$data['required_settings_keys'] = method_exists( $gateway, 'get_required_settings_keys' )
|
||||
? $gateway->get_required_settings_keys()
|
||||
: array();
|
||||
|
||||
$response->set_data( $data );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get payment gateway scripts for post-install.
|
||||
*
|
||||
* @param WC_Payment_Gateway $gateway Payment gateway object.
|
||||
* @return array Install scripts.
|
||||
*/
|
||||
public static function get_post_install_scripts( $gateway ) {
|
||||
$scripts = array();
|
||||
$wp_scripts = wp_scripts();
|
||||
|
||||
$handles = method_exists( $gateway, 'get_post_install_script_handles' )
|
||||
? $gateway->get_post_install_script_handles()
|
||||
: array();
|
||||
|
||||
foreach ( $handles as $handle ) {
|
||||
if ( isset( $wp_scripts->registered[ $handle ] ) ) {
|
||||
$scripts[] = $wp_scripts->registered[ $handle ];
|
||||
}
|
||||
}
|
||||
|
||||
return $scripts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call an action after a gating has been successfully returned.
|
||||
*/
|
||||
public static function possibly_do_connection_return_action() {
|
||||
// phpcs:disable WordPress.Security.NonceVerification
|
||||
if (
|
||||
! isset( $_GET['page'] ) ||
|
||||
'wc-admin' !== $_GET['page'] ||
|
||||
! isset( $_GET['task'] ) ||
|
||||
'payments' !== $_GET['task'] ||
|
||||
! isset( $_GET['connection-return'] )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$gateway_id = sanitize_text_field( wp_unslash( $_GET['connection-return'] ) );
|
||||
|
||||
// phpcs:enable WordPress.Security.NonceVerification
|
||||
|
||||
do_action( 'woocommerce_admin_payment_gateway_connection_return', $gateway_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a successful gateway connection.
|
||||
*
|
||||
* @param string $gateway_id Gateway ID.
|
||||
*/
|
||||
public static function handle_successfull_connection( $gateway_id ) {
|
||||
// phpcs:disable WordPress.Security.NonceVerification
|
||||
if ( ! isset( $_GET['success'] ) || 1 !== intval( $_GET['success'] ) ) {
|
||||
return;
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification
|
||||
|
||||
$payment_gateways = WC()->payment_gateways()->payment_gateways();
|
||||
$payment_gateway = isset( $payment_gateways[ $gateway_id ] ) ? $payment_gateways[ $gateway_id ] : null;
|
||||
|
||||
if ( ! $payment_gateway ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$payment_gateway->update_option( 'enabled', 'yes' );
|
||||
|
||||
TransientNotices::add(
|
||||
array(
|
||||
'user_id' => get_current_user_id(),
|
||||
'id' => 'payment-gateway-connection-return-' . str_replace( ',', '-', $gateway_id ),
|
||||
'status' => 'success',
|
||||
'content' => sprintf(
|
||||
/* translators: the title of the payment gateway */
|
||||
__( '%s connected successfully', 'woocommerce' ),
|
||||
$payment_gateway->method_title
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
wc_admin_record_tracks_event(
|
||||
'tasklist_payment_connect_method',
|
||||
array(
|
||||
'payment_method' => $gateway_id,
|
||||
)
|
||||
);
|
||||
|
||||
wp_safe_redirect( wc_admin_url() );
|
||||
}
|
||||
}
|
@ -0,0 +1,154 @@
|
||||
<?php
|
||||
/**
|
||||
* Handles polling and storage of specs
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\RemoteFreeExtensions;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Specs data source poller class.
|
||||
* This handles polling specs from JSON endpoints.
|
||||
*/
|
||||
class DataSourcePoller {
|
||||
const DATA_SOURCES = array(
|
||||
'https://woocommerce.com/wp-json/wccom/obw-free-extensions/2.0/extensions.json',
|
||||
);
|
||||
|
||||
/**
|
||||
* The logger instance.
|
||||
*
|
||||
* @var WC_Logger|null
|
||||
*/
|
||||
protected static $logger = null;
|
||||
|
||||
/**
|
||||
* Get the logger instance.
|
||||
*
|
||||
* @return WC_Logger
|
||||
*/
|
||||
private static function get_logger() {
|
||||
if ( is_null( self::$logger ) ) {
|
||||
self::$logger = wc_get_logger();
|
||||
}
|
||||
|
||||
return self::$logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the data sources for specs and persists those specs.
|
||||
*
|
||||
* @return bool Whether any specs were read.
|
||||
*/
|
||||
public static function read_specs_from_data_sources() {
|
||||
$specs = array();
|
||||
$data_sources = apply_filters( 'woocommerce_admin_remote_free_extensions_data_sources', self::DATA_SOURCES );
|
||||
|
||||
// Note that this merges the specs from the data sources based on the
|
||||
// key - last one wins.
|
||||
foreach ( $data_sources as $url ) {
|
||||
$specs_from_data_source = self::read_data_source( $url );
|
||||
self::merge_specs( $specs_from_data_source, $specs, $url );
|
||||
}
|
||||
|
||||
return $specs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a single data source and return the read specs
|
||||
*
|
||||
* @param string $url The URL to read the specs from.
|
||||
*
|
||||
* @return array The specs that have been read from the data source.
|
||||
*/
|
||||
private static function read_data_source( $url ) {
|
||||
$logger_context = array( 'source' => $url );
|
||||
$logger = self::get_logger();
|
||||
$response = wp_remote_get(
|
||||
add_query_arg(
|
||||
'_locale',
|
||||
get_user_locale(),
|
||||
$url
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) || ! isset( $response['body'] ) ) {
|
||||
$logger->error(
|
||||
'Error getting remote payment method data feed',
|
||||
$logger_context
|
||||
);
|
||||
// phpcs:ignore
|
||||
$logger->error( print_r( $response, true ), $logger_context );
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
$body = $response['body'];
|
||||
$specs = json_decode( $body );
|
||||
|
||||
if ( null === $specs ) {
|
||||
$logger->error(
|
||||
'Empty response in remote payment method data feed',
|
||||
$logger_context
|
||||
);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
if ( ! is_array( $specs ) ) {
|
||||
$logger->error(
|
||||
'Remote payment method data feed is not an array',
|
||||
$logger_context
|
||||
);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
return $specs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the specs.
|
||||
*
|
||||
* @param Array $specs_to_merge_in The specs to merge in to $specs.
|
||||
* @param Array $specs The list of specs being merged into.
|
||||
* @param string $url The url of the feed being merged in (for error reporting).
|
||||
*/
|
||||
private static function merge_specs( $specs_to_merge_in, &$specs, $url ) {
|
||||
foreach ( $specs_to_merge_in as $spec ) {
|
||||
if ( ! self::validate_spec( $spec, $url ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = $spec->key;
|
||||
$specs[ $key ] = $spec;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the spec.
|
||||
*
|
||||
* @param object $spec The spec to validate.
|
||||
* @param string $url The url of the feed that provided the spec.
|
||||
*
|
||||
* @return bool The result of the validation.
|
||||
*/
|
||||
private static function validate_spec( $spec, $url ) {
|
||||
$logger = self::get_logger();
|
||||
$logger_context = array( 'source' => $url );
|
||||
|
||||
if ( ! isset( $spec->key ) ) {
|
||||
$logger->error(
|
||||
'Spec is invalid because the key is missing in feed',
|
||||
$logger_context
|
||||
);
|
||||
// phpcs:ignore
|
||||
$logger->error( print_r( $spec, true ), $logger_context );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,402 @@
|
||||
<?php
|
||||
/**
|
||||
* Gets a list of fallback methods if remote fetching is disabled.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\RemoteFreeExtensions;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Init as OnboardingTasks;
|
||||
|
||||
/**
|
||||
* Default Free Extensions
|
||||
*/
|
||||
class DefaultFreeExtensions {
|
||||
|
||||
/**
|
||||
* Get default specs.
|
||||
*
|
||||
* @return array Default specs.
|
||||
*/
|
||||
public static function get_all() {
|
||||
$bundles = [
|
||||
[
|
||||
'key' => 'basics',
|
||||
'title' => __( 'Get the basics', 'woocommerce' ),
|
||||
'plugins' => [
|
||||
[
|
||||
'key' => 'woocommerce-payments',
|
||||
'description' => sprintf(
|
||||
/* translators: 1: opening product link tag. 2: closing link tag */
|
||||
__( 'Accept credit cards with %1$sWooCommerce Payments%2$s', 'woocommerce' ),
|
||||
'<a href="https://woocommerce.com/products/woocommerce-payments" target="_blank">',
|
||||
'</a>'
|
||||
),
|
||||
'is_visible' => [
|
||||
[
|
||||
'type' => 'or',
|
||||
'operands' => [
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'US',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'PR',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'AU',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'CA',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'DE',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'ES',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'FR',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'GB',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'IE',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'IT',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'NZ',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'AT',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'BE',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'NL',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'PL',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'PT',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'CH',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'HK',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'SG',
|
||||
'operation' => '=',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'type' => 'option',
|
||||
'transformers' => [
|
||||
[
|
||||
'use' => 'dot_notation',
|
||||
'arguments' => [
|
||||
'path' => 'industry',
|
||||
],
|
||||
],
|
||||
[
|
||||
'use' => 'array_column',
|
||||
'arguments' => [
|
||||
'key' => 'slug',
|
||||
],
|
||||
],
|
||||
[
|
||||
'use' => 'array_search',
|
||||
'arguments' => [
|
||||
'value' => 'cbd-other-hemp-derived-products',
|
||||
],
|
||||
],
|
||||
],
|
||||
'option_name' => 'woocommerce_onboarding_profile',
|
||||
'value' => 'cbd-other-hemp-derived-products',
|
||||
'default' => '',
|
||||
'operation' => '!=',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'woocommerce-services:shipping',
|
||||
'description' => sprintf(
|
||||
/* translators: 1: opening product link tag. 2: closing link tag */
|
||||
__( 'Print shipping labels with %1$sWooCommerce Shipping%2$s', 'woocommerce' ),
|
||||
'<a href="https://woocommerce.com/products/shipping" target="_blank">',
|
||||
'</a>'
|
||||
),
|
||||
'is_visible' => [
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'US',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'not',
|
||||
'operand' => [
|
||||
[
|
||||
'type' => 'plugins_activated',
|
||||
'plugins' => [ 'woocommerce-services' ],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'type' => 'or',
|
||||
'operands' => [
|
||||
[
|
||||
[
|
||||
'type' => 'option',
|
||||
'transformers' => [
|
||||
[
|
||||
'use' => 'dot_notation',
|
||||
'arguments' => [
|
||||
'path' => 'product_types',
|
||||
],
|
||||
],
|
||||
[
|
||||
'use' => 'count',
|
||||
],
|
||||
],
|
||||
'option_name' => 'woocommerce_onboarding_profile',
|
||||
'value' => 1,
|
||||
'default' => '',
|
||||
'operation' => '!=',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'type' => 'option',
|
||||
'transformers' => [
|
||||
[
|
||||
'use' => 'dot_notation',
|
||||
'arguments' => [
|
||||
'path' => 'product_types.0',
|
||||
],
|
||||
],
|
||||
],
|
||||
'option_name' => 'woocommerce_onboarding_profile',
|
||||
'value' => 'downloads',
|
||||
'default' => '',
|
||||
'operation' => '!=',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'woocommerce-services:tax',
|
||||
'description' => sprintf(
|
||||
/* translators: 1: opening product link tag. 2: closing link tag */
|
||||
__( 'Get automated sales tax with %1$sWooCommerce Tax%2$s', 'woocommerce' ),
|
||||
'<a href="https://woocommerce.com/products/tax" target="_blank">',
|
||||
'</a>'
|
||||
),
|
||||
'is_visible' => [
|
||||
[
|
||||
'type' => 'or',
|
||||
'operands' => [
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'US',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'FR',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'GB',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'DE',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'CA',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'AU',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'GR',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'BE',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'PT',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'DK',
|
||||
'operation' => '=',
|
||||
],
|
||||
[
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'SE',
|
||||
'operation' => '=',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'type' => 'not',
|
||||
'operand' => [
|
||||
[
|
||||
'type' => 'plugins_activated',
|
||||
'plugins' => [ 'woocommerce-services' ],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'jetpack',
|
||||
'description' => sprintf(
|
||||
/* translators: 1: opening product link tag. 2: closing link tag */
|
||||
__( 'Enhance speed and security with %1$sJetpack%2$s', 'woocommerce' ),
|
||||
'<a href="https://woocommerce.com/products/jetpack" target="_blank">',
|
||||
'</a>'
|
||||
),
|
||||
'is_visible' => [
|
||||
[
|
||||
'type' => 'not',
|
||||
'operand' => [
|
||||
[
|
||||
'type' => 'plugins_activated',
|
||||
'plugins' => [ 'jetpack' ],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'mailpoet',
|
||||
'name' => __( 'MailPoet', 'woocommerce' ),
|
||||
'description' => sprintf(
|
||||
/* translators: 1: opening product link tag. 2: closing link tag */
|
||||
__( 'Level up your email marketing with %1$sMailPoet%2$s', 'woocommerce' ),
|
||||
'<a href="https://woocommerce.com/products/mailpoet" target="_blank">',
|
||||
'</a>'
|
||||
),
|
||||
'manage_url' => 'admin.php?page=mailpoet-newsletters',
|
||||
'is_visible' => [
|
||||
[
|
||||
'type' => 'not',
|
||||
'operand' => [
|
||||
[
|
||||
'type' => 'plugins_activated',
|
||||
'plugins' => [ 'mailpoet' ],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'reach',
|
||||
'title' => __( 'Reach out to customers', 'woocommerce' ),
|
||||
'plugins' => [
|
||||
[
|
||||
'key' => 'mailpoet',
|
||||
'name' => __( 'MailPoet', 'woocommerce' ),
|
||||
'description' => __( 'Create and send purchase follow-up emails, newsletters, and promotional campaigns straight from your dashboard.', 'woocommerce' ),
|
||||
'image_url' => plugins_url( 'images/onboarding/mailpoet.png', WC_ADMIN_PLUGIN_FILE ),
|
||||
'manage_url' => 'admin.php?page=mailpoet-newsletters',
|
||||
],
|
||||
[
|
||||
'key' => 'mailchimp-for-woocommerce',
|
||||
'name' => __( 'Mailchimp', 'woocommerce' ),
|
||||
'description' => __( 'Send targeted campaigns, recover abandoned carts and much more with Mailchimp.', 'woocommerce' ),
|
||||
'image_url' => plugins_url( 'images/onboarding/mailchimp-for-woocommerce.png', WC_ADMIN_PLUGIN_FILE ),
|
||||
'manage_url' => 'admin.php?page=mailchimp-woocommerce',
|
||||
],
|
||||
[
|
||||
'key' => 'creative-mail-by-constant-contact',
|
||||
'name' => __( 'Creative Mail for WooCommerce', 'woocommerce' ),
|
||||
'description' => __( 'Create on-brand store campaigns, fast email promotions and customer retargeting with Creative Mail.', 'woocommerce' ),
|
||||
'image_url' => plugins_url( 'images/onboarding/creative-mail-by-constant-contact.png', WC_ADMIN_PLUGIN_FILE ),
|
||||
'manage_url' => 'admin.php?page=creativemail',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'grow',
|
||||
'title' => __( 'Grow your store', 'woocommerce' ),
|
||||
'plugins' => [
|
||||
[
|
||||
'key' => 'google-listings-and-ads',
|
||||
'name' => __( 'Google Listings & Ads', 'woocommerce' ),
|
||||
'description' => __( 'Reach more shoppers and drive sales for your store. Integrate with Google to list your products for free and launch paid ad campaigns.', 'woocommerce' ),
|
||||
'image_url' => plugins_url( 'images/onboarding/google-listings-and-ads.png', WC_ADMIN_PLUGIN_FILE ),
|
||||
'manage_url' => 'admin.php?page=wc-admin&path=%2Fgoogle%2Fstart',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$bundles = wp_json_encode( $bundles );
|
||||
return json_decode( $bundles );
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* Evaluates the spec and returns a status.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\RemoteFreeExtensions;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\PluginsHelper;
|
||||
use Automattic\WooCommerce\Admin\RemoteInboxNotifications\RuleEvaluator;
|
||||
|
||||
/**
|
||||
* Evaluates the extension and returns it.
|
||||
*/
|
||||
class EvaluateExtension {
|
||||
/**
|
||||
* Evaluates the extension and returns it.
|
||||
*
|
||||
* @param object $extension The extension to evaluate.
|
||||
* @return object The evaluated extension.
|
||||
*/
|
||||
public static function evaluate( $extension ) {
|
||||
$rule_evaluator = new RuleEvaluator();
|
||||
|
||||
if ( isset( $extension->is_visible ) ) {
|
||||
$is_visible = $rule_evaluator->evaluate( $extension->is_visible );
|
||||
$extension->is_visible = $is_visible;
|
||||
} else {
|
||||
$extension->is_visible = true;
|
||||
}
|
||||
|
||||
$installed_plugins = PluginsHelper::get_installed_plugin_slugs();
|
||||
$activated_plugins = PluginsHelper::get_active_plugin_slugs();
|
||||
$extension->is_installed = in_array( explode( ':', $extension->key )[0], $installed_plugins, true );
|
||||
$extension->is_activated = in_array( explode( ':', $extension->key )[0], $activated_plugins, true );
|
||||
|
||||
return $extension;
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/**
|
||||
* Handles running payment method specs
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\RemoteFreeExtensions;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\RemoteInboxNotifications\SpecRunner;
|
||||
use Automattic\WooCommerce\Admin\Features\RemoteFreeExtensions\DefaultFreeExtensions;
|
||||
|
||||
/**
|
||||
* Remote Payment Methods engine.
|
||||
* This goes through the specs and gets eligible payment methods.
|
||||
*/
|
||||
class Init {
|
||||
const SPECS_TRANSIENT_NAME = 'woocommerce_admin_remote_free_extensions_specs';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'change_locale', array( __CLASS__, 'delete_specs_transient' ) );
|
||||
add_action( 'woocommerce_admin_updated', array( __CLASS__, 'delete_specs_transient' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Go through the specs and run them.
|
||||
*
|
||||
* @param array $allowed_bundles Optional array of allowed bundles to be returned.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_extensions( $allowed_bundles = array() ) {
|
||||
$bundles = array();
|
||||
$specs = self::get_specs();
|
||||
|
||||
foreach ( $specs as $spec ) {
|
||||
$spec = (object) $spec;
|
||||
$bundle = (array) $spec;
|
||||
$bundle['plugins'] = array();
|
||||
|
||||
if ( ! empty( $allowed_bundles ) && ! in_array( $spec->key, $allowed_bundles, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( $spec->plugins as $plugin ) {
|
||||
$extension = EvaluateExtension::evaluate( (object) $plugin );
|
||||
|
||||
if ( ! property_exists( $extension, 'is_visible' ) || $extension->is_visible ) {
|
||||
$bundle['plugins'][] = $extension;
|
||||
}
|
||||
}
|
||||
|
||||
$bundles[] = $bundle;
|
||||
}
|
||||
|
||||
return $bundles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the specs transient.
|
||||
*/
|
||||
public static function delete_specs_transient() {
|
||||
delete_transient( self::SPECS_TRANSIENT_NAME );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specs or fetch remotely if they don't exist.
|
||||
*/
|
||||
public static function get_specs() {
|
||||
$specs = get_transient( self::SPECS_TRANSIENT_NAME );
|
||||
|
||||
// Fetch specs if they don't yet exist.
|
||||
if ( false === $specs || ! is_array( $specs ) || 0 === count( $specs ) ) {
|
||||
if ( 'no' === get_option( 'woocommerce_show_marketplace_suggestions', 'yes' ) ) {
|
||||
return DefaultFreeExtensions::get_all();
|
||||
}
|
||||
|
||||
$specs = DataSourcePoller::read_specs_from_data_sources();
|
||||
|
||||
// Fall back to default specs if polling failed.
|
||||
if ( ! $specs || empty( $specs ) ) {
|
||||
return DefaultFreeExtensions::get_all();
|
||||
}
|
||||
|
||||
set_transient( self::SPECS_TRANSIENT_NAME, $specs, 7 * DAY_IN_SECONDS );
|
||||
}
|
||||
|
||||
return $specs;
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* Remote Inbox Notifications feature.
|
||||
* NOTE: DO NOT edit this file in WooCommerce core, this is generated from woocommerce-admin.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use \Automattic\WooCommerce\Admin\RemoteInboxNotifications\RemoteInboxNotificationsEngine;
|
||||
|
||||
/**
|
||||
* Remote Inbox Notifications feature logic.
|
||||
*/
|
||||
class RemoteInboxNotifications {
|
||||
/**
|
||||
* Option name used to toggle this feature.
|
||||
*/
|
||||
const TOGGLE_OPTION_NAME = 'woocommerce_show_marketplace_suggestions';
|
||||
|
||||
/**
|
||||
* Class instance.
|
||||
*
|
||||
* @var RemoteInboxNotifications 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 ( Features::is_enabled( 'remote-inbox-notifications' ) ) {
|
||||
RemoteInboxNotificationsEngine::init();
|
||||
}
|
||||
}
|
||||
}
|
179
packages/woocommerce-admin/src/Features/Settings.php
Normal file
179
packages/woocommerce-admin/src/Features/Settings.php
Normal file
@ -0,0 +1,179 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Settings.
|
||||
* NOTE: DO NOT edit this file in WooCommerce core, this is generated from woocommerce-admin.
|
||||
*/
|
||||
|
||||
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 Settings feature.
|
||||
*/
|
||||
class Settings {
|
||||
/**
|
||||
* Option name used to toggle this feature.
|
||||
*/
|
||||
const TOGGLE_OPTION_NAME = 'woocommerce_settings_enabled';
|
||||
|
||||
/**
|
||||
* Class instance.
|
||||
*
|
||||
* @var Settings 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;
|
||||
}
|
||||
|
||||
add_filter( 'woocommerce_settings_features', array( $this, 'add_feature_toggle' ) );
|
||||
|
||||
if ( 'yes' !== get_option( 'woocommerce_settings_enabled', 'no' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_filter( 'woocommerce_admin_shared_settings', array( __CLASS__, 'add_component_settings' ) );
|
||||
// Run this after the original WooCommerce settings have been added.
|
||||
add_action( 'admin_menu', array( $this, 'register_pages' ), 60 );
|
||||
add_action( 'init', array( $this, 'redirect_core_settings_pages' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the necessary data to initially load the WooCommerce Settings pages.
|
||||
*
|
||||
* @param array $settings Array of component settings.
|
||||
* @return array Array of component settings.
|
||||
*/
|
||||
public static function add_component_settings( $settings ) {
|
||||
if ( ! is_admin() ) {
|
||||
return $settings;
|
||||
}
|
||||
|
||||
$setting_pages = \WC_Admin_Settings::get_settings_pages();
|
||||
$pages = array();
|
||||
foreach ( $setting_pages as $setting_page ) {
|
||||
$pages = $setting_page->add_settings_page( $pages );
|
||||
}
|
||||
|
||||
$settings['settingsPages'] = $pages;
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the feature toggle to the features settings.
|
||||
*
|
||||
* @param array $features Feature sections.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_feature_toggle( $features ) {
|
||||
$features[] = array(
|
||||
'title' => __( 'Settings', 'woocommerce' ),
|
||||
'desc' => __(
|
||||
'Adds the new WooCommerce settings UI.',
|
||||
'woocommerce'
|
||||
),
|
||||
'id' => 'woocommerce_settings_enabled',
|
||||
'type' => 'checkbox',
|
||||
);
|
||||
|
||||
return $features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers settings pages.
|
||||
*/
|
||||
public function register_pages() {
|
||||
$controller = PageController::get_instance();
|
||||
|
||||
$setting_pages = \WC_Admin_Settings::get_settings_pages();
|
||||
$settings = array();
|
||||
foreach ( $setting_pages as $setting_page ) {
|
||||
$settings = $setting_page->add_settings_page( $settings );
|
||||
}
|
||||
|
||||
$order = 0;
|
||||
foreach ( $settings as $key => $setting ) {
|
||||
$order += 10;
|
||||
$settings_page = array(
|
||||
'parent' => 'woocommerce-settings',
|
||||
'title' => $setting,
|
||||
'id' => 'settings-' . $key,
|
||||
'path' => "/settings/$key",
|
||||
'nav_args' => array(
|
||||
'capability' => 'manage_woocommerce',
|
||||
'order' => $order,
|
||||
'parent' => 'woocommerce-settings',
|
||||
),
|
||||
);
|
||||
|
||||
// Replace the old menu with the first settings item.
|
||||
if ( 10 === $order ) {
|
||||
$this->replace_settings_page( $settings_page );
|
||||
}
|
||||
|
||||
$controller->register_page( $settings_page );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the Settings page in the original WooCommerce menu.
|
||||
*
|
||||
* @param array $page Page used to replace the original.
|
||||
*/
|
||||
protected function replace_settings_page( $page ) {
|
||||
global $submenu;
|
||||
|
||||
// Check if WooCommerce parent menu has been registered.
|
||||
if ( ! isset( $submenu['woocommerce'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $submenu['woocommerce'] as &$item ) {
|
||||
// The "slug" (aka the path) is the third item in the array.
|
||||
if ( 0 === strpos( $item[2], 'wc-settings' ) ) {
|
||||
$item[2] = wc_admin_url( "&path={$page['path']}" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect the old settings page URLs to the new ones.
|
||||
*/
|
||||
public function redirect_core_settings_pages() {
|
||||
/* phpcs:disable WordPress.Security.NonceVerification */
|
||||
if ( ! isset( $_GET['page'] ) || 'wc-settings' !== $_GET['page'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$setting_pages = \WC_Admin_Settings::get_settings_pages();
|
||||
$default_setting = isset( $setting_pages[0] ) ? $setting_pages[0]->get_id() : '';
|
||||
$setting = isset( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : $default_setting;
|
||||
/* phpcs:enable */
|
||||
|
||||
wp_safe_redirect( wc_admin_url( "&path=/settings/$setting" ) );
|
||||
exit;
|
||||
}
|
||||
}
|
171
packages/woocommerce-admin/src/Features/ShippingLabelBanner.php
Normal file
171
packages/woocommerce-admin/src/Features/ShippingLabelBanner.php
Normal file
@ -0,0 +1,171 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Shipping Label banner.
|
||||
* NOTE: DO NOT edit this file in WooCommerce core, this is generated from woocommerce-admin.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features;
|
||||
|
||||
use \Automattic\WooCommerce\Admin\Loader;
|
||||
use \Automattic\Jetpack\Connection\Manager as Jetpack_Connection_Manager;
|
||||
|
||||
/**
|
||||
* Shows print shipping label banner on edit order page.
|
||||
*/
|
||||
class ShippingLabelBanner {
|
||||
|
||||
/**
|
||||
* Singleton for the display rules class
|
||||
*
|
||||
* @var ShippingLabelBannerDisplayRules
|
||||
*/
|
||||
private $shipping_label_banner_display_rules;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( ! is_admin() ) {
|
||||
return;
|
||||
}
|
||||
add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ), 6, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if WooCommerce Shipping makes sense for this merchant.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function should_show_meta_box() {
|
||||
if ( ! $this->shipping_label_banner_display_rules ) {
|
||||
$jetpack_version = null;
|
||||
$jetpack_connected = null;
|
||||
$wcs_version = null;
|
||||
$wcs_tos_accepted = null;
|
||||
|
||||
if ( defined( 'JETPACK__VERSION' ) ) {
|
||||
$jetpack_version = JETPACK__VERSION;
|
||||
}
|
||||
|
||||
if ( class_exists( Jetpack_Connection_Manager::class ) ) {
|
||||
$jetpack_connected = ( new Jetpack_Connection_Manager() )->is_active();
|
||||
}
|
||||
|
||||
if ( class_exists( '\WC_Connect_Loader' ) ) {
|
||||
$wcs_version = \WC_Connect_Loader::get_wcs_version();
|
||||
}
|
||||
|
||||
if ( class_exists( '\WC_Connect_Options' ) ) {
|
||||
$wcs_tos_accepted = \WC_Connect_Options::get_option( 'tos_accepted' );
|
||||
}
|
||||
|
||||
$incompatible_plugins = class_exists( '\WC_Shipping_Fedex_Init' ) ||
|
||||
class_exists( '\WC_Shipping_UPS_Init' ) ||
|
||||
class_exists( '\WC_Integration_ShippingEasy' ) ||
|
||||
class_exists( '\WC_ShipStation_Integration' );
|
||||
|
||||
$this->shipping_label_banner_display_rules =
|
||||
new ShippingLabelBannerDisplayRules(
|
||||
$jetpack_version,
|
||||
$jetpack_connected,
|
||||
$wcs_version,
|
||||
$wcs_tos_accepted,
|
||||
$incompatible_plugins
|
||||
);
|
||||
}
|
||||
|
||||
return $this->shipping_label_banner_display_rules->should_display_banner();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add metabox to order page.
|
||||
*
|
||||
* @param string $post_type current post type.
|
||||
* @param \WP_Post $post Current post object.
|
||||
*/
|
||||
public function add_meta_boxes( $post_type, $post ) {
|
||||
$order = wc_get_order( $post );
|
||||
if ( $this->should_show_meta_box() ) {
|
||||
add_meta_box(
|
||||
'woocommerce-admin-print-label',
|
||||
__( 'Shipping Label', 'woocommerce' ),
|
||||
array( $this, 'meta_box' ),
|
||||
null,
|
||||
'normal',
|
||||
'high',
|
||||
array(
|
||||
'context' => 'shipping_label',
|
||||
'order' => $post->ID,
|
||||
'items' => $this->count_shippable_items( $order ),
|
||||
)
|
||||
);
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'add_print_shipping_label_script' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count shippable items
|
||||
*
|
||||
* @param \WC_Order $order Current order.
|
||||
* @return int
|
||||
*/
|
||||
private function count_shippable_items( \WC_Order $order ) {
|
||||
$count = 0;
|
||||
foreach ( $order->get_items() as $item ) {
|
||||
if ( $item instanceof \WC_Order_Item_Product ) {
|
||||
$product = $item->get_product();
|
||||
if ( $product && $product->needs_shipping() ) {
|
||||
$count += $item->get_quantity();
|
||||
}
|
||||
}
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
/**
|
||||
* Adds JS to order page to render shipping banner.
|
||||
*
|
||||
* @param string $hook current page hook.
|
||||
*/
|
||||
public function add_print_shipping_label_script( $hook ) {
|
||||
$rtl = is_rtl() ? '.rtl' : '';
|
||||
wp_enqueue_style(
|
||||
'print-shipping-label-banner-style',
|
||||
Loader::get_url( "print-shipping-label-banner/style{$rtl}", 'css' ),
|
||||
array( 'wp-components' ),
|
||||
Loader::get_file_version( 'css' )
|
||||
);
|
||||
|
||||
$script_assets_filename = Loader::get_script_asset_filename( 'wp-admin-scripts', 'print-shipping-label-banner' );
|
||||
$script_assets = require WC_ADMIN_ABSPATH . WC_ADMIN_DIST_JS_FOLDER . 'wp-admin-scripts/' . $script_assets_filename;
|
||||
|
||||
wp_enqueue_script(
|
||||
'print-shipping-label-banner',
|
||||
Loader::get_url( 'wp-admin-scripts/print-shipping-label-banner', 'js' ),
|
||||
array_merge( array( WC_ADMIN_APP ), $script_assets ['dependencies'] ),
|
||||
Loader::get_file_version( 'js' ),
|
||||
true
|
||||
);
|
||||
|
||||
$payload = array(
|
||||
'nonce' => wp_create_nonce( 'wp_rest' ),
|
||||
'baseURL' => get_rest_url(),
|
||||
'wcs_server_connection' => true,
|
||||
);
|
||||
|
||||
wp_localize_script( 'print-shipping-label-banner', 'wcConnectData', $payload );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render placeholder metabox.
|
||||
*
|
||||
* @param \WP_Post $post current post.
|
||||
* @param array $args empty args.
|
||||
*/
|
||||
public function meta_box( $post, $args ) {
|
||||
|
||||
?>
|
||||
<div id="wc-admin-shipping-banner-root" class="woocommerce <?php echo esc_attr( 'wc-admin-shipping-banner' ); ?>" data-args="<?php echo esc_attr( wp_json_encode( $args['args'] ) ); ?>">
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
@ -0,0 +1,208 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Shipping Label Banner Display Rules.
|
||||
* NOTE: DO NOT edit this file in WooCommerce core, this is generated from woocommerce-admin.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features;
|
||||
|
||||
/**
|
||||
* Determines whether or not the Shipping Label Banner should be displayed
|
||||
*/
|
||||
class ShippingLabelBannerDisplayRules {
|
||||
|
||||
/**
|
||||
* Holds the installed Jetpack version.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $jetpack_version;
|
||||
|
||||
|
||||
/**
|
||||
* Whether or not the installed Jetpack is connected.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $jetpack_connected;
|
||||
|
||||
/**
|
||||
* Holds the installed WooCommerce Shipping & Tax version.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $wcs_version;
|
||||
|
||||
/**
|
||||
* Whether or not there're plugins installed incompatible with the banner.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $no_incompatible_plugins_installed;
|
||||
|
||||
/**
|
||||
* Whether or not the WooCommerce Shipping & Tax ToS has been accepted.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $wcs_tos_accepted;
|
||||
|
||||
/**
|
||||
* Minimum supported Jetpack version.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $min_jetpack_version = '4.4';
|
||||
|
||||
/**
|
||||
* Minimum supported WooCommerce Shipping & Tax version.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $min_wcs_version = '1.22.5';
|
||||
|
||||
/**
|
||||
* Supported countries by USPS, see: https://webpmt.usps.gov/pmt010.cfm
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $supported_countries = array( 'US', 'AS', 'PR', 'VI', 'GU', 'MP', 'UM', 'FM', 'MH' );
|
||||
|
||||
/**
|
||||
* Array of supported currency codes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $supported_currencies = array( 'USD' );
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $jetpack_version Installed Jetpack version to check.
|
||||
* @param bool $jetpack_connected Is Jetpack connected?.
|
||||
* @param string $wcs_version Installed WooCommerce Shipping & Tax version to check.
|
||||
* @param bool $wcs_tos_accepted WooCommerce Shipping & Tax Terms of Service accepted?.
|
||||
* @param bool $incompatible_plugins_installed Are there any incompatible plugins installed?.
|
||||
*/
|
||||
public function __construct( $jetpack_version, $jetpack_connected, $wcs_version, $wcs_tos_accepted, $incompatible_plugins_installed ) {
|
||||
$this->jetpack_version = $jetpack_version;
|
||||
$this->jetpack_connected = $jetpack_connected;
|
||||
$this->wcs_version = $wcs_version;
|
||||
$this->wcs_tos_accepted = $wcs_tos_accepted;
|
||||
$this->no_incompatible_plugins_installed = ! $incompatible_plugins_installed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether banner is eligible for display (does not include a/b logic).
|
||||
*/
|
||||
public function should_display_banner() {
|
||||
return $this->banner_not_dismissed() &&
|
||||
$this->jetpack_installed_and_active() &&
|
||||
$this->jetpack_up_to_date() &&
|
||||
$this->jetpack_connected &&
|
||||
$this->no_incompatible_plugins_installed &&
|
||||
$this->order_has_shippable_products() &&
|
||||
$this->store_in_us_and_usd() &&
|
||||
( $this->wcs_not_installed() || (
|
||||
$this->wcs_up_to_date() && ! $this->wcs_tos_accepted
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the banner was not dismissed by the user.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function banner_not_dismissed() {
|
||||
$dismissed_timestamp_ms = get_option( 'woocommerce_shipping_dismissed_timestamp' );
|
||||
|
||||
if ( ! is_numeric( $dismissed_timestamp_ms ) ) {
|
||||
return true;
|
||||
}
|
||||
$dismissed_timestamp_ms = intval( $dismissed_timestamp_ms );
|
||||
$dismissed_timestamp = intval( round( $dismissed_timestamp_ms / 1000 ) );
|
||||
$expired_timestamp = $dismissed_timestamp + 24 * 60 * 60; // 24 hours from click time
|
||||
|
||||
$dismissed_for_good = -1 === $dismissed_timestamp_ms;
|
||||
$dismissed_24h = time() < $expired_timestamp;
|
||||
|
||||
return ! $dismissed_for_good && ! $dismissed_24h;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if jetpack is installed and active.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function jetpack_installed_and_active() {
|
||||
return ! ! $this->jetpack_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if Jetpack version is supported.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function jetpack_up_to_date() {
|
||||
return version_compare( $this->jetpack_version, $this->min_jetpack_version, '>=' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there's a shippable product in the current order.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function order_has_shippable_products() {
|
||||
$post = get_post();
|
||||
if ( ! $post ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$order = wc_get_order( get_post()->ID );
|
||||
|
||||
if ( ! $order ) {
|
||||
return false;
|
||||
}
|
||||
// At this point (no packaging data), only show if there's at least one existing and shippable product.
|
||||
foreach ( $order->get_items() as $item ) {
|
||||
if ( $item instanceof \WC_Order_Item_Product ) {
|
||||
$product = $item->get_product();
|
||||
|
||||
if ( $product && $product->needs_shipping() ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the store is in the US and has its default currency set to USD.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function store_in_us_and_usd() {
|
||||
$base_currency = get_woocommerce_currency();
|
||||
$base_location = wc_get_base_location();
|
||||
|
||||
return in_array( $base_currency, $this->supported_currencies, true ) && in_array( $base_location['country'], $this->supported_countries, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if WooCommerce Shipping & Tax is not installed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function wcs_not_installed() {
|
||||
return ! $this->wcs_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if WooCommerce Shipping & Tax is up to date.
|
||||
*/
|
||||
private function wcs_up_to_date() {
|
||||
return $this->wcs_version && version_compare( $this->wcs_version, $this->min_wcs_version, '>=' );
|
||||
}
|
||||
}
|
124
packages/woocommerce-admin/src/Features/TransientNotices.php
Normal file
124
packages/woocommerce-admin/src/Features/TransientNotices.php
Normal file
@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Transient Notices
|
||||
* NOTE: DO NOT edit this file in WooCommerce core, this is generated from woocommerce-admin.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features;
|
||||
|
||||
use \Automattic\WooCommerce\Admin\Loader;
|
||||
|
||||
/**
|
||||
* Shows print shipping label banner on edit order page.
|
||||
*/
|
||||
class TransientNotices {
|
||||
|
||||
/**
|
||||
* Option name for the queue.
|
||||
*/
|
||||
const QUEUE_OPTION = 'woocommerce_admin_transient_notices_queue';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
add_filter( 'woocommerce_admin_preload_options', array( $this, 'preload_options' ) );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get all notices in the queue.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_queue() {
|
||||
return get_option( self::QUEUE_OPTION, array() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all notices in the queue by a given user ID.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_queue_by_user( $user_id ) {
|
||||
$notices = self::get_queue();
|
||||
|
||||
return array_filter(
|
||||
$notices,
|
||||
function( $notice ) use ( $user_id ) {
|
||||
return ! isset( $notice['user_id'] ) ||
|
||||
null === $notice['user_id'] ||
|
||||
$user_id === $notice['user_id'];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a notice by ID.
|
||||
*
|
||||
* @param array $notice_id Notice of ID to get.
|
||||
* @return array|null
|
||||
*/
|
||||
public static function get( $notice_id ) {
|
||||
$queue = self::get_queue();
|
||||
|
||||
if ( isset( $queue[ $notice_id ] ) ) {
|
||||
return $queue[ $notice_id ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a notice to be shown.
|
||||
*
|
||||
* @param array $notice Notice.
|
||||
* $notice = array(
|
||||
* 'id' => (string) Unique ID for the notice. Required.
|
||||
* 'user_id' => (int|null) User ID to show the notice to.
|
||||
* 'status' => (string) info|error|success
|
||||
* 'content' => (string) Content to be shown for the notice. Required.
|
||||
* 'options' => (array) Array of options to be passed to the notice component.
|
||||
* See https://developer.wordpress.org/block-editor/reference-guides/data/data-core-notices/#createNotice for available options.
|
||||
* ).
|
||||
*/
|
||||
public static function add( $notice ) {
|
||||
$queue = self::get_queue();
|
||||
|
||||
$defaults = array(
|
||||
'user_id' => null,
|
||||
'status' => 'info',
|
||||
'options' => array(),
|
||||
);
|
||||
$notice_data = array_merge( $defaults, $notice );
|
||||
$notice_data['options'] = (object) $notice_data['options'];
|
||||
|
||||
$queue[ $notice['id'] ] = $notice_data;
|
||||
update_option( self::QUEUE_OPTION, $queue );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a notice by ID.
|
||||
*
|
||||
* @param array $notice_id Notice of ID to remove.
|
||||
*/
|
||||
public static function remove( $notice_id ) {
|
||||
$queue = self::get_queue();
|
||||
unset( $queue[ $notice_id ] );
|
||||
update_option( self::QUEUE_OPTION, $queue );
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload options to prime state of the application.
|
||||
*
|
||||
* @param array $options Array of options to preload.
|
||||
* @return array
|
||||
*/
|
||||
public function preload_options( $options ) {
|
||||
$options[] = self::QUEUE_OPTION;
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
<?php
|
||||
/**
|
||||
* Handles polling and storage of specs
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\WcPayPromotion;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Specs data source poller class.
|
||||
* This handles polling specs from JSON endpoints.
|
||||
*/
|
||||
class DataSourcePoller {
|
||||
/**
|
||||
* Name of data sources filter.
|
||||
*/
|
||||
const FILTER_NAME = 'woocommerce_admin_payment_method_promotions_data_sources';
|
||||
|
||||
/**
|
||||
* Default data sources array.
|
||||
*/
|
||||
const DATA_SOURCES = array(
|
||||
'https://woocommerce.com/wp-json/wccom/payment-gateway-suggestions/1.0/payment-method/promotions.json',
|
||||
);
|
||||
|
||||
/**
|
||||
* The logger instance.
|
||||
*
|
||||
* @var WC_Logger|null
|
||||
*/
|
||||
protected static $logger = null;
|
||||
|
||||
/**
|
||||
* Get the logger instance.
|
||||
*
|
||||
* @return WC_Logger
|
||||
*/
|
||||
private static function get_logger() {
|
||||
if ( is_null( self::$logger ) ) {
|
||||
self::$logger = wc_get_logger();
|
||||
}
|
||||
|
||||
return self::$logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the data sources for specs and persists those specs.
|
||||
*
|
||||
* @return bool Whether any specs were read.
|
||||
*/
|
||||
public static function read_specs_from_data_sources() {
|
||||
$specs = array();
|
||||
$data_sources = apply_filters( self::FILTER_NAME, self::DATA_SOURCES );
|
||||
|
||||
// Note that this merges the specs from the data sources based on the
|
||||
// product - last one wins.
|
||||
foreach ( $data_sources as $url ) {
|
||||
$specs_from_data_source = self::read_data_source( $url );
|
||||
self::merge_specs( $specs_from_data_source, $specs, $url );
|
||||
}
|
||||
|
||||
return $specs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a single data source and return the read specs
|
||||
*
|
||||
* @param string $url The URL to read the specs from.
|
||||
*
|
||||
* @return array The specs that have been read from the data source.
|
||||
*/
|
||||
private static function read_data_source( $url ) {
|
||||
$logger_context = array( 'source' => $url );
|
||||
$logger = self::get_logger();
|
||||
$response = wp_remote_get(
|
||||
add_query_arg(
|
||||
'_locale',
|
||||
get_user_locale(),
|
||||
$url
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) || ! isset( $response['body'] ) ) {
|
||||
$logger->error(
|
||||
'Error getting remote payment method data feed',
|
||||
$logger_context
|
||||
);
|
||||
// phpcs:ignore
|
||||
$logger->error( print_r( $response, true ), $logger_context );
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
$body = $response['body'];
|
||||
$specs = json_decode( $body );
|
||||
|
||||
if ( null === $specs ) {
|
||||
$logger->error(
|
||||
'Empty response in remote payment method data feed',
|
||||
$logger_context
|
||||
);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
if ( ! is_array( $specs ) ) {
|
||||
$logger->error(
|
||||
'Remote payment method data feed is not an array',
|
||||
$logger_context
|
||||
);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
return $specs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the specs.
|
||||
*
|
||||
* @param Array $specs_to_merge_in The specs to merge in to $specs.
|
||||
* @param Array $specs The list of specs being merged into.
|
||||
*/
|
||||
private static function merge_specs( $specs_to_merge_in, &$specs ) {
|
||||
foreach ( $specs_to_merge_in as $spec ) {
|
||||
$id = $spec->id;
|
||||
$specs[ $id ] = $spec;
|
||||
}
|
||||
}
|
||||
}
|
217
packages/woocommerce-admin/src/Features/WcPayPromotion/Init.php
Normal file
217
packages/woocommerce-admin/src/Features/WcPayPromotion/Init.php
Normal file
@ -0,0 +1,217 @@
|
||||
<?php
|
||||
/**
|
||||
* Handles wcpay promotion
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\WcPayPromotion;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Loader;
|
||||
use Automattic\WooCommerce\Admin\PaymentPlugins;
|
||||
use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\EvaluateSuggestion;
|
||||
|
||||
/**
|
||||
* WC Pay Promotion engine.
|
||||
*/
|
||||
class Init {
|
||||
const SPECS_TRANSIENT_NAME = 'woocommerce_admin_payment_method_promotion_specs';
|
||||
const EXPLAT_VARIATION_PREFIX = 'woocommerce_wc_pay_promotion_payment_methods_table_';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
include_once __DIR__ . '/WCPaymentGatewayPreInstallWCPayPromotion.php';
|
||||
|
||||
add_action( 'change_locale', array( __CLASS__, 'delete_specs_transient' ) );
|
||||
add_filter( PaymentPlugins::FILTER_NAME, array( __CLASS__, 'possibly_filter_recommended_payment_gateways' ) );
|
||||
|
||||
if ( ! isset( $_GET['page'] ) || 'wc-settings' !== $_GET['page'] || ! isset( $_GET['tab'] ) || 'checkout' !== $_GET['tab'] ) { // phpcs:ignore WordPress.Security.NonceVerification
|
||||
return;
|
||||
}
|
||||
|
||||
add_filter( 'woocommerce_payment_gateways', array( __CLASS__, 'possibly_register_pre_install_wc_pay_promotion_gateway' ) );
|
||||
add_filter( 'option_woocommerce_gateway_order', [ __CLASS__, 'set_gateway_top_of_list' ] );
|
||||
add_filter( 'default_option_woocommerce_gateway_order', [ __CLASS__, 'set_gateway_top_of_list' ] );
|
||||
|
||||
$rtl = is_rtl() ? '.rtl' : '';
|
||||
|
||||
wp_enqueue_style(
|
||||
'wc-admin-payment-method-promotions',
|
||||
Loader::get_url( "payment-method-promotions/style{$rtl}", 'css' ),
|
||||
array( 'wp-components' ),
|
||||
Loader::get_file_version( 'css' )
|
||||
);
|
||||
|
||||
$script_assets_filename = Loader::get_script_asset_filename( 'wp-admin-scripts', 'payment-method-promotions' );
|
||||
$script_assets = require WC_ADMIN_ABSPATH . WC_ADMIN_DIST_JS_FOLDER . 'wp-admin-scripts/' . $script_assets_filename;
|
||||
|
||||
wp_enqueue_script(
|
||||
'wc-admin-payment-method-promotions',
|
||||
Loader::get_url( 'wp-admin-scripts/payment-method-promotions', 'js' ),
|
||||
array_merge( array( WC_ADMIN_APP ), $script_assets ['dependencies'] ),
|
||||
Loader::get_file_version( 'js' ),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Possibly registers the pre install wc pay promoted gateway.
|
||||
*
|
||||
* @param array $gateways list of gateway classes.
|
||||
* @return array list of gateway classes.
|
||||
*/
|
||||
public static function possibly_register_pre_install_wc_pay_promotion_gateway( $gateways ) {
|
||||
if ( self::should_register_pre_install_wc_pay_promoted_gateway() ) {
|
||||
$gateways[] = 'Automattic\WooCommerce\Admin\Features\WCPayPromotion\WCPaymentGatewayPreInstallWCPayPromotion';
|
||||
}
|
||||
return $gateways;
|
||||
}
|
||||
|
||||
/**
|
||||
* Possibly filters out woocommerce-payments from recommended payment methods.
|
||||
*
|
||||
* @param array $payment_methods list of payment methods.
|
||||
* @return array list of payment method.
|
||||
*/
|
||||
public static function possibly_filter_recommended_payment_gateways( $payment_methods ) {
|
||||
if ( self::should_register_pre_install_wc_pay_promoted_gateway() ) {
|
||||
return array_filter(
|
||||
$payment_methods,
|
||||
function( $payment_method ) {
|
||||
return 'woocommerce-payments' !== $payment_method['product'];
|
||||
}
|
||||
);
|
||||
}
|
||||
return $payment_methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if promoted gateway should be registered.
|
||||
*
|
||||
* @return boolean if promoted gateway should be registered.
|
||||
*/
|
||||
public static function should_register_pre_install_wc_pay_promoted_gateway() {
|
||||
// Check if WC Pay is enabled.
|
||||
if ( class_exists( '\WC_Payments' ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( 'no' === get_option( 'woocommerce_show_marketplace_suggestions', 'yes' ) ) {
|
||||
return false;
|
||||
}
|
||||
$wc_pay_spec = self::get_wc_pay_promotion_spec();
|
||||
|
||||
if ( ! $wc_pay_spec || ! isset( $wc_pay_spec->additional_info ) || ! isset( $wc_pay_spec->additional_info->experiment_version ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$anon_id = isset( $_COOKIE['tk_ai'] ) ? sanitize_text_field( wp_unslash( $_COOKIE['tk_ai'] ) ) : '';
|
||||
$allow_tracking = 'yes' === get_option( 'woocommerce_allow_tracking' );
|
||||
$abtest = new \WooCommerce\Admin\Experimental_Abtest(
|
||||
$anon_id,
|
||||
'woocommerce',
|
||||
$allow_tracking
|
||||
);
|
||||
|
||||
$variation_name = $abtest->get_variation( self::EXPLAT_VARIATION_PREFIX . $wc_pay_spec->additional_info->experiment_version );
|
||||
|
||||
if ( 'treatment' === $variation_name ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* By default, new payment gateways are put at the bottom of the list on the admin "Payments" settings screen.
|
||||
* For visibility, we want WooCommerce Payments to be at the top of the list.
|
||||
*
|
||||
* @param array $ordering Existing ordering of the payment gateways.
|
||||
*
|
||||
* @return array Modified ordering.
|
||||
*/
|
||||
public static function set_gateway_top_of_list( $ordering ) {
|
||||
$ordering = (array) $ordering;
|
||||
$id = WCPaymentGatewayPreInstallWCPayPromotion::GATEWAY_ID;
|
||||
// Only tweak the ordering if the list hasn't been reordered with WooCommerce Payments in it already.
|
||||
if ( ! isset( $ordering[ $id ] ) || ! is_numeric( $ordering[ $id ] ) ) {
|
||||
$is_empty = empty( $ordering ) || empty( $ordering[0] );
|
||||
$ordering[ $id ] = $is_empty ? 0 : ( min( $ordering ) - 1 );
|
||||
}
|
||||
return $ordering;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get WC Pay promotion spec.
|
||||
*/
|
||||
public static function get_wc_pay_promotion_spec() {
|
||||
$promotions = self::get_promotions();
|
||||
$wc_pay_promotion_spec = array_values(
|
||||
array_filter(
|
||||
$promotions,
|
||||
function( $promotion ) {
|
||||
return isset( $promotion->plugins ) && in_array( 'woocommerce-payments', $promotion->plugins, true );
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
return current( $wc_pay_promotion_spec );
|
||||
}
|
||||
|
||||
/**
|
||||
* Go through the specs and run them.
|
||||
*/
|
||||
public static function get_promotions() {
|
||||
$suggestions = array();
|
||||
$specs = self::get_specs();
|
||||
|
||||
foreach ( $specs as $spec ) {
|
||||
$suggestion = EvaluateSuggestion::evaluate( $spec );
|
||||
$suggestions[] = $suggestion;
|
||||
}
|
||||
|
||||
return array_values(
|
||||
array_filter(
|
||||
$suggestions,
|
||||
function( $suggestion ) {
|
||||
return ! property_exists( $suggestion, 'is_visible' ) || $suggestion->is_visible;
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the specs transient.
|
||||
*/
|
||||
public static function delete_specs_transient() {
|
||||
delete_transient( self::SPECS_TRANSIENT_NAME );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specs or fetch remotely if they don't exist.
|
||||
*/
|
||||
public static function get_specs() {
|
||||
$specs = get_transient( self::SPECS_TRANSIENT_NAME );
|
||||
|
||||
// Fetch specs if they don't yet exist.
|
||||
if ( false === $specs || ! is_array( $specs ) || 0 === count( $specs ) ) {
|
||||
if ( 'no' === get_option( 'woocommerce_show_marketplace_suggestions', 'yes' ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$specs = DataSourcePoller::read_specs_from_data_sources();
|
||||
|
||||
// Fall back to default specs if polling failed.
|
||||
if ( ! $specs ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
set_transient( self::SPECS_TRANSIENT_NAME, $specs, 7 * DAY_IN_SECONDS );
|
||||
}
|
||||
|
||||
return $specs;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WCPaymentGatewayPreInstallWCPayPromotion
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\WcPayPromotion;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* A Psuedo WCPay gateway class.
|
||||
*
|
||||
* @extends WC_Payment_Gateway
|
||||
*/
|
||||
class WCPaymentGatewayPreInstallWCPayPromotion extends \WC_Payment_Gateway {
|
||||
|
||||
const GATEWAY_ID = 'pre_install_woocommerce_payments_promotion';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
$wc_pay_spec = Init::get_wc_pay_promotion_spec();
|
||||
$this->id = static::GATEWAY_ID;
|
||||
$this->method_title = $wc_pay_spec->title;
|
||||
if ( property_exists( $wc_pay_spec, 'sub_title' ) ) {
|
||||
$this->title = sprintf( '<span class="gateway-subtitle" >%s</span>', $wc_pay_spec->sub_title );
|
||||
}
|
||||
$this->method_description = $wc_pay_spec->content;
|
||||
$this->has_fields = false;
|
||||
|
||||
// Get setting values.
|
||||
$this->enabled = false;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user