plugin install
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Async Product Editor Category Field.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\AsyncProductEditorCategoryField;
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Internal\Admin\WCAdminAssets;
|
||||
use Automattic\WooCommerce\Admin\PageController;
|
||||
|
||||
/**
|
||||
* Loads assets related to the async category field for the product editor.
|
||||
*/
|
||||
class Init {
|
||||
|
||||
const FEATURE_ID = 'async-product-editor-category-field';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( Features::is_enabled( self::FEATURE_ID ) ) {
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_styles' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
|
||||
add_filter( 'woocommerce_taxonomy_args_product_cat', array( $this, 'add_metabox_args' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds meta_box_cb callback arguments for custom metabox.
|
||||
*
|
||||
* @param array $args Category taxonomy args.
|
||||
* @return array $args category taxonomy args.
|
||||
*/
|
||||
public function add_metabox_args( $args ) {
|
||||
if ( ! isset( $args['meta_box_cb'] ) ) {
|
||||
$args['meta_box_cb'] = 'WC_Meta_Box_Product_Categories::output';
|
||||
$args['meta_box_sanitize_cb'] = 'taxonomy_meta_box_sanitize_cb_checkboxes';
|
||||
}
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue scripts needed for the product form block editor.
|
||||
*/
|
||||
public function enqueue_scripts() {
|
||||
if ( ! PageController::is_embed_page() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
WCAdminAssets::register_script( 'wp-admin-scripts', 'product-category-metabox', true );
|
||||
wp_localize_script(
|
||||
'wc-admin-product-category-metabox',
|
||||
'wc_product_category_metabox_params',
|
||||
array(
|
||||
'search_categories_nonce' => wp_create_nonce( 'search-categories' ),
|
||||
'search_taxonomy_terms_nonce' => wp_create_nonce( 'search-taxonomy-terms' ),
|
||||
)
|
||||
);
|
||||
wp_enqueue_script( 'product-category-metabox' );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue styles needed for the rich text editor.
|
||||
*/
|
||||
public function enqueue_styles() {
|
||||
if ( ! PageController::is_embed_page() ) {
|
||||
return;
|
||||
}
|
||||
$version = Constants::get_constant( 'WC_VERSION' );
|
||||
|
||||
wp_register_style(
|
||||
'woocommerce_admin_product_category_metabox_styles',
|
||||
WCAdminAssets::get_url( 'product-category-metabox/style', 'css' ),
|
||||
array(),
|
||||
$version
|
||||
);
|
||||
wp_style_add_data( 'woocommerce_admin_product_category_metabox_styles', 'rtl', 'replace' );
|
||||
|
||||
wp_enqueue_style( 'woocommerce_admin_product_category_metabox_styles' );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
<?php
|
||||
/**
|
||||
* Features loader for features developed in WooCommerce Admin.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features;
|
||||
|
||||
use Automattic\WooCommerce\Admin\PageController;
|
||||
use Automattic\WooCommerce\Internal\Admin\Loader;
|
||||
use Automattic\WooCommerce\Internal\Admin\WCAdminAssets;
|
||||
|
||||
/**
|
||||
* 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',
|
||||
'new-product-management-experience',
|
||||
'settings',
|
||||
);
|
||||
|
||||
/**
|
||||
* Get class instance.
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( ! self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->register_internal_class_aliases();
|
||||
// Load feature before WooCommerce update hooks.
|
||||
add_action( 'init', array( __CLASS__, 'load_features' ), 4 );
|
||||
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 = array();
|
||||
|
||||
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 = array();
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @deprecated 7.0 The WooCommerce Admin features are now handled by the WooCommerce features engine (see the FeaturesController class).
|
||||
*
|
||||
* @param array $sections Sections.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_features_section( $sections ) {
|
||||
return $sections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the Features settings.
|
||||
*
|
||||
* @deprecated 7.0 The WooCommerce Admin features are now handled by the WooCommerce features engine (see the FeaturesController class).
|
||||
*
|
||||
* @param array $settings Settings.
|
||||
* @param string $current_section Current section slug.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_features_settings( $settings, $current_section ) {
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
WCAdminAssets::register_style( 'beta-features-tracking-modal', 'style', array( 'wp-components' ) );
|
||||
WCAdminAssets::register_script( 'wp-admin-scripts', 'beta-features-tracking-modal', array( 'wp-i18n', 'wp-element', WC_ADMIN_APP ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the required scripts on the correct pages.
|
||||
*/
|
||||
public static function load_scripts() {
|
||||
if ( ! PageController::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 ( ! PageController::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 ";
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias internal features classes to make them backward compatible.
|
||||
* We've moved our feature classes to src-internal as part of merging this
|
||||
* repository with WooCommerce Core to form a monorepo.
|
||||
* See https://wp.me/p90Yrv-2HY for details.
|
||||
*/
|
||||
private function register_internal_class_aliases() {
|
||||
$aliases = array(
|
||||
// new class => original class (this will be aliased).
|
||||
'Automattic\WooCommerce\Internal\Admin\WCPayPromotion\Init' => 'Automattic\WooCommerce\Admin\Features\WcPayPromotion\Init',
|
||||
'Automattic\WooCommerce\Internal\Admin\RemoteFreeExtensions\Init' => 'Automattic\WooCommerce\Admin\Features\RemoteFreeExtensions\Init',
|
||||
'Automattic\WooCommerce\Internal\Admin\ActivityPanels' => 'Automattic\WooCommerce\Admin\Features\ActivityPanels',
|
||||
'Automattic\WooCommerce\Internal\Admin\Analytics' => 'Automattic\WooCommerce\Admin\Features\Analytics',
|
||||
'Automattic\WooCommerce\Internal\Admin\Coupons' => 'Automattic\WooCommerce\Admin\Features\Coupons',
|
||||
'Automattic\WooCommerce\Internal\Admin\CouponsMovedTrait' => 'Automattic\WooCommerce\Admin\Features\CouponsMovedTrait',
|
||||
'Automattic\WooCommerce\Internal\Admin\CustomerEffortScoreTracks' => 'Automattic\WooCommerce\Admin\Features\CustomerEffortScoreTracks',
|
||||
'Automattic\WooCommerce\Internal\Admin\Homescreen' => 'Automattic\WooCommerce\Admin\Features\Homescreen',
|
||||
'Automattic\WooCommerce\Internal\Admin\Marketing' => 'Automattic\WooCommerce\Admin\Features\Marketing',
|
||||
'Automattic\WooCommerce\Internal\Admin\MobileAppBanner' => 'Automattic\WooCommerce\Admin\Features\MobileAppBanner',
|
||||
'Automattic\WooCommerce\Internal\Admin\RemoteInboxNotifications' => 'Automattic\WooCommerce\Admin\Features\RemoteInboxNotifications',
|
||||
'Automattic\WooCommerce\Internal\Admin\SettingsNavigationFeature' => 'Automattic\WooCommerce\Admin\Features\Settings',
|
||||
'Automattic\WooCommerce\Internal\Admin\ShippingLabelBanner' => 'Automattic\WooCommerce\Admin\Features\ShippingLabelBanner',
|
||||
'Automattic\WooCommerce\Internal\Admin\ShippingLabelBannerDisplayRules' => 'Automattic\WooCommerce\Admin\Features\ShippingLabelBannerDisplayRules',
|
||||
'Automattic\WooCommerce\Internal\Admin\WcPayWelcomePage' => 'Automattic\WooCommerce\Admin\Features\WcPayWelcomePage',
|
||||
);
|
||||
foreach ( $aliases as $new_class => $orig_class ) {
|
||||
class_alias( $new_class, $orig_class );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features;
|
||||
|
||||
use Automattic\WooCommerce\Admin\PageController;
|
||||
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
|
||||
use Automattic\WooCommerce\Admin\WCAdminHelper;
|
||||
|
||||
/**
|
||||
* Takes care of Launch Your Store related actions.
|
||||
*/
|
||||
class LaunchYourStore {
|
||||
const BANNER_DISMISS_USER_META_KEY = 'woocommerce_coming_soon_banner_dismissed';
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'woocommerce_update_options_site-visibility', array( $this, 'save_site_visibility_options' ) );
|
||||
add_filter( 'woocommerce_admin_shared_settings', array( $this, 'preload_settings' ) );
|
||||
add_action( 'wp_footer', array( $this, 'maybe_add_coming_soon_banner_on_frontend' ) );
|
||||
add_action( 'init', array( $this, 'register_launch_your_store_user_meta_fields' ) );
|
||||
add_filter( 'woocommerce_tracks_event_properties', array( $this, 'append_coming_soon_global_tracks' ), 10, 2 );
|
||||
add_action( 'wp_login', array( $this, 'reset_woocommerce_coming_soon_banner_dismissed' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save values submitted from WooCommerce -> Settings -> General.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save_site_visibility_options() {
|
||||
$nonce = isset( $_REQUEST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ) : '';
|
||||
if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'woocommerce-settings' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// options to allowed update and their allowed values.
|
||||
$options = array(
|
||||
'woocommerce_coming_soon' => array( 'yes', 'no' ),
|
||||
'woocommerce_store_pages_only' => array( 'yes', 'no' ),
|
||||
'woocommerce_private_link' => array( 'yes', 'no' ),
|
||||
);
|
||||
|
||||
$event_data = array();
|
||||
|
||||
foreach ( $options as $name => $allowed_values ) {
|
||||
$current_value = get_option( $name, 'not set' );
|
||||
$new_value = $current_value;
|
||||
|
||||
if ( isset( $_POST[ $name ] ) ) {
|
||||
$input_value = sanitize_text_field( wp_unslash( $_POST[ $name ] ) );
|
||||
|
||||
// no-op if input value is invalid.
|
||||
if ( in_array( $input_value, $allowed_values, true ) ) {
|
||||
update_option( $name, $input_value );
|
||||
$new_value = $input_value;
|
||||
|
||||
// log the transition if there is one.
|
||||
if ( $current_value !== $new_value ) {
|
||||
$enabled_or_disabled = 'yes' === $new_value ? 'enabled' : 'disabled';
|
||||
$event_data[ $name . '_toggled' ] = $enabled_or_disabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
$event_data[ $name ] = $new_value;
|
||||
}
|
||||
wc_admin_record_tracks_event( 'site_visibility_saved', $event_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Append coming soon prop tracks globally.
|
||||
*
|
||||
* @param array $event_properties Event properties array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function append_coming_soon_global_tracks( $event_properties ) {
|
||||
if ( is_array( $event_properties ) ) {
|
||||
$coming_soon = 'no';
|
||||
if ( 'yes' === get_option( 'woocommerce_coming_soon', 'no' ) ) {
|
||||
if ( 'yes' === get_option( 'woocommerce_store_pages_only', 'no' ) ) {
|
||||
$coming_soon = 'store';
|
||||
} else {
|
||||
$coming_soon = 'site';
|
||||
}
|
||||
}
|
||||
$event_properties['coming_soon'] = $coming_soon;
|
||||
}
|
||||
return $event_properties;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Preload settings for Site Visibility.
|
||||
*
|
||||
* @param array $settings settings array.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function preload_settings( $settings ) {
|
||||
if ( ! is_admin() ) {
|
||||
return $settings;
|
||||
}
|
||||
|
||||
$current_screen = get_current_screen();
|
||||
$is_setting_page = $current_screen && 'woocommerce_page_wc-settings' === $current_screen->id;
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
$is_woopayments_connect = isset( $_GET['path'] ) &&
|
||||
isset( $_GET['page'] ) &&
|
||||
( '/payments/connect' === sanitize_text_field( wp_unslash( $_GET['path'] ) ) || '/payments/onboarding' === sanitize_text_field( wp_unslash( $_GET['path'] ) ) ) &&
|
||||
'wc-admin' === $_GET['page'];
|
||||
// phpcs:enable
|
||||
|
||||
if ( $is_setting_page || $is_woopayments_connect ) {
|
||||
// Regnerate the share key if it's not set.
|
||||
add_option( 'woocommerce_share_key', wp_generate_password( 32, false ) );
|
||||
|
||||
$settings['siteVisibilitySettings'] = array(
|
||||
'shop_permalink' => get_permalink( wc_get_page_id( 'shop' ) ),
|
||||
'woocommerce_coming_soon' => get_option( 'woocommerce_coming_soon' ),
|
||||
'woocommerce_store_pages_only' => get_option( 'woocommerce_store_pages_only' ),
|
||||
'woocommerce_private_link' => get_option( 'woocommerce_private_link' ),
|
||||
'woocommerce_share_key' => get_option( 'woocommerce_share_key' ),
|
||||
);
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* User must be an admin or editor.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_manager_or_admin() {
|
||||
// phpcs:ignore
|
||||
if ( ! current_user_can( 'shop_manager' ) && ! current_user_can( 'administrator' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add 'coming soon' banner on the frontend when the following conditions met.
|
||||
*
|
||||
* - User must be either an admin or store editor (must be logged in).
|
||||
* - 'woocommerce_coming_soon' option value must be 'yes'
|
||||
* - The page must not be the Coming soon page itself.
|
||||
*/
|
||||
public function maybe_add_coming_soon_banner_on_frontend() {
|
||||
// Do not show the banner if the site is being previewed.
|
||||
if ( isset( $_GET['site-preview'] ) ) { // @phpcs:ignore
|
||||
return false;
|
||||
}
|
||||
|
||||
$current_user_id = get_current_user_id();
|
||||
if ( ! $current_user_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( get_user_meta( $current_user_id, self::BANNER_DISMISS_USER_META_KEY, true ) === 'yes' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $this->is_manager_or_admin() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 'woocommerce_coming_soon' must be 'yes'
|
||||
if ( get_option( 'woocommerce_coming_soon', 'no' ) !== 'yes' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$store_pages_only = get_option( 'woocommerce_store_pages_only' ) === 'yes';
|
||||
if ( $store_pages_only && ! WCAdminHelper::is_store_page() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$link = admin_url( 'admin.php?page=wc-settings&tab=site-visibility' );
|
||||
$rest_url = rest_url( 'wp/v2/users/' . $current_user_id );
|
||||
$rest_nonce = wp_create_nonce( 'wp_rest' );
|
||||
|
||||
$text = sprintf(
|
||||
// translators: no need to translate it. It's a link.
|
||||
__(
|
||||
"
|
||||
This page is in \"Coming soon\" mode and is only visible to you and those who have permission. To make it public to everyone, <a href='%s'>change visibility settings</a>
|
||||
",
|
||||
'woocommerce'
|
||||
),
|
||||
$link
|
||||
);
|
||||
// phpcs:ignore
|
||||
echo "<div id='coming-soon-footer-banner'>$text<a class='coming-soon-footer-banner-dismiss' data-rest-url='$rest_url' data-rest-nonce='$rest_nonce'></a></div>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Register user meta fields for Launch Your Store.
|
||||
*/
|
||||
public function register_launch_your_store_user_meta_fields() {
|
||||
if ( ! $this->is_manager_or_admin() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
register_meta(
|
||||
'user',
|
||||
'woocommerce_launch_your_store_tour_hidden',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'description' => 'Indicate whether the user has dismissed the site visibility tour on the home screen.',
|
||||
'single' => true,
|
||||
'show_in_rest' => true,
|
||||
)
|
||||
);
|
||||
|
||||
register_meta(
|
||||
'user',
|
||||
self::BANNER_DISMISS_USER_META_KEY,
|
||||
array(
|
||||
'type' => 'string',
|
||||
'description' => 'Indicate whether the user has dismissed the coming soon notice or not.',
|
||||
'single' => true,
|
||||
'show_in_rest' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset 'woocommerce_coming_soon_banner_dismissed' user meta to 'no'.
|
||||
*
|
||||
* Runs when a user logs-in successfully.
|
||||
*
|
||||
* @param string $user_login user login.
|
||||
* @param object $user user object.
|
||||
*/
|
||||
public function reset_woocommerce_coming_soon_banner_dismissed( $user_login, $user ) {
|
||||
$existing_meta = get_user_meta( $user->ID, self::BANNER_DISMISS_USER_META_KEY, true );
|
||||
if ( 'yes' === $existing_meta ) {
|
||||
update_user_meta( $user->ID, self::BANNER_DISMISS_USER_META_KEY, 'no' );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
<?php
|
||||
/**
|
||||
* Gets a list of fallback methods if remote fetching is disabled.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\MarketingRecommendations;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Default Marketing Recommendations
|
||||
*/
|
||||
class DefaultMarketingRecommendations {
|
||||
/**
|
||||
* Get default specs.
|
||||
*
|
||||
* @return array Default specs.
|
||||
*/
|
||||
public static function get_all() {
|
||||
// Icon directory URL.
|
||||
$icon_dir_url = WC_ADMIN_IMAGES_FOLDER_URL . '/marketing';
|
||||
|
||||
$utm_string = '?utm_source=marketingtab&utm_medium=product&utm_campaign=wcaddons';
|
||||
|
||||
// Categories. Note that these are keys used in code, not texts to be displayed in the UI.
|
||||
$marketing = 'marketing';
|
||||
$coupons = 'coupons';
|
||||
|
||||
// Subcategories.
|
||||
$sales_channels = array(
|
||||
'slug' => 'sales-channels',
|
||||
'name' => __( 'Sales channels', 'woocommerce' ),
|
||||
);
|
||||
$email = array(
|
||||
'slug' => 'email',
|
||||
'name' => __( 'Email', 'woocommerce' ),
|
||||
);
|
||||
$automations = array(
|
||||
'slug' => 'automations',
|
||||
'name' => __( 'Automations', 'woocommerce' ),
|
||||
);
|
||||
$conversion = array(
|
||||
'slug' => 'conversion',
|
||||
'name' => __( 'Conversion', 'woocommerce' ),
|
||||
);
|
||||
$crm = array(
|
||||
'slug' => 'crm',
|
||||
'name' => __( 'CRM', 'woocommerce' ),
|
||||
);
|
||||
|
||||
// Tags.
|
||||
$built_by_woocommerce = array(
|
||||
'slug' => 'built-by-woocommerce',
|
||||
'name' => __( 'Built by WooCommerce', 'woocommerce' ),
|
||||
);
|
||||
|
||||
return array(
|
||||
array(
|
||||
'title' => 'Google Listings and Ads',
|
||||
'description' => __( 'Get in front of shoppers and drive traffic so you can grow your business with Smart Shopping Campaigns and free listings.', 'woocommerce' ),
|
||||
'url' => "https://woocommerce.com/products/google-listings-and-ads/{$utm_string}",
|
||||
'direct_install' => true,
|
||||
'icon' => "{$icon_dir_url}/google.svg",
|
||||
'product' => 'google-listings-and-ads',
|
||||
'plugin' => 'google-listings-and-ads/google-listings-and-ads.php',
|
||||
'categories' => array(
|
||||
$marketing,
|
||||
),
|
||||
'subcategories' => array(
|
||||
$sales_channels,
|
||||
),
|
||||
'tags' => array(
|
||||
$built_by_woocommerce,
|
||||
),
|
||||
),
|
||||
array(
|
||||
'title' => 'Pinterest for WooCommerce',
|
||||
'description' => __( 'Grow your business on Pinterest! Use this official plugin to allow shoppers to Pin products while browsing your store, track conversions, and advertise on Pinterest.', 'woocommerce' ),
|
||||
'url' => "https://woocommerce.com/products/pinterest-for-woocommerce/{$utm_string}",
|
||||
'direct_install' => true,
|
||||
'icon' => "{$icon_dir_url}/pinterest.svg",
|
||||
'product' => 'pinterest-for-woocommerce',
|
||||
'plugin' => 'pinterest-for-woocommerce/pinterest-for-woocommerce.php',
|
||||
'categories' => array(
|
||||
$marketing,
|
||||
),
|
||||
'subcategories' => array(
|
||||
$sales_channels,
|
||||
),
|
||||
'tags' => array(
|
||||
$built_by_woocommerce,
|
||||
),
|
||||
),
|
||||
array(
|
||||
'title' => 'TikTok for WooCommerce',
|
||||
'description' => __( 'Create advertising campaigns and reach one billion global users with TikTok for WooCommerce.', 'woocommerce' ),
|
||||
'url' => "https://woocommerce.com/products/tiktok-for-woocommerce/{$utm_string}",
|
||||
'direct_install' => true,
|
||||
'icon' => "{$icon_dir_url}/tiktok.jpg",
|
||||
'product' => 'tiktok-for-business',
|
||||
'plugin' => 'tiktok-for-business/tiktok-for-woocommerce.php',
|
||||
'categories' => array(
|
||||
$marketing,
|
||||
),
|
||||
'subcategories' => array(
|
||||
$sales_channels,
|
||||
),
|
||||
'tags' => array(),
|
||||
),
|
||||
array(
|
||||
'title' => 'MailPoet',
|
||||
'description' => __( 'Create and send purchase follow-up emails, newsletters, and promotional campaigns straight from your dashboard.', 'woocommerce' ),
|
||||
'url' => "https://woocommerce.com/products/mailpoet/{$utm_string}",
|
||||
'direct_install' => true,
|
||||
'icon' => "{$icon_dir_url}/mailpoet.svg",
|
||||
'product' => 'mailpoet',
|
||||
'plugin' => 'mailpoet/mailpoet.php',
|
||||
'categories' => array(
|
||||
$marketing,
|
||||
),
|
||||
'subcategories' => array(
|
||||
$email,
|
||||
),
|
||||
'tags' => array(
|
||||
$built_by_woocommerce,
|
||||
),
|
||||
),
|
||||
array(
|
||||
'title' => 'Mailchimp for WooCommerce',
|
||||
'description' => __( 'Send targeted campaigns, recover abandoned carts and more with Mailchimp.', 'woocommerce' ),
|
||||
'url' => "https://woocommerce.com/products/mailchimp-for-woocommerce/{$utm_string}",
|
||||
'direct_install' => true,
|
||||
'icon' => "{$icon_dir_url}/mailchimp.svg",
|
||||
'product' => 'mailchimp-for-woocommerce',
|
||||
'plugin' => 'mailchimp-for-woocommerce/mailchimp-woocommerce.php',
|
||||
'categories' => array(
|
||||
$marketing,
|
||||
),
|
||||
'subcategories' => array(
|
||||
$email,
|
||||
),
|
||||
'tags' => array(),
|
||||
),
|
||||
array(
|
||||
'title' => 'Klaviyo for WooCommerce',
|
||||
'description' => __( 'Grow and retain customers with intelligent, impactful email and SMS marketing automation and a consolidated view of customer interactions.', 'woocommerce' ),
|
||||
'url' => "https://woocommerce.com/products/klaviyo-for-woocommerce/{$utm_string}",
|
||||
'direct_install' => true,
|
||||
'icon' => "{$icon_dir_url}/klaviyo.png",
|
||||
'product' => 'klaviyo',
|
||||
'plugin' => 'klaviyo/klaviyo.php',
|
||||
'categories' => array(
|
||||
$marketing,
|
||||
),
|
||||
'subcategories' => array(
|
||||
$email,
|
||||
),
|
||||
'tags' => array(),
|
||||
),
|
||||
array(
|
||||
'title' => 'AutomateWoo',
|
||||
'description' => __( 'Convert and retain customers with automated marketing that does the hard work for you.', 'woocommerce' ),
|
||||
'url' => "https://woocommerce.com/products/automatewoo/{$utm_string}",
|
||||
'direct_install' => false,
|
||||
'icon' => "{$icon_dir_url}/automatewoo.svg",
|
||||
'product' => 'automatewoo',
|
||||
'plugin' => 'automatewoo/automatewoo.php',
|
||||
'categories' => array(
|
||||
$marketing,
|
||||
),
|
||||
'subcategories' => array(
|
||||
$automations,
|
||||
),
|
||||
'tags' => array(
|
||||
$built_by_woocommerce,
|
||||
),
|
||||
),
|
||||
array(
|
||||
'title' => 'AutomateWoo Refer a Friend',
|
||||
'description' => __( 'Boost your organic sales by adding a customer referral program to your WooCommerce store.', 'woocommerce' ),
|
||||
'url' => "https://woocommerce.com/products/automatewoo-refer-a-friend/{$utm_string}",
|
||||
'direct_install' => false,
|
||||
'icon' => "{$icon_dir_url}/automatewoo.svg",
|
||||
'product' => 'automatewoo-referrals',
|
||||
'plugin' => 'automatewoo-referrals/automatewoo-referrals.php',
|
||||
'categories' => array(
|
||||
$marketing,
|
||||
),
|
||||
'subcategories' => array(
|
||||
$automations,
|
||||
),
|
||||
'tags' => array(
|
||||
$built_by_woocommerce,
|
||||
),
|
||||
),
|
||||
array(
|
||||
'title' => 'AutomateWoo Birthdays',
|
||||
'description' => __( 'Delight customers and boost organic sales with a special WooCommerce birthday email (and coupon!) on their special day.', 'woocommerce' ),
|
||||
'url' => "https://woocommerce.com/products/automatewoo-birthdays/{$utm_string}",
|
||||
'direct_install' => false,
|
||||
'icon' => "{$icon_dir_url}/automatewoo.svg",
|
||||
'product' => 'automatewoo-birthdays',
|
||||
'plugin' => 'automatewoo-birthdays/automatewoo-birthdays.php',
|
||||
'categories' => array(
|
||||
$marketing,
|
||||
),
|
||||
'subcategories' => array(
|
||||
$automations,
|
||||
),
|
||||
'tags' => array(
|
||||
$built_by_woocommerce,
|
||||
),
|
||||
),
|
||||
array(
|
||||
'title' => 'Trustpilot Reviews',
|
||||
'description' => __( 'Collect and showcase verified reviews that consumers trust.', 'woocommerce' ),
|
||||
'url' => "https://woocommerce.com/products/trustpilot-reviews/{$utm_string}",
|
||||
'direct_install' => true,
|
||||
'icon' => "{$icon_dir_url}/trustpilot.png",
|
||||
'product' => 'trustpilot-reviews',
|
||||
'plugin' => 'trustpilot-reviews/wc_trustpilot.php',
|
||||
'categories' => array(
|
||||
$marketing,
|
||||
),
|
||||
'subcategories' => array(
|
||||
$conversion,
|
||||
),
|
||||
'tags' => array(),
|
||||
),
|
||||
array(
|
||||
'title' => 'Vimeo for WooCommerce',
|
||||
'description' => __( 'Turn your product images into stunning videos that engage and convert audiences - no video experience required.', 'woocommerce' ),
|
||||
'url' => "https://woocommerce.com/products/vimeo/{$utm_string}",
|
||||
'direct_install' => true,
|
||||
'icon' => "{$icon_dir_url}/vimeo.png",
|
||||
'product' => 'vimeo',
|
||||
'plugin' => 'vimeo/Core.php',
|
||||
'categories' => array(
|
||||
$marketing,
|
||||
),
|
||||
'subcategories' => array(
|
||||
$conversion,
|
||||
),
|
||||
'tags' => array(),
|
||||
),
|
||||
array(
|
||||
'title' => 'Jetpack CRM for WooCommerce',
|
||||
'description' => __( 'Harness data from WooCommerce to grow your business. Manage leads, customers, and segments, through automation, quotes, invoicing, billing, and email marketing. Power up your store with CRM.', 'woocommerce' ),
|
||||
'url' => "https://woocommerce.com/products/jetpack-crm/{$utm_string}",
|
||||
'direct_install' => true,
|
||||
'icon' => "{$icon_dir_url}/jetpack-crm.svg",
|
||||
'product' => 'zero-bs-crm',
|
||||
'plugin' => 'zero-bs-crm/ZeroBSCRM.php',
|
||||
'categories' => array(
|
||||
$marketing,
|
||||
),
|
||||
'subcategories' => array(
|
||||
$crm,
|
||||
),
|
||||
'tags' => array(),
|
||||
),
|
||||
array(
|
||||
'title' => 'WooCommerce Zapier',
|
||||
'description' => __( 'Integrate your WooCommerce store with 5000+ cloud apps and services today. Trusted by 11,000+ users.', 'woocommerce' ),
|
||||
'url' => "https://woocommerce.com/products/woocommerce-zapier/{$utm_string}",
|
||||
'direct_install' => false,
|
||||
'icon' => "{$icon_dir_url}/zapier.png",
|
||||
'product' => 'woocommerce-zapier',
|
||||
'plugin' => 'woocommerce-zapier/woocommerce-zapier.php',
|
||||
'categories' => array(
|
||||
$marketing,
|
||||
),
|
||||
'subcategories' => array(
|
||||
$crm,
|
||||
),
|
||||
'tags' => array(),
|
||||
),
|
||||
array(
|
||||
'title' => 'Salesforce',
|
||||
'description' => __( 'Sync your website\'s data like contacts, products, and orders over Salesforce CRM with Salesforce Integration for WooCommerce.', 'woocommerce' ),
|
||||
'url' => "https://woocommerce.com/products/integration-with-salesforce-for-woocommerce/{$utm_string}",
|
||||
'direct_install' => false,
|
||||
'icon' => "{$icon_dir_url}/salesforce.jpg",
|
||||
'product' => 'integration-with-salesforce',
|
||||
'plugin' => 'integration-with-salesforce/integration-with-salesforce.php',
|
||||
'categories' => array(
|
||||
$marketing,
|
||||
),
|
||||
'subcategories' => array(
|
||||
$crm,
|
||||
),
|
||||
'tags' => array(),
|
||||
),
|
||||
array(
|
||||
'title' => 'Personalized Coupons',
|
||||
'description' => __( 'Generate dynamic personalized coupons for your customers that increase purchase rates.', 'woocommerce' ),
|
||||
'url' => "https://woocommerce.com/products/automatewoo/{$utm_string}",
|
||||
'direct_install' => false,
|
||||
'icon' => "{$icon_dir_url}/automatewoo-personalized-coupons.svg",
|
||||
'product' => 'automatewoo',
|
||||
'plugin' => 'automatewoo/automatewoo.php',
|
||||
'categories' => array(
|
||||
$coupons,
|
||||
),
|
||||
'subcategories' => array(),
|
||||
'tags' => array(),
|
||||
),
|
||||
array(
|
||||
'title' => 'Smart Coupons',
|
||||
'description' => __( 'Powerful, "all in one" solution for gift certificates, store credits, discount coupons and vouchers.', 'woocommerce' ),
|
||||
'url' => "https://woocommerce.com/products/smart-coupons/{$utm_string}",
|
||||
'direct_install' => false,
|
||||
'icon' => "{$icon_dir_url}/woocommerce-smart-coupons.svg",
|
||||
'product' => 'woocommerce-smart-coupons',
|
||||
'plugin' => 'woocommerce-smart-coupons/woocommerce-smart-coupons.php',
|
||||
'categories' => array(
|
||||
$coupons,
|
||||
),
|
||||
'subcategories' => array(),
|
||||
'tags' => array(),
|
||||
),
|
||||
array(
|
||||
'title' => 'URL Coupons',
|
||||
'description' => __( 'Create a unique URL that applies a discount and optionally adds one or more products to the customer\'s cart.', 'woocommerce' ),
|
||||
'url' => "https://woocommerce.com/products/url-coupons/{$utm_string}",
|
||||
'direct_install' => false,
|
||||
'icon' => "{$icon_dir_url}/woocommerce-url-coupons.svg",
|
||||
'product' => 'woocommerce-url-coupons',
|
||||
'plugin' => 'woocommerce-url-coupons/woocommerce-url-coupons.php',
|
||||
'categories' => array(
|
||||
$coupons,
|
||||
),
|
||||
'subcategories' => array(),
|
||||
'tags' => array(),
|
||||
),
|
||||
array(
|
||||
'title' => 'WooCommerce Store Credit',
|
||||
'description' => __( 'Create "store credit" coupons for customers which are redeemable at checkout.', 'woocommerce' ),
|
||||
'url' => "https://woocommerce.com/products/store-credit/{$utm_string}",
|
||||
'direct_install' => false,
|
||||
'icon' => "{$icon_dir_url}/woocommerce-store-credit.svg",
|
||||
'product' => 'woocommerce-store-credit',
|
||||
'plugin' => 'woocommerce-store-credit/woocommerce-store-credit.php',
|
||||
'categories' => array(
|
||||
$coupons,
|
||||
),
|
||||
'subcategories' => array(),
|
||||
'tags' => array(),
|
||||
),
|
||||
array(
|
||||
'title' => 'Free Gift Coupons',
|
||||
'description' => __( 'Give away a free item to any customer with the coupon code.', 'woocommerce' ),
|
||||
'url' => "https://woocommerce.com/products/free-gift-coupons/{$utm_string}",
|
||||
'direct_install' => false,
|
||||
'icon' => "{$icon_dir_url}/woocommerce-free-gift-coupons.svg",
|
||||
'product' => 'woocommerce-free-gift-coupons',
|
||||
'plugin' => 'woocommerce-free-gift-coupons/woocommerce-free-gift-coupons.php',
|
||||
'categories' => array(
|
||||
$coupons,
|
||||
),
|
||||
'subcategories' => array(),
|
||||
'tags' => array(),
|
||||
),
|
||||
array(
|
||||
'title' => 'Group Coupons',
|
||||
'description' => __( 'Coupons for groups. Provides the option to have coupons that are restricted to group members or roles. Works with the free Groups plugin.', 'woocommerce' ),
|
||||
'url' => "https://woocommerce.com/products/group-coupons/{$utm_string}",
|
||||
'direct_install' => false,
|
||||
'icon' => "{$icon_dir_url}/woocommerce-group-coupons.svg",
|
||||
'product' => 'woocommerce-group-coupons',
|
||||
'plugin' => 'woocommerce-group-coupons/woocommerce-group-coupons.php',
|
||||
'categories' => array(
|
||||
$coupons,
|
||||
),
|
||||
'subcategories' => array(),
|
||||
'tags' => array(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\MarketingRecommendations;
|
||||
|
||||
use Automattic\WooCommerce\Admin\RemoteSpecs\RemoteSpecsEngine;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Marketing Recommendations engine.
|
||||
* This goes through the specs and gets marketing recommendations.
|
||||
*/
|
||||
class Init extends RemoteSpecsEngine {
|
||||
/**
|
||||
* Slug of the category specifying marketing extensions on the WooCommerce.com store.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const MARKETING_EXTENSION_CATEGORY_SLUG = 'marketing';
|
||||
|
||||
/**
|
||||
* Slug of the subcategory specifying marketing channels on the WooCommerce.com store.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const MARKETING_CHANNEL_SUBCATEGORY_SLUG = 'sales-channels';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'woocommerce_updated', array( __CLASS__, 'delete_specs_transient' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the specs transient.
|
||||
*/
|
||||
public static function delete_specs_transient() {
|
||||
MarketingRecommendationsDataSourcePoller::get_instance()->delete_specs_transient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specs or fetch remotely if they don't exist.
|
||||
*/
|
||||
public static function get_specs() {
|
||||
if ( 'no' === get_option( 'woocommerce_show_marketplace_suggestions', 'yes' ) ) {
|
||||
return DefaultMarketingRecommendations::get_all();
|
||||
}
|
||||
$specs = MarketingRecommendationsDataSourcePoller::get_instance()->get_specs_from_data_sources();
|
||||
|
||||
// Fetch specs if they don't yet exist.
|
||||
if ( ! is_array( $specs ) || 0 === count( $specs ) ) {
|
||||
return DefaultMarketingRecommendations::get_all();
|
||||
}
|
||||
|
||||
return $specs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process specs.
|
||||
*
|
||||
* @param array|null $specs Marketing recommendations spec array.
|
||||
* @return array
|
||||
*/
|
||||
protected static function evaluate_specs( array $specs = null ) {
|
||||
$suggestions = array();
|
||||
$errors = array();
|
||||
|
||||
foreach ( $specs as $spec ) {
|
||||
try {
|
||||
$suggestions[] = self::object_to_array( $spec );
|
||||
} catch ( \Throwable $e ) {
|
||||
$errors[] = $e;
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'suggestions' => $suggestions,
|
||||
'errors' => $errors,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load recommended plugins from WooCommerce.com
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_recommended_plugins(): array {
|
||||
$specs = self::get_specs();
|
||||
$results = self::evaluate_specs( $specs );
|
||||
|
||||
$specs_to_return = $results['suggestions'];
|
||||
$specs_to_save = null;
|
||||
|
||||
if ( empty( $specs_to_return ) ) {
|
||||
// When suggestions is empty, replace it with defaults and save for 3 hours.
|
||||
$specs_to_save = DefaultMarketingRecommendations::get_all();
|
||||
$specs_to_return = self::evaluate_specs( $specs_to_save )['suggestions'];
|
||||
} elseif ( count( $results['errors'] ) > 0 ) {
|
||||
// When suggestions is not empty but has errors, save it for 3 hours.
|
||||
$specs_to_save = $specs;
|
||||
}
|
||||
|
||||
if ( $specs_to_save ) {
|
||||
MarketingRecommendationsDataSourcePoller::get_instance()->set_specs_transient( $specs_to_save, 3 * HOUR_IN_SECONDS );
|
||||
}
|
||||
$errors = $results['errors'];
|
||||
if ( ! empty( $errors ) ) {
|
||||
self::log_errors( $errors );
|
||||
}
|
||||
|
||||
return $specs_to_return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return only the recommended marketing channels from WooCommerce.com.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_recommended_marketing_channels(): array {
|
||||
return array_filter(
|
||||
self::get_recommended_plugins(),
|
||||
function ( array $plugin_data ) {
|
||||
return self::is_marketing_channel_plugin( $plugin_data );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all recommended marketing extensions EXCEPT the marketing channels from WooCommerce.com.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_recommended_marketing_extensions_excluding_channels(): array {
|
||||
return array_filter(
|
||||
self::get_recommended_plugins(),
|
||||
function ( array $plugin_data ) {
|
||||
return self::is_marketing_plugin( $plugin_data ) && ! self::is_marketing_channel_plugin( $plugin_data );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a plugin is a marketing extension.
|
||||
*
|
||||
* @param array $plugin_data The plugin properties returned by the API.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function is_marketing_plugin( array $plugin_data ): bool {
|
||||
$categories = $plugin_data['categories'] ?? array();
|
||||
|
||||
return in_array( self::MARKETING_EXTENSION_CATEGORY_SLUG, $categories, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a plugin is a marketing channel.
|
||||
*
|
||||
* @param array $plugin_data The plugin properties returned by the API.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function is_marketing_channel_plugin( array $plugin_data ): bool {
|
||||
if ( ! self::is_marketing_plugin( $plugin_data ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$subcategories = $plugin_data['subcategories'] ?? array();
|
||||
foreach ( $subcategories as $subcategory ) {
|
||||
if ( isset( $subcategory['slug'] ) && self::MARKETING_CHANNEL_SUBCATEGORY_SLUG === $subcategory['slug'] ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an object to an array.
|
||||
* This is used to convert the specs to an array so that they can be returned by the API.
|
||||
*
|
||||
* @param mixed $obj Object to convert.
|
||||
* @param array &$visited Reference to an array keeping track of all seen objects to detect circular references.
|
||||
* @return array
|
||||
*/
|
||||
public static function object_to_array( $obj, &$visited = array() ) {
|
||||
if ( is_object( $obj ) ) {
|
||||
if ( in_array( $obj, $visited, true ) ) {
|
||||
// Circular reference detected.
|
||||
return null;
|
||||
}
|
||||
$visited[] = $obj;
|
||||
$obj = (array) $obj;
|
||||
}
|
||||
if ( is_array( $obj ) ) {
|
||||
$new = array();
|
||||
foreach ( $obj as $key => $val ) {
|
||||
$new[ $key ] = self::object_to_array( $val, $visited );
|
||||
}
|
||||
} else {
|
||||
$new = $obj;
|
||||
}
|
||||
return $new;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\MarketingRecommendations;
|
||||
|
||||
use Automattic\WooCommerce\Admin\RemoteSpecs\DataSourcePoller;
|
||||
|
||||
/**
|
||||
* Specs data source poller class for marketing recommendations.
|
||||
*/
|
||||
class MarketingRecommendationsDataSourcePoller extends DataSourcePoller {
|
||||
|
||||
/**
|
||||
* Data Source Poller ID.
|
||||
*/
|
||||
const ID = 'marketing_recommendations';
|
||||
|
||||
/**
|
||||
* Default data sources array.
|
||||
*/
|
||||
const DATA_SOURCES = array(
|
||||
'https://woocommerce.com/wp-json/wccom/marketing-tab/1.3/recommendations.json',
|
||||
);
|
||||
|
||||
/**
|
||||
* Class instance.
|
||||
*
|
||||
* @var Analytics instance
|
||||
*/
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* Get class instance.
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( ! self::$instance ) {
|
||||
self::$instance = new self(
|
||||
self::ID,
|
||||
self::DATA_SOURCES,
|
||||
array(
|
||||
'spec_key' => 'product',
|
||||
)
|
||||
);
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,463 @@
|
||||
<?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;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\TaskLists;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
|
||||
|
||||
/**
|
||||
* 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 unfulfilled order count
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_shop_order_count() {
|
||||
$status_counts = array_map( 'wc_orders_count', array( 'processing', 'on-hold' ) );
|
||||
return array_sum( $status_counts );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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',
|
||||
'badge' => self::get_shop_order_count(),
|
||||
'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' => __( '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 = self::get_order_menu_items();
|
||||
$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();
|
||||
$setup_tasks_remaining = TaskLists::setup_tasks_remaining();
|
||||
if ( defined( '\Automattic\WooCommerce\Internal\Admin\Homescreen::MENU_SLUG' ) ) {
|
||||
$home_item = array(
|
||||
'id' => 'woocommerce-home',
|
||||
'title' => __( 'Home', 'woocommerce' ),
|
||||
'url' => \Automattic\WooCommerce\Internal\Admin\Homescreen::MENU_SLUG,
|
||||
'order' => 0,
|
||||
'matchExpression' => 'page=wc-admin((?!path=).)*$',
|
||||
'badge' => $setup_tasks_remaining ? $setup_tasks_remaining : null,
|
||||
);
|
||||
}
|
||||
|
||||
$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,
|
||||
);
|
||||
}
|
||||
|
||||
$add_product_mvp = array();
|
||||
if ( Features::is_enabled( 'new-product-management-experience' ) ) {
|
||||
$add_product_mvp = array(
|
||||
'id' => 'woocommerce-add-product-mbp',
|
||||
'title' => __( 'Add New (MVP)', 'woocommerce' ),
|
||||
'url' => 'admin.php?page=wc-admin&path=/add-product',
|
||||
'parent' => 'woocommerce-products',
|
||||
'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.
|
||||
array(
|
||||
'title' => __( 'Marketplace', 'woocommerce' ),
|
||||
'capability' => 'manage_woocommerce',
|
||||
'id' => 'woocommerce-marketplace',
|
||||
'url' => 'wc-addons',
|
||||
'menuId' => 'secondary',
|
||||
'order' => 10,
|
||||
),
|
||||
$add_product_mvp,
|
||||
),
|
||||
// Tools category.
|
||||
self::get_tool_items(),
|
||||
// WooCommerce Admin items.
|
||||
$wca_items,
|
||||
// Settings category.
|
||||
$setting_items,
|
||||
// Legacy report items.
|
||||
self::get_legacy_report_items()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Supplies menu items for orders.
|
||||
*
|
||||
* This varies depending on whether we are actively using traditional post type-based orders or the new custom
|
||||
* table-based orders.
|
||||
*
|
||||
* @return ?array
|
||||
*/
|
||||
private static function get_order_menu_items(): ?array {
|
||||
if ( ! wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled() ) {
|
||||
return Menu::get_post_type_items( 'shop_order', array( 'parent' => 'woocommerce-orders' ) );
|
||||
}
|
||||
|
||||
$main_orders_menu = array(
|
||||
'title' => __( 'Orders', 'woocommerce' ),
|
||||
'capability' => 'edit_others_shop_orders',
|
||||
'id' => 'woocommerce-orders-default',
|
||||
'url' => 'admin.php?page=wc-orders',
|
||||
'parent' => 'woocommerce-orders',
|
||||
);
|
||||
|
||||
$all_orders_entry = $main_orders_menu;
|
||||
$all_orders_entry['id'] = 'woocommerce-orders-all-items';
|
||||
$all_orders_entry['order'] = 10;
|
||||
|
||||
$new_orders_entry = $main_orders_menu;
|
||||
$new_orders_entry['title'] = __( 'Add order', 'woocommerce' );
|
||||
$new_orders_entry['id'] = 'woocommerce-orders-add-item';
|
||||
$new_orders_entry['url'] = 'admin.php?page=TBD';
|
||||
$new_orders_entry['order'] = 20;
|
||||
|
||||
return array(
|
||||
'default' => $main_orders_menu,
|
||||
'all' => $all_orders_entry,
|
||||
'new' => $new_orders_entry,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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',
|
||||
);
|
||||
|
||||
return apply_filters( 'woocommerce_navigation_core_excluded_items', $excluded_items );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Navigation Favorite
|
||||
*
|
||||
* @package Woocommerce Navigation
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\Navigation;
|
||||
|
||||
use Automattic\WooCommerce\Internal\Admin\WCAdminUser;
|
||||
|
||||
/**
|
||||
* Contains logic for the WooCommerce Navigation menu.
|
||||
*/
|
||||
class Favorites {
|
||||
|
||||
/**
|
||||
* Array index of menu capability.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const META_NAME = 'navigation_favorites';
|
||||
|
||||
/**
|
||||
* Favorites instance.
|
||||
*
|
||||
* @var Favorites|null
|
||||
*/
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
WCAdminUser::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 ) {
|
||||
|
||||
$all_favorites = self::get_all( $user_id );
|
||||
|
||||
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_id, $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 ) {
|
||||
$all_favorites = self::get_all( $user_id );
|
||||
|
||||
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_id, $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 ) {
|
||||
$response = WCAdminUser::get_user_data_field( $user_id, self::META_NAME );
|
||||
|
||||
return $response ? json_decode( $response, true ) : array();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
/**
|
||||
* Navigation Experience
|
||||
*
|
||||
* @package Woocommerce Admin
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\Navigation;
|
||||
|
||||
use Automattic\WooCommerce\Internal\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;
|
||||
use Automattic\WooCommerce\Internal\Admin\WCAdminAssets;
|
||||
|
||||
/**
|
||||
* Contains logic for the Navigation
|
||||
*/
|
||||
class Init {
|
||||
/**
|
||||
* Option name used to toggle this feature.
|
||||
*/
|
||||
const TOGGLE_OPTION_NAME = 'woocommerce_navigation_enabled';
|
||||
|
||||
/**
|
||||
* Determines if the feature has been toggled on or off.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $is_updated = false;
|
||||
|
||||
/**
|
||||
* Hook into WooCommerce.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'update_option_' . self::TOGGLE_OPTION_NAME, array( $this, 'reload_page_on_toggle' ), 10, 2 );
|
||||
add_action( 'woocommerce_settings_saved', array( $this, 'maybe_reload_page' ) );
|
||||
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.
|
||||
*
|
||||
* @deprecated 7.0 The WooCommerce Admin features are now handled by the WooCommerce features engine (see the FeaturesController class).
|
||||
*
|
||||
* @param array $features Feature sections.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_feature_toggle( $features ) {
|
||||
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' );
|
||||
}
|
||||
|
||||
self::$is_updated = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the page if the setting has been updated.
|
||||
*/
|
||||
public static function maybe_reload_page() {
|
||||
if ( ! isset( $_SERVER['REQUEST_URI'] ) || ! self::$is_updated ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_safe_redirect( wp_unslash( $_SERVER['REQUEST_URI'] ) );
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the opt out scripts.
|
||||
*/
|
||||
public function maybe_enqueue_opt_out_scripts() {
|
||||
if ( get_option( 'woocommerce_navigation_show_opt_out', 'no' ) !== 'yes' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
WCAdminAssets::register_style( 'navigation-opt-out', 'style', array( 'wp-components' ) );
|
||||
WCAdminAssets::register_script( 'wp-admin-scripts', 'navigation-opt-out', 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' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,799 @@
|
||||
<?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'] ] ) ) {
|
||||
wc_doing_it_wrong(
|
||||
__METHOD__,
|
||||
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'] . '`'
|
||||
),
|
||||
'6.5.0'
|
||||
);
|
||||
|
||||
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( get_current_user_id() );
|
||||
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' );
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce New Product Management Experience
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\TransientNotices;
|
||||
use Automattic\WooCommerce\Admin\PageController;
|
||||
use Automattic\WooCommerce\Internal\Admin\Loader;
|
||||
use WP_Block_Editor_Context;
|
||||
|
||||
/**
|
||||
* Loads assets related to the new product management experience page.
|
||||
*/
|
||||
class NewProductManagementExperience {
|
||||
|
||||
/**
|
||||
* Option name used to toggle this feature.
|
||||
*/
|
||||
const TOGGLE_OPTION_NAME = 'woocommerce_new_product_management_enabled';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->maybe_show_disabled_notice();
|
||||
if ( ! Features::is_enabled( 'new-product-management-experience' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_styles' ) );
|
||||
add_action( 'get_edit_post_link', array( $this, 'update_edit_product_link' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe show disabled notice.
|
||||
*/
|
||||
public function maybe_show_disabled_notice() {
|
||||
$new_product_experience_param = 'new-product-experience-disabled';
|
||||
if ( isset( $_GET[ $new_product_experience_param ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
TransientNotices::add(
|
||||
array(
|
||||
'user_id' => get_current_user_id(),
|
||||
'id' => 'new-product-experience-disbled',
|
||||
'status' => 'success',
|
||||
'content' => __( '🌟 Thanks for the feedback. We’ll put it to good use!', 'woocommerce' ),
|
||||
)
|
||||
);
|
||||
|
||||
$url = isset( $_SERVER['REQUEST_URI'] ) ? wc_clean( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
|
||||
$url = remove_query_arg( 'new-product-experience-disabled', $url );
|
||||
wp_safe_redirect( $url );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue styles needed for the rich text editor.
|
||||
*/
|
||||
public function enqueue_styles() {
|
||||
if ( ! PageController::is_admin_or_embed_page() ) {
|
||||
return;
|
||||
}
|
||||
wp_enqueue_style( 'wp-edit-blocks' );
|
||||
wp_enqueue_style( 'wp-format-library' );
|
||||
wp_enqueue_editor();
|
||||
/**
|
||||
* Enqueue any block editor related assets.
|
||||
*
|
||||
* @since 7.1.0
|
||||
*/
|
||||
do_action( 'enqueue_block_editor_assets' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the edit product links when the new experience is enabled.
|
||||
*
|
||||
* @param string $link The edit link.
|
||||
* @param int $post_id Post ID.
|
||||
* @return string
|
||||
*/
|
||||
public function update_edit_product_link( $link, $post_id ) {
|
||||
$product = wc_get_product( $post_id );
|
||||
|
||||
if ( ! $product ) {
|
||||
return $link;
|
||||
}
|
||||
|
||||
if ( $product->get_type() === 'simple' ) {
|
||||
return admin_url( 'admin.php?page=wc-admin&path=/product/' . $product->get_id() );
|
||||
}
|
||||
|
||||
return $link;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Onboarding
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features;
|
||||
|
||||
use Automattic\WooCommerce\Admin\DeprecatedClassFacade;
|
||||
|
||||
/**
|
||||
* Contains backend logic for the onboarding profile and checklist feature.
|
||||
*
|
||||
* @deprecated since 6.3.0, use WooCommerce\Internal\Admin\Onboarding.
|
||||
*/
|
||||
class Onboarding extends DeprecatedClassFacade {
|
||||
/**
|
||||
* The name of the non-deprecated class that this facade covers.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $facade_over_classname = 'Automattic\WooCommerce\Admin\Features\Onboarding';
|
||||
|
||||
/**
|
||||
* The version that this class was deprecated in.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $deprecated_in_version = '6.3.0';
|
||||
|
||||
/**
|
||||
* Hook into WooCommerce.
|
||||
*/
|
||||
public function __construct() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of allowed industries for the onboarding wizard.
|
||||
*
|
||||
* @deprecated 6.3.0
|
||||
* @return array
|
||||
*/
|
||||
public static function get_allowed_industries() {
|
||||
wc_deprecated_function( 'get_allowed_industries', '6.3', '\Automattic\WooCommerce\Internal\Admin\OnboardingIndustries::get_allowed_industries()' );
|
||||
return \Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingIndustries::get_allowed_industries();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of allowed product types for the onboarding wizard.
|
||||
*
|
||||
* @deprecated 6.3.0
|
||||
* @return array
|
||||
*/
|
||||
public static function get_allowed_product_types() {
|
||||
wc_deprecated_function( 'get_allowed_product_types', '6.3', '\Automattic\WooCommerce\Internal\Admin\OnboardingProducts::get_allowed_product_types()' );
|
||||
return \Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProducts::get_allowed_product_types();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of themes for the onboarding wizard.
|
||||
*
|
||||
* @deprecated 6.3.0
|
||||
* @return array
|
||||
*/
|
||||
public static function get_themes() {
|
||||
wc_deprecated_function( 'get_themes', '6.3', '\Automattic\WooCommerce\Internal\Admin\OnboardingThemes::get_themes()' );
|
||||
return \Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingThemes::get_themes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get theme data used in onboarding theme browser.
|
||||
*
|
||||
* @deprecated 6.3.0
|
||||
* @param WP_Theme $theme Theme to gather data from.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_theme_data( $theme ) {
|
||||
wc_deprecated_function( 'get_theme_data', '6.3', '\Automattic\WooCommerce\Internal\Admin\OnboardingThemes::get_theme_data()' );
|
||||
return \Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingThemes::get_theme_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of themes that can be installed & activated via the onboarding wizard.
|
||||
*
|
||||
* @deprecated 6.3.0
|
||||
* @return array
|
||||
*/
|
||||
public static function get_allowed_themes() {
|
||||
wc_deprecated_function( 'get_allowed_themes', '6.3', '\Automattic\WooCommerce\Internal\Admin\OnboardingThemes::get_allowed_themes()' );
|
||||
return \Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingThemes::get_allowed_themes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get dynamic product data from API.
|
||||
*
|
||||
* @deprecated 6.3.0
|
||||
* @param array $product_types Array of product types.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_product_data( $product_types ) {
|
||||
wc_deprecated_function( 'get_product_data', '6.3', '\Automattic\WooCommerce\Internal\Admin\OnboardingProducts::get_product_data()' );
|
||||
return \Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProducts::get_product_data();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
/**
|
||||
* A temporary class for creating tasks on the fly from deprecated tasks.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks;
|
||||
|
||||
/**
|
||||
* DeprecatedExtendedTask class.
|
||||
*/
|
||||
class DeprecatedExtendedTask extends Task {
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id = '';
|
||||
|
||||
/**
|
||||
* Additional info.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $additional_info = '';
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $content = '';
|
||||
|
||||
/**
|
||||
* Whether the task is complete or not.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $is_complete = false;
|
||||
|
||||
/**
|
||||
* Snoozeable.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $is_snoozeable = false;
|
||||
|
||||
/**
|
||||
* Dismissable.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $is_dismissable = false;
|
||||
|
||||
/**
|
||||
* Whether the store is capable of viewing the task.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $can_view = true;
|
||||
|
||||
/**
|
||||
* Level.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $level = 3;
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $time;
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $title = '';
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param TaskList $task_list Parent task list.
|
||||
* @param array $args Array of task args.
|
||||
*/
|
||||
public function __construct( $task_list, $args ) {
|
||||
parent::__construct( $task_list );
|
||||
$task_args = wp_parse_args(
|
||||
$args,
|
||||
array(
|
||||
'id' => null,
|
||||
'is_dismissable' => false,
|
||||
'is_snoozeable' => false,
|
||||
'can_view' => true,
|
||||
'level' => 3,
|
||||
'additional_info' => null,
|
||||
'content' => '',
|
||||
'title' => '',
|
||||
'is_complete' => false,
|
||||
'time' => null,
|
||||
)
|
||||
);
|
||||
|
||||
$this->id = $task_args['id'];
|
||||
$this->additional_info = $task_args['additional_info'];
|
||||
$this->content = $task_args['content'];
|
||||
$this->is_complete = $task_args['is_complete'];
|
||||
$this->is_dismissable = $task_args['is_dismissable'];
|
||||
$this->is_snoozeable = $task_args['is_snoozeable'];
|
||||
$this->can_view = $task_args['can_view'];
|
||||
$this->level = $task_args['level'];
|
||||
$this->time = $task_args['time'];
|
||||
$this->title = $task_args['title'];
|
||||
}
|
||||
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional info.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_additional_info() {
|
||||
return $this->additional_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Level.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_level() {
|
||||
return $this->level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function get_time() {
|
||||
return $this->time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a task is snoozeable.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_snoozeable() {
|
||||
return $this->is_snoozeable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a task is dismissable.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_dismissable() {
|
||||
return $this->is_dismissable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a task is dismissable.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
return $this->is_complete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a task is dismissable.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view() {
|
||||
return $this->can_view;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* Filters for maintaining backwards compatibility with deprecated options.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\TaskList;
|
||||
use WC_Install;
|
||||
|
||||
/**
|
||||
* DeprecatedOptions class.
|
||||
*/
|
||||
class DeprecatedOptions {
|
||||
/**
|
||||
* Initialize.
|
||||
*/
|
||||
public static function init() {
|
||||
add_filter( 'pre_option_woocommerce_task_list_hidden', array( __CLASS__, 'get_deprecated_options' ), 10, 2 );
|
||||
add_filter( 'pre_option_woocommerce_extended_task_list_hidden', array( __CLASS__, 'get_deprecated_options' ), 10, 2 );
|
||||
add_action( 'pre_update_option_woocommerce_task_list_hidden', array( __CLASS__, 'update_deprecated_options' ), 10, 3 );
|
||||
add_action( 'pre_update_option_woocommerce_extended_task_list_hidden', array( __CLASS__, 'update_deprecated_options' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 static function get_deprecated_options( $pre_option, $option ) {
|
||||
if ( defined( 'WC_INSTALLING' ) && WC_INSTALLING === true ) {
|
||||
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 static 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->unhide();
|
||||
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->unhide();
|
||||
delete_option( 'woocommerce_extended_task_list_hidden' );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Onboarding Tasks
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\DeprecatedOptions;
|
||||
|
||||
/**
|
||||
* Contains the logic for completing onboarding tasks.
|
||||
*/
|
||||
class Init {
|
||||
/**
|
||||
* Class instance.
|
||||
*
|
||||
* @var OnboardingTasks instance
|
||||
*/
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* Get class instance.
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( ! self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
DeprecatedOptions::init();
|
||||
TaskLists::init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,608 @@
|
||||
<?php
|
||||
/**
|
||||
* Handles task related methods.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks;
|
||||
|
||||
use Automattic\WooCommerce\Internal\Admin\WCAdminUser;
|
||||
|
||||
/**
|
||||
* Task class.
|
||||
*/
|
||||
abstract class Task {
|
||||
/**
|
||||
* Task traits.
|
||||
*/
|
||||
use TaskTraits;
|
||||
|
||||
/**
|
||||
* Name of the dismiss option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DISMISSED_OPTION = 'woocommerce_task_list_dismissed_tasks';
|
||||
|
||||
/**
|
||||
* Name of the snooze option.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @deprecated 7.2.0
|
||||
*/
|
||||
const SNOOZED_OPTION = 'woocommerce_task_list_remind_me_later_tasks';
|
||||
|
||||
/**
|
||||
* Name of the actioned option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ACTIONED_OPTION = 'woocommerce_task_list_tracked_completed_actions';
|
||||
|
||||
/**
|
||||
* Option name of completed tasks.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const COMPLETED_OPTION = 'woocommerce_task_list_tracked_completed_tasks';
|
||||
|
||||
/**
|
||||
* Name of the active task transient.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ACTIVE_TASK_TRANSIENT = 'wc_onboarding_active_task';
|
||||
|
||||
/**
|
||||
* Parent task list.
|
||||
*
|
||||
* @var TaskList
|
||||
*/
|
||||
protected $task_list;
|
||||
|
||||
/**
|
||||
* 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 TaskList|null $task_list Parent task list.
|
||||
*/
|
||||
public function __construct( $task_list = null ) {
|
||||
$this->task_list = $task_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_id();
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_title();
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_content();
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_time();
|
||||
|
||||
/**
|
||||
* Parent ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_parent_id() {
|
||||
if ( ! $this->task_list ) {
|
||||
return '';
|
||||
}
|
||||
return $this->task_list->get_list_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get task list options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_parent_options() {
|
||||
if ( ! $this->task_list ) {
|
||||
return array();
|
||||
}
|
||||
return $this->task_list->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom option.
|
||||
*
|
||||
* @param string $option_name name of custom option.
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function get_parent_option( $option_name ) {
|
||||
if ( $this->task_list && isset( $this->task_list->options[ $option_name ] ) ) {
|
||||
return $this->task_list->options[ $option_name ];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prefix event for track event naming.
|
||||
*
|
||||
* @param string $event_name Event name.
|
||||
* @return string
|
||||
*/
|
||||
public function prefix_event( $event_name ) {
|
||||
if ( ! $this->task_list ) {
|
||||
return '';
|
||||
}
|
||||
return $this->task_list->prefix_event( $event_name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional info.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_additional_info() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional data.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_additional_data() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Badge.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_badge() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Level.
|
||||
*
|
||||
* @deprecated 7.2.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_level() {
|
||||
wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '7.2.0' );
|
||||
|
||||
return 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action label.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_label() {
|
||||
return __( "Let's go", 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Action URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_url() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a task is dismissable.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_dismissable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->get_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->get_id();
|
||||
$update = update_option( self::DISMISSED_OPTION, array_unique( $dismissed ) );
|
||||
|
||||
if ( $update ) {
|
||||
$this->record_tracks_event( 'dismiss_task', array( 'task_name' => $this->get_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->get_id() ) );
|
||||
$update = update_option( self::DISMISSED_OPTION, $dismissed );
|
||||
|
||||
if ( $update ) {
|
||||
$this->record_tracks_event( 'undo_dismiss_task', array( 'task_name' => $this->get_id() ) );
|
||||
}
|
||||
|
||||
return $update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a task is snoozeable.
|
||||
*
|
||||
* @deprecated 7.2.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_snoozeable() {
|
||||
wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '7.2.0' );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the snoozed until datetime.
|
||||
*
|
||||
* @deprecated 7.2.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_snoozed_until() {
|
||||
wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '7.2.0' );
|
||||
|
||||
$snoozed_tasks = get_option( self::SNOOZED_OPTION, array() );
|
||||
if ( isset( $snoozed_tasks[ $this->get_id() ] ) ) {
|
||||
return $snoozed_tasks[ $this->get_id() ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bool for task snoozed.
|
||||
*
|
||||
* @deprecated 7.2.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_snoozed() {
|
||||
wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '7.2.0' );
|
||||
|
||||
if ( ! $this->is_snoozeable() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$snoozed = get_option( self::SNOOZED_OPTION, array() );
|
||||
|
||||
return isset( $snoozed[ $this->get_id() ] ) && $snoozed[ $this->get_id() ] > ( time() * 1000 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Snooze the task.
|
||||
*
|
||||
* @param string $duration Duration to snooze. day|hour|week.
|
||||
*
|
||||
* @deprecated 7.2.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function snooze( $duration = 'day' ) {
|
||||
wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '7.2.0' );
|
||||
|
||||
if ( ! $this->is_snoozeable() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$snoozed = get_option( self::SNOOZED_OPTION, array() );
|
||||
$snoozed_until = $this->duration_to_ms[ $duration ] + ( time() * 1000 );
|
||||
$snoozed[ $this->get_id() ] = $snoozed_until;
|
||||
$update = update_option( self::SNOOZED_OPTION, $snoozed );
|
||||
|
||||
if ( $update ) {
|
||||
if ( $update ) {
|
||||
$this->record_tracks_event( 'remindmelater_task', array( 'task_name' => $this->get_id() ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo task snooze.
|
||||
*
|
||||
* @deprecated 7.2.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function undo_snooze() {
|
||||
wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '7.2.0' );
|
||||
|
||||
$snoozed = get_option( self::SNOOZED_OPTION, array() );
|
||||
unset( $snoozed[ $this->get_id() ] );
|
||||
$update = update_option( self::SNOOZED_OPTION, $snoozed );
|
||||
|
||||
if ( $update ) {
|
||||
$this->record_tracks_event( 'undo_remindmelater_task', array( 'task_name' => $this->get_id() ) );
|
||||
}
|
||||
|
||||
return $update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a task list has previously been marked as complete.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_previously_completed() {
|
||||
$complete = get_option( self::COMPLETED_OPTION, array() );
|
||||
return in_array( $this->get_id(), $complete, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Track task completion if task is viewable.
|
||||
*/
|
||||
public function possibly_track_completion() {
|
||||
if ( $this->has_previously_completed() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Expensive check.
|
||||
if ( ! $this->is_complete() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$completed_tasks = get_option( self::COMPLETED_OPTION, array() );
|
||||
$completed_tasks[] = $this->get_id();
|
||||
update_option( self::COMPLETED_OPTION, $completed_tasks );
|
||||
$this->record_tracks_event( 'task_completed', array( 'task_name' => $this->get_id() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this as the active task across page loads.
|
||||
*/
|
||||
public function set_active() {
|
||||
if ( $this->is_complete() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
set_transient(
|
||||
self::ACTIVE_TASK_TRANSIENT,
|
||||
$this->get_id(),
|
||||
DAY_IN_SECONDS
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is the active task.
|
||||
*/
|
||||
public function is_active() {
|
||||
return get_transient( self::ACTIVE_TASK_TRANSIENT ) === $this->get_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store is capable of viewing the task.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if task is disabled.
|
||||
*
|
||||
* @deprecated 7.2.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_disabled() {
|
||||
wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '7.2.0' );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the task is complete.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
return self::is_actioned();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the task has been visited.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_visited() {
|
||||
$user_id = get_current_user_id();
|
||||
$response = WCAdminUser::get_user_data_field( $user_id, 'task_list_tracked_started_tasks' );
|
||||
$tracked_tasks = $response ? json_decode( $response, true ) : array();
|
||||
|
||||
return isset( $tracked_tasks[ $this->get_id() ] ) && $tracked_tasks[ $this->get_id() ] > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if should record event when task is viewed
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_record_view_event(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the task as JSON.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_json() {
|
||||
$this->possibly_track_completion();
|
||||
|
||||
return array(
|
||||
'id' => $this->get_id(),
|
||||
'parentId' => $this->get_parent_id(),
|
||||
'title' => $this->get_title(),
|
||||
'badge' => $this->get_badge(),
|
||||
'canView' => $this->can_view(),
|
||||
'content' => $this->get_content(),
|
||||
'additionalInfo' => $this->get_additional_info(),
|
||||
'actionLabel' => $this->get_action_label(),
|
||||
'actionUrl' => $this->get_action_url(),
|
||||
'isComplete' => $this->is_complete(),
|
||||
'time' => $this->get_time(),
|
||||
'level' => 3,
|
||||
'isActioned' => $this->is_actioned(),
|
||||
'isDismissed' => $this->is_dismissed(),
|
||||
'isDismissable' => $this->is_dismissable(),
|
||||
'isSnoozed' => false,
|
||||
'isSnoozeable' => false,
|
||||
'isVisited' => $this->is_visited(),
|
||||
'isDisabled' => false,
|
||||
'snoozedUntil' => null,
|
||||
'additionalData' => self::convert_object_to_camelcase( $this->get_additional_data() ),
|
||||
'eventPrefix' => $this->prefix_event( '' ),
|
||||
'recordViewEvent' => $this->get_record_view_event(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert object keys to camelcase.
|
||||
*
|
||||
* @param array $data Data to convert.
|
||||
* @return object
|
||||
*/
|
||||
public static function convert_object_to_camelcase( $data ) {
|
||||
if ( ! is_array( $data ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$new_object = (object) array();
|
||||
|
||||
foreach ( $data as $key => $value ) {
|
||||
$new_key = lcfirst( implode( '', array_map( 'ucfirst', explode( '_', $key ) ) ) );
|
||||
$new_object->$new_key = $value;
|
||||
}
|
||||
|
||||
return $new_object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a task as actioned. Used to verify an action has taken place in some tasks.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function mark_actioned() {
|
||||
$actioned = get_option( self::ACTIONED_OPTION, array() );
|
||||
|
||||
$actioned[] = $this->get_id();
|
||||
$update = update_option( self::ACTIONED_OPTION, array_unique( $actioned ) );
|
||||
|
||||
if ( $update ) {
|
||||
$this->record_tracks_event( 'actioned_task', array( 'task_name' => $this->get_id() ) );
|
||||
}
|
||||
|
||||
return $update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a task has been actioned.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_actioned() {
|
||||
return self::is_task_actioned( $this->get_id() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a provided task ID has been actioned.
|
||||
*
|
||||
* @param string $id Task ID.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_task_actioned( $id ) {
|
||||
$actioned = get_option( self::ACTIONED_OPTION, array() );
|
||||
return in_array( $id, $actioned, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorting function for tasks.
|
||||
*
|
||||
* @param Task $a Task a.
|
||||
* @param Task $b Task b.
|
||||
* @param array $sort_by list of columns with sort order.
|
||||
* @return int
|
||||
*/
|
||||
public static function sort( $a, $b, $sort_by = array() ) {
|
||||
$result = 0;
|
||||
foreach ( $sort_by as $data ) {
|
||||
$key = $data['key'];
|
||||
$a_val = $a->$key ?? false;
|
||||
$b_val = $b->$key ?? false;
|
||||
if ( 'asc' === $data['order'] ) {
|
||||
$result = $a_val <=> $b_val;
|
||||
} else {
|
||||
$result = $b_val <=> $a_val;
|
||||
}
|
||||
|
||||
if ( 0 !== $result ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
<?php
|
||||
/**
|
||||
* Handles storage and retrieval of a task list
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
use Automattic\WooCommerce\Admin\WCAdminHelper;
|
||||
|
||||
|
||||
/**
|
||||
* Task List class.
|
||||
*/
|
||||
class TaskList {
|
||||
/**
|
||||
* Task traits.
|
||||
*/
|
||||
use TaskTraits;
|
||||
|
||||
/**
|
||||
* Option name hidden task lists.
|
||||
*/
|
||||
const HIDDEN_OPTION = 'woocommerce_task_list_hidden_lists';
|
||||
|
||||
/**
|
||||
* Option name of completed task lists.
|
||||
*/
|
||||
const COMPLETED_OPTION = 'woocommerce_task_list_completed_lists';
|
||||
|
||||
/**
|
||||
* Option name of hidden reminder bar.
|
||||
*/
|
||||
const REMINDER_BAR_HIDDEN_OPTION = 'woocommerce_task_list_reminder_bar_hidden';
|
||||
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id = '';
|
||||
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $hidden_id = '';
|
||||
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $display_progress_header = false;
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $title = '';
|
||||
|
||||
/**
|
||||
* Tasks.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $tasks = array();
|
||||
|
||||
/**
|
||||
* Sort keys.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $sort_by = array();
|
||||
|
||||
/**
|
||||
* Event prefix.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $event_prefix = null;
|
||||
|
||||
/**
|
||||
* Task list visibility.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $visible = true;
|
||||
|
||||
/**
|
||||
* Array of custom options.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $options = array();
|
||||
|
||||
/**
|
||||
* Array of TaskListSection.
|
||||
*
|
||||
* @deprecated 7.2.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $sections = array();
|
||||
|
||||
/**
|
||||
* Key value map of task class and id used for sections.
|
||||
*
|
||||
* @deprecated 7.2.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $task_class_id_map = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $data Task list data.
|
||||
*/
|
||||
public function __construct( $data = array() ) {
|
||||
$defaults = array(
|
||||
'id' => null,
|
||||
'hidden_id' => null,
|
||||
'title' => '',
|
||||
'tasks' => array(),
|
||||
'sort_by' => array(),
|
||||
'event_prefix' => null,
|
||||
'options' => array(),
|
||||
'visible' => true,
|
||||
'display_progress_header' => false,
|
||||
);
|
||||
|
||||
$data = wp_parse_args( $data, $defaults );
|
||||
|
||||
$this->id = $data['id'];
|
||||
$this->hidden_id = $data['hidden_id'];
|
||||
$this->title = $data['title'];
|
||||
$this->sort_by = $data['sort_by'];
|
||||
$this->event_prefix = $data['event_prefix'];
|
||||
$this->options = $data['options'];
|
||||
$this->visible = $data['visible'];
|
||||
$this->display_progress_header = $data['display_progress_header'];
|
||||
|
||||
foreach ( $data['tasks'] as $task_name ) {
|
||||
$class = 'Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\\' . $task_name;
|
||||
$task = new $class( $this );
|
||||
$this->add_task( $task );
|
||||
}
|
||||
|
||||
$this->possibly_remove_reminder_bar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the task list is hidden.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_hidden() {
|
||||
$hidden = get_option( self::HIDDEN_OPTION, array() );
|
||||
return in_array( $this->hidden_id ? $this->hidden_id : $this->id, $hidden, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the task list is visible.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_visible() {
|
||||
if ( ! $this->visible || $this->is_hidden() || ! count( $this->get_viewable_tasks() ) > 0 ) {
|
||||
return false;
|
||||
}
|
||||
return ! $this->is_hidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the task list.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hide() {
|
||||
if ( $this->is_hidden() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$viewable_tasks = $this->get_viewable_tasks();
|
||||
$completed_count = array_reduce(
|
||||
$viewable_tasks,
|
||||
function( $total, $task ) {
|
||||
return $task->is_complete() ? $total + 1 : $total;
|
||||
},
|
||||
0
|
||||
);
|
||||
|
||||
$this->record_tracks_event(
|
||||
'completed',
|
||||
array(
|
||||
'action' => 'remove_card',
|
||||
'completed_task_count' => $completed_count,
|
||||
'incomplete_task_count' => count( $viewable_tasks ) - $completed_count,
|
||||
'tasklist_id' => $this->id,
|
||||
)
|
||||
);
|
||||
|
||||
$hidden = get_option( self::HIDDEN_OPTION, array() );
|
||||
$hidden[] = $this->hidden_id ? $this->hidden_id : $this->id;
|
||||
$this->maybe_set_default_layout( $hidden );
|
||||
return update_option( self::HIDDEN_OPTION, array_unique( $hidden ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default homepage layout to two_columns if "setup" tasklist is completed or hidden.
|
||||
*
|
||||
* @param array $completed_or_hidden_tasklist_ids Array of tasklist ids.
|
||||
*/
|
||||
public function maybe_set_default_layout( $completed_or_hidden_tasklist_ids ) {
|
||||
if ( in_array( 'setup', $completed_or_hidden_tasklist_ids, true ) ) {
|
||||
update_option( 'woocommerce_default_homepage_layout', 'two_columns' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo hiding of the task list.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function unhide() {
|
||||
$hidden = get_option( self::HIDDEN_OPTION, array() );
|
||||
$hidden = array_diff( $hidden, array( $this->hidden_id ? $this->hidden_id : $this->id ) );
|
||||
return update_option( self::HIDDEN_OPTION, $hidden );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if all viewable tasks are complete.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
foreach ( $this->get_viewable_tasks() as $viewable_task ) {
|
||||
if ( $viewable_task->is_complete() === false ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a task list has previously been marked as complete.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_previously_completed() {
|
||||
$complete = get_option( self::COMPLETED_OPTION, array() );
|
||||
return in_array( $this->get_list_id(), $complete, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add task to the task list.
|
||||
*
|
||||
* @param Task $task Task class.
|
||||
*/
|
||||
public function add_task( $task ) {
|
||||
if ( ! is_subclass_of( $task, 'Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task' ) ) {
|
||||
return new \WP_Error(
|
||||
'woocommerce_task_list_invalid_task',
|
||||
__( 'Task is not a subclass of `Task`', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
if ( array_search( $task, $this->tasks, true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->tasks[] = $task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get only visible tasks in list.
|
||||
*
|
||||
* @param string $task_id id of task.
|
||||
* @return Task
|
||||
*/
|
||||
public function get_task( $task_id ) {
|
||||
return current(
|
||||
array_filter(
|
||||
$this->tasks,
|
||||
function( $task ) use ( $task_id ) {
|
||||
return $task->get_id() === $task_id;
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get only visible tasks in list.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_viewable_tasks() {
|
||||
return array_values(
|
||||
array_filter(
|
||||
$this->tasks,
|
||||
function( $task ) {
|
||||
return $task->can_view();
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get task list sections.
|
||||
*
|
||||
* @deprecated 7.2.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_sections() {
|
||||
wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '7.2.0' );
|
||||
|
||||
return $this->sections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Track list completion of viewable tasks.
|
||||
*/
|
||||
public function possibly_track_completion() {
|
||||
if ( $this->has_previously_completed() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's hidden, completion is tracked via hide method.
|
||||
if ( $this->is_hidden() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Expensive check, do it last.
|
||||
if ( ! $this->is_complete() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$completed_lists = get_option( self::COMPLETED_OPTION, array() );
|
||||
$completed_lists[] = $this->get_list_id();
|
||||
update_option( self::COMPLETED_OPTION, $completed_lists );
|
||||
$this->maybe_set_default_layout( $completed_lists );
|
||||
$this->record_tracks_event(
|
||||
'tasks_completed',
|
||||
array(
|
||||
'tasklist_id' => $this->id,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the attached tasks array.
|
||||
*
|
||||
* @param array $sort_by list of columns with sort order.
|
||||
* @return TaskList returns $this, for chaining.
|
||||
*/
|
||||
public function sort_tasks( $sort_by = array() ) {
|
||||
$sort_by = count( $sort_by ) > 0 ? $sort_by : $this->sort_by;
|
||||
if ( 0 !== count( $sort_by ) ) {
|
||||
usort(
|
||||
$this->tasks,
|
||||
function( $a, $b ) use ( $sort_by ) {
|
||||
return Task::sort( $a, $b, $sort_by );
|
||||
}
|
||||
);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefix event for track event naming.
|
||||
*
|
||||
* @param string $event_name Event name.
|
||||
* @return string
|
||||
*/
|
||||
public function prefix_event( $event_name ) {
|
||||
if ( null !== $this->event_prefix ) {
|
||||
return $this->event_prefix . $event_name;
|
||||
}
|
||||
return $this->get_list_id() . '_tasklist_' . $event_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns option to keep completed task list.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_keep_completed_task_list() {
|
||||
return get_option( 'woocommerce_task_list_keep_completed', 'no' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove reminder bar four weeks after store creation.
|
||||
*/
|
||||
public static function possibly_remove_reminder_bar() {
|
||||
$bar_hidden = get_option( self::REMINDER_BAR_HIDDEN_OPTION, 'no' );
|
||||
$active_for_four_weeks = WCAdminHelper::is_wc_admin_active_for( WEEK_IN_SECONDS * 4 );
|
||||
|
||||
if ( 'yes' === $bar_hidden || ! $active_for_four_weeks ) {
|
||||
return;
|
||||
}
|
||||
|
||||
update_option( self::REMINDER_BAR_HIDDEN_OPTION, 'yes' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list for use in JSON.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_json() {
|
||||
$this->possibly_track_completion();
|
||||
$tasks_json = array();
|
||||
|
||||
// We have no use for hidden lists, it's expensive to compute individual tasks completion.
|
||||
// Exception: Secret tasklist is always hidden.
|
||||
if ( $this->is_visible() || 'secret_tasklist' === $this->id ) {
|
||||
foreach ( $this->tasks as $task ) {
|
||||
$json = $task->get_json();
|
||||
if ( $json['canView'] ) {
|
||||
$tasks_json[] = $json;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'id' => $this->get_list_id(),
|
||||
'title' => $this->title,
|
||||
'isHidden' => $this->is_hidden(),
|
||||
'isVisible' => $this->is_visible(),
|
||||
'isComplete' => $this->is_complete(),
|
||||
'tasks' => $tasks_json,
|
||||
'eventPrefix' => $this->prefix_event( '' ),
|
||||
'displayProgressHeader' => $this->display_progress_header,
|
||||
'keepCompletedTaskList' => $this->get_keep_completed_task_list(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/**
|
||||
* Handles storage and retrieval of a task list section
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks;
|
||||
|
||||
/**
|
||||
* Task List section class.
|
||||
*
|
||||
* @deprecated 7.2.0
|
||||
*/
|
||||
class TaskListSection {
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id = '';
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $title = '';
|
||||
|
||||
/**
|
||||
* Description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $description = '';
|
||||
|
||||
/**
|
||||
* Image.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $image = '';
|
||||
|
||||
/**
|
||||
* Tasks.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $task_names = array();
|
||||
|
||||
/**
|
||||
* Parent task list.
|
||||
*
|
||||
* @var TaskList
|
||||
*/
|
||||
protected $task_list;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $data Task list data.
|
||||
* @param TaskList|null $task_list Parent task list.
|
||||
*/
|
||||
public function __construct( $data = array(), $task_list = null ) {
|
||||
$defaults = array(
|
||||
'id' => '',
|
||||
'title' => '',
|
||||
'description' => '',
|
||||
'image' => '',
|
||||
'tasks' => array(),
|
||||
);
|
||||
|
||||
$data = wp_parse_args( $data, $defaults );
|
||||
|
||||
$this->task_list = $task_list;
|
||||
$this->id = $data['id'];
|
||||
$this->title = $data['title'];
|
||||
$this->description = $data['description'];
|
||||
$this->image = $data['image'];
|
||||
$this->task_names = $data['task_names'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if section is complete.
|
||||
*
|
||||
* @return boolean;
|
||||
*/
|
||||
private function is_complete() {
|
||||
$complete = true;
|
||||
foreach ( $this->task_names as $task_name ) {
|
||||
if ( null !== $this->task_list && isset( $this->task_list->task_class_id_map[ $task_name ] ) ) {
|
||||
$task = $this->task_list->get_task( $this->task_list->task_class_id_map[ $task_name ] );
|
||||
if ( $task->can_view() && ! $task->is_complete() ) {
|
||||
$complete = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $complete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list for use in JSON.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_json() {
|
||||
return array(
|
||||
'id' => $this->id,
|
||||
'title' => $this->title,
|
||||
'description' => $this->description,
|
||||
'image' => $this->image,
|
||||
'tasks' => array_map(
|
||||
function( $task_name ) {
|
||||
if ( null !== $this->task_list && isset( $this->task_list->task_class_id_map[ $task_name ] ) ) {
|
||||
return $this->task_list->task_class_id_map[ $task_name ];
|
||||
}
|
||||
return '';
|
||||
},
|
||||
$this->task_names
|
||||
),
|
||||
'isComplete' => $this->is_complete(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
<?php
|
||||
/**
|
||||
* Handles storage and retrieval of task lists
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\DeprecatedExtendedTask;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\ReviewShippingOptions;
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* Boolean value to indicate if default tasks have been added.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $default_tasks_loaded = false;
|
||||
|
||||
/**
|
||||
* The contents of this array is used in init_tasks() to run their init() methods.
|
||||
* If the classes do not have an init() method then nothing is executed.
|
||||
* Beyond that, adding tasks to this list has no effect, see init_default_lists() for the list of tasks.
|
||||
* that are added for each task list.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
const DEFAULT_TASKS = array(
|
||||
'StoreDetails',
|
||||
'Products',
|
||||
'WooCommercePayments',
|
||||
'Payments',
|
||||
'Tax',
|
||||
'Shipping',
|
||||
'Marketing',
|
||||
'Appearance',
|
||||
'AdditionalPayments',
|
||||
'ReviewShippingOptions',
|
||||
'GetMobileApp',
|
||||
);
|
||||
|
||||
/**
|
||||
* 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( 'admin_init', array( __CLASS__, 'set_active_task' ), 5 );
|
||||
add_action( 'init', array( __CLASS__, 'init_tasks' ) );
|
||||
add_action( 'admin_menu', array( __CLASS__, 'menu_task_count' ) );
|
||||
add_filter( 'woocommerce_admin_shared_settings', array( __CLASS__, 'task_list_preloaded_settings' ), 20 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an experiment is the treatment or control.
|
||||
*
|
||||
* @param string $name Name prefix of experiment.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_experiment_treatment( $name ) {
|
||||
$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
|
||||
);
|
||||
|
||||
$date = new \DateTime();
|
||||
$date->setTimeZone( new \DateTimeZone( 'UTC' ) );
|
||||
|
||||
$experiment_name = sprintf(
|
||||
'%s_%s_%s',
|
||||
$name,
|
||||
$date->format( 'Y' ),
|
||||
$date->format( 'm' )
|
||||
);
|
||||
return $abtest->get_variation( $experiment_name ) === 'treatment';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize default lists.
|
||||
*/
|
||||
public static function init_default_lists() {
|
||||
$tasks = array(
|
||||
'CustomizeStore',
|
||||
'StoreDetails',
|
||||
'Products',
|
||||
'Appearance',
|
||||
'WooCommercePayments',
|
||||
'Payments',
|
||||
'Tax',
|
||||
'Shipping',
|
||||
'Marketing',
|
||||
'LaunchYourStore',
|
||||
);
|
||||
|
||||
if ( Features::is_enabled( 'core-profiler' ) ) {
|
||||
$key = array_search( 'StoreDetails', $tasks, true );
|
||||
if ( false !== $key ) {
|
||||
unset( $tasks[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the old Personalize your store task if the new CustomizeStore is enabled.
|
||||
$task_to_remove = Features::is_enabled( 'customize-store' ) ? 'Appearance' : 'CustomizeStore';
|
||||
$store_customisation_task_index = array_search( $task_to_remove, $tasks, true );
|
||||
|
||||
if ( false !== $store_customisation_task_index ) {
|
||||
unset( $tasks[ $store_customisation_task_index ] );
|
||||
}
|
||||
|
||||
self::add_list(
|
||||
array(
|
||||
'id' => 'setup',
|
||||
'title' => __( 'Get ready to start selling', 'woocommerce' ),
|
||||
'tasks' => $tasks,
|
||||
'display_progress_header' => true,
|
||||
'event_prefix' => 'tasklist_',
|
||||
'options' => array(
|
||||
'use_completed_title' => true,
|
||||
),
|
||||
'visible' => true,
|
||||
)
|
||||
);
|
||||
|
||||
self::add_list(
|
||||
array(
|
||||
'id' => 'extended',
|
||||
'title' => __( 'Things to do next', 'woocommerce' ),
|
||||
'sort_by' => array(
|
||||
array(
|
||||
'key' => 'is_complete',
|
||||
'order' => 'asc',
|
||||
),
|
||||
array(
|
||||
'key' => 'level',
|
||||
'order' => 'asc',
|
||||
),
|
||||
),
|
||||
'tasks' => array(
|
||||
'StoreConnect',
|
||||
'AdditionalPayments',
|
||||
'GetMobileApp',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( Features::is_enabled( 'shipping-smart-defaults' ) ) {
|
||||
self::add_task(
|
||||
'extended',
|
||||
new ReviewShippingOptions(
|
||||
self::get_list( 'extended' )
|
||||
)
|
||||
);
|
||||
|
||||
// Tasklist that will never be shown in homescreen,
|
||||
// used for having tasks that are accessed by other means.
|
||||
self::add_list(
|
||||
array(
|
||||
'id' => 'secret_tasklist',
|
||||
'hidden_id' => 'setup',
|
||||
'tasks' => array(
|
||||
'ExperimentalShippingRecommendation',
|
||||
),
|
||||
'event_prefix' => 'secret_tasklist_',
|
||||
'visible' => false,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( has_filter( 'woocommerce_admin_experimental_onboarding_tasklists' ) ) {
|
||||
/**
|
||||
* Filter to override default task lists.
|
||||
*
|
||||
* @since 7.4
|
||||
* @param array $lists Array of tasklists.
|
||||
*/
|
||||
self::$lists = apply_filters( 'woocommerce_admin_experimental_onboarding_tasklists', self::$lists );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize tasks.
|
||||
*/
|
||||
public static function init_tasks() {
|
||||
foreach ( self::DEFAULT_TASKS as $task ) {
|
||||
$class = 'Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\\' . $task;
|
||||
if ( ! method_exists( $class, 'init' ) ) {
|
||||
continue;
|
||||
}
|
||||
$class::init();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily store the active task to persist across page loads when necessary.
|
||||
* Most tasks do not need this.
|
||||
*/
|
||||
public static function set_active_task() {
|
||||
if ( ! isset( $_GET[ Task::ACTIVE_TASK_TRANSIENT ] ) || ! current_user_can( 'manage_woocommerce' ) ) { // phpcs:ignore csrf ok.
|
||||
return;
|
||||
}
|
||||
$referer = wp_get_referer();
|
||||
if ( ! $referer || 0 !== strpos( $referer, wc_admin_url() ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$task_id = sanitize_title_with_dashes( wp_unslash( $_GET[ Task::ACTIVE_TASK_TRANSIENT ] ) ); // phpcs:ignore csrf ok.
|
||||
|
||||
$task = self::get_task( $task_id );
|
||||
|
||||
if ( ! $task ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$task->set_active();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a task list.
|
||||
*
|
||||
* @param array $args Task list properties.
|
||||
* @return \WP_Error|TaskList
|
||||
*/
|
||||
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 );
|
||||
return self::$lists[ $args['id'] ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add task to a given task list.
|
||||
*
|
||||
* @param string $list_id List ID to add the task to.
|
||||
* @param Task $task Task object.
|
||||
*
|
||||
* @return \WP_Error|Task
|
||||
*/
|
||||
public static function add_task( $list_id, $task ) {
|
||||
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( $task );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add default extended task lists.
|
||||
*
|
||||
* @param array $extended_tasks list of extended tasks.
|
||||
*/
|
||||
public static function maybe_add_extended_tasks( $extended_tasks ) {
|
||||
$tasks = $extended_tasks ?? array();
|
||||
|
||||
foreach ( self::$lists as $task_list ) {
|
||||
if ( 'extended' !== substr( $task_list->id, 0, 8 ) ) {
|
||||
continue;
|
||||
}
|
||||
foreach ( $tasks as $args ) {
|
||||
$task = new DeprecatedExtendedTask( $task_list, $args );
|
||||
$task_list->add_task( $task );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all task lists.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_lists() {
|
||||
return self::$lists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all task lists.
|
||||
*
|
||||
* @param array $ids list of task list ids.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_lists_by_ids( $ids ) {
|
||||
return array_filter(
|
||||
self::$lists,
|
||||
function( $list ) use ( $ids ) {
|
||||
return in_array( $list->get_list_id(), $ids, true );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all task list ids.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_list_ids() {
|
||||
return array_keys( self::$lists );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all task lists.
|
||||
*/
|
||||
public static function clear_lists() {
|
||||
self::$lists = array();
|
||||
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_visible();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve a task list by ID.
|
||||
*
|
||||
* @param String $id Task list ID.
|
||||
*
|
||||
* @return TaskList|null
|
||||
*/
|
||||
public static function get_list( $id ) {
|
||||
if ( isset( self::$lists[ $id ] ) ) {
|
||||
return self::$lists[ $id ];
|
||||
}
|
||||
|
||||
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_list( $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->get_id() ) {
|
||||
return $task;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return number of setup tasks remaining
|
||||
*
|
||||
* @return number
|
||||
*/
|
||||
public static function setup_tasks_remaining() {
|
||||
$setup_list = self::get_list( 'setup' );
|
||||
|
||||
if ( ! $setup_list || $setup_list->is_hidden() || $setup_list->has_previously_completed() || $setup_list->is_complete() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$remaining_tasks = array_values(
|
||||
array_filter(
|
||||
$setup_list->get_viewable_tasks(),
|
||||
function( $task ) {
|
||||
return ! $task->is_complete();
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
return count( $remaining_tasks );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add badge to homescreen menu item for remaining tasks
|
||||
*/
|
||||
public static function menu_task_count() {
|
||||
global $submenu;
|
||||
|
||||
$tasks_count = self::setup_tasks_remaining();
|
||||
|
||||
if ( ! $tasks_count || ! isset( $submenu['woocommerce'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $submenu['woocommerce'] as $key => $menu_item ) {
|
||||
if ( 0 === strpos( $menu_item[0], _x( 'Home', 'Admin menu name', 'woocommerce' ) ) ) {
|
||||
$submenu['woocommerce'][ $key ][0] .= ' <span class="awaiting-mod update-plugins remaining-tasks-badge woocommerce-task-list-remaining-tasks-badge"><span class="count-' . esc_attr( $tasks_count ) . '">' . absint( $tasks_count ) . '</span></span>'; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add visible list ids to component settings.
|
||||
*
|
||||
* @param array $settings Component settings.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function task_list_preloaded_settings( $settings ) {
|
||||
$settings['visibleTaskListIds'] = array_keys( self::get_visible() );
|
||||
|
||||
return $settings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* Task and TaskList Traits
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* TaskTraits class.
|
||||
*/
|
||||
trait TaskTraits {
|
||||
/**
|
||||
* Record a tracks event with the prefixed event name.
|
||||
*
|
||||
* @param string $event_name Event name.
|
||||
* @param array $args Array of tracks arguments.
|
||||
* @return string Prefixed event name.
|
||||
*/
|
||||
public function record_tracks_event( $event_name, $args = array() ) {
|
||||
if ( ! $this->get_list_id() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$prefixed_event_name = $this->prefix_event( $event_name );
|
||||
|
||||
wc_admin_record_tracks_event(
|
||||
$prefixed_event_name,
|
||||
$args
|
||||
);
|
||||
|
||||
return $prefixed_event_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the task list ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_list_id() {
|
||||
$namespaced_class = get_class( $this );
|
||||
return is_subclass_of( $namespaced_class, 'Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task' )
|
||||
? $this->get_parent_id()
|
||||
: $this->id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\Payments;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\WooCommercePayments;
|
||||
use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\Init;
|
||||
|
||||
/**
|
||||
* Payments Task
|
||||
*/
|
||||
class AdditionalPayments extends Payments {
|
||||
|
||||
/**
|
||||
* Used to cache is_complete() method result.
|
||||
*
|
||||
* @var null
|
||||
*/
|
||||
private $is_complete_result = null;
|
||||
|
||||
/**
|
||||
* Used to cache can_view() method result.
|
||||
*
|
||||
* @var null
|
||||
*/
|
||||
private $can_view_result = null;
|
||||
|
||||
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'payments';
|
||||
}
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return __(
|
||||
'Set up additional payment options',
|
||||
'woocommerce'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
return __(
|
||||
'Choose payment providers and enable payment methods at checkout.',
|
||||
'woocommerce'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_time() {
|
||||
return __( '2 minutes', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Task completion.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
if ( null === $this->is_complete_result ) {
|
||||
$this->is_complete_result = self::has_enabled_additional_gateways();
|
||||
}
|
||||
|
||||
return $this->is_complete_result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Task visibility.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view() {
|
||||
if ( ! Features::is_enabled( 'payment-gateway-suggestions' ) ) {
|
||||
// Hide task if feature not enabled.
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( null !== $this->can_view_result ) {
|
||||
return $this->can_view_result;
|
||||
}
|
||||
|
||||
// Show task if woocommerce-payments is connected or if there are any suggested gateways in other category enabled.
|
||||
$this->can_view_result = (
|
||||
WooCommercePayments::is_connected() ||
|
||||
self::has_enabled_other_category_gateways()
|
||||
);
|
||||
|
||||
// Early return if task is not visible.
|
||||
if ( ! $this->can_view_result ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Show task if there are any suggested gateways in additional category.
|
||||
$this->can_view_result = ! empty( self::get_suggestion_gateways( 'category_additional' ) );
|
||||
|
||||
return $this->can_view_result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_url() {
|
||||
return admin_url( 'admin.php?page=wc-admin&task=payments' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store has any enabled gateways in other category.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function has_enabled_other_category_gateways() {
|
||||
$other_gateways = self::get_suggestion_gateways( 'category_other' );
|
||||
$other_gateways_ids = wp_list_pluck( $other_gateways, 'id' );
|
||||
|
||||
return self::has_enabled_gateways(
|
||||
function( $gateway ) use ( $other_gateways_ids ) {
|
||||
return in_array( $gateway->id, $other_gateways_ids, true );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store has any enabled gateways in additional category.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function has_enabled_additional_gateways() {
|
||||
$additional_gateways = self::get_suggestion_gateways( 'category_additional' );
|
||||
$additional_gateways_ids = wp_list_pluck( $additional_gateways, 'id' );
|
||||
|
||||
return self::has_enabled_gateways(
|
||||
function( $gateway ) use ( $additional_gateways_ids ) {
|
||||
return 'yes' === $gateway->enabled
|
||||
&& in_array( $gateway->id, $additional_gateways_ids, true );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store has any enabled gateways based on the given criteria.
|
||||
*
|
||||
* @param callable|null $filter A callback function to filter the gateways.
|
||||
* @return bool
|
||||
*/
|
||||
private static function has_enabled_gateways( $filter = null ) {
|
||||
$gateways = WC()->payment_gateways->get_available_payment_gateways();
|
||||
$enabled_gateways = array_filter(
|
||||
$gateways,
|
||||
function( $gateway ) use ( $filter ) {
|
||||
if ( is_callable( $filter ) ) {
|
||||
return 'yes' === $gateway->enabled && call_user_func( $filter, $gateway );
|
||||
} else {
|
||||
return 'yes' === $gateway->enabled;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return ! empty( $enabled_gateways );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of gateways to suggest.
|
||||
*
|
||||
* @param string $filter_by Filter by category. "category_additional" or "category_other".
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function get_suggestion_gateways( $filter_by = 'category_additional' ) {
|
||||
$country = wc_get_base_location()['country'];
|
||||
$plugin_suggestions = Init::get_suggestions();
|
||||
$plugin_suggestions = array_filter(
|
||||
$plugin_suggestions,
|
||||
function( $plugin ) use ( $country, $filter_by ) {
|
||||
if ( ! isset( $plugin->{$filter_by} ) || ! isset( $plugin->plugins[0] ) ) {
|
||||
return false;
|
||||
}
|
||||
return in_array( $country, $plugin->{$filter_by}, true );
|
||||
}
|
||||
);
|
||||
return $plugin_suggestions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\PageController;
|
||||
use Automattic\WooCommerce\Internal\Admin\Loader;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\Products;
|
||||
use Automattic\WooCommerce\Internal\Admin\WCAdminAssets;
|
||||
|
||||
/**
|
||||
* Appearance Task
|
||||
*/
|
||||
class Appearance extends Task {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( ! $this->is_complete() ) {
|
||||
add_action( 'load-theme-install.php', array( $this, 'mark_actioned' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'appearance';
|
||||
}
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return __( 'Choose your theme', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
return __(
|
||||
"Choose a theme that best fits your brand's look and feel, then make it your own. Change the colors, add your logo, and create pages.",
|
||||
'woocommerce'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_time() {
|
||||
return __( '2 minutes', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Action label.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_label() {
|
||||
return __( 'Choose theme', 'woocommerce' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
use Jetpack_Gutenberg;
|
||||
|
||||
/**
|
||||
* Customize Your Store Task
|
||||
*/
|
||||
class CustomizeStore extends Task {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param TaskList $task_list Parent task list.
|
||||
*/
|
||||
public function __construct( $task_list ) {
|
||||
parent::__construct( $task_list );
|
||||
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'possibly_add_site_editor_scripts' ) );
|
||||
|
||||
add_action( 'show_admin_bar', array( $this, 'possibly_hide_wp_admin_bar' ) );
|
||||
|
||||
// Hook to remove unwanted UI elements when users are viewing with ?cys-hide-admin-bar=true.
|
||||
add_action( 'wp_head', array( $this, 'possibly_remove_unwanted_ui_elements' ) );
|
||||
|
||||
add_action( 'save_post_wp_global_styles', array( $this, 'mark_task_as_complete' ), 10, 3 );
|
||||
add_action( 'save_post_wp_template', array( $this, 'mark_task_as_complete' ), 10, 3 );
|
||||
add_action( 'save_post_wp_template_part', array( $this, 'mark_task_as_complete' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the CYS task as complete whenever the user updates their global styles.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @param \WP_Post $post Post object.
|
||||
* @param bool $update Whether this is an existing post being updated.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mark_task_as_complete( $post_id, $post, $update ) {
|
||||
if ( $post instanceof \WP_Post ) {
|
||||
$is_cys_complete = '{"version": 2, "isGlobalStylesUserThemeJSON": true }' !== $post->post_content || in_array( $post->post_type, array( 'wp_template', 'wp_template_part' ), true );
|
||||
|
||||
if ( $is_cys_complete ) {
|
||||
update_option( 'woocommerce_admin_customize_store_completed', 'yes' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'customize-store';
|
||||
}
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return __( 'Customize your store ', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_time() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Task completion.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
return get_option( 'woocommerce_admin_customize_store_completed' ) === 'yes';
|
||||
}
|
||||
|
||||
/**
|
||||
* Task visibility.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_url() {
|
||||
return admin_url( 'admin.php?page=wc-admin&path=%2Fcustomize-store' );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Possibly add site editor scripts.
|
||||
*/
|
||||
public function possibly_add_site_editor_scripts() {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
$is_wc_admin_page = (
|
||||
isset( $_GET['page'] ) &&
|
||||
'wc-admin' === $_GET['page'] &&
|
||||
isset( $_GET['path'] )
|
||||
);
|
||||
|
||||
$is_assembler_hub = $is_wc_admin_page && str_starts_with( wc_clean( wp_unslash( $_GET['path'] ) ), '/customize-store/assembler-hub' );
|
||||
$is_transitional_page = $is_wc_admin_page && str_starts_with( wc_clean( wp_unslash( $_GET['path'] ) ), '/customize-store/transitional' );
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
if ( ! ( $is_assembler_hub || $is_transitional_page ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// See: https://github.com/WordPress/WordPress/blob/master/wp-admin/site-editor.php.
|
||||
if ( ! wp_is_block_theme() ) {
|
||||
wp_die( esc_html__( 'The theme you are currently using is not compatible.', 'woocommerce' ) );
|
||||
}
|
||||
global $editor_styles;
|
||||
|
||||
// Flag that we're loading the block editor.
|
||||
$current_screen = get_current_screen();
|
||||
$current_screen->is_block_editor( true );
|
||||
|
||||
// Default to is-fullscreen-mode to avoid jumps in the UI.
|
||||
add_filter(
|
||||
'admin_body_class',
|
||||
static function ( $classes ) {
|
||||
return "$classes is-fullscreen-mode";
|
||||
}
|
||||
);
|
||||
|
||||
$block_editor_context = new \WP_Block_Editor_Context( array( 'name' => 'core/edit-site' ) );
|
||||
$indexed_template_types = array();
|
||||
foreach ( get_default_block_template_types() as $slug => $template_type ) {
|
||||
$template_type['slug'] = (string) $slug;
|
||||
$indexed_template_types[] = $template_type;
|
||||
}
|
||||
|
||||
$custom_settings = array(
|
||||
'siteUrl' => site_url(),
|
||||
'postsPerPage' => get_option( 'posts_per_page' ),
|
||||
'styles' => get_block_editor_theme_styles(),
|
||||
'defaultTemplateTypes' => $indexed_template_types,
|
||||
'defaultTemplatePartAreas' => get_allowed_block_template_part_areas(),
|
||||
'supportsLayout' => wp_theme_has_theme_json(),
|
||||
'supportsTemplatePartsMode' => ! wp_is_block_theme() && current_theme_supports( 'block-template-parts' ),
|
||||
);
|
||||
|
||||
// Add additional back-compat patterns registered by `current_screen` et al.
|
||||
$custom_settings['__experimentalAdditionalBlockPatterns'] = \WP_Block_Patterns_Registry::get_instance()->get_all_registered( true );
|
||||
$custom_settings['__experimentalAdditionalBlockPatternCategories'] = \WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered( true );
|
||||
|
||||
$editor_settings = get_block_editor_settings( $custom_settings, $block_editor_context );
|
||||
$active_global_styles_id = \WP_Theme_JSON_Resolver::get_user_global_styles_post_id();
|
||||
$active_theme = get_stylesheet();
|
||||
$preload_paths = array(
|
||||
array( '/wp/v2/media', 'OPTIONS' ),
|
||||
'/wp/v2/types?context=view',
|
||||
'/wp/v2/types/wp_template?context=edit',
|
||||
'/wp/v2/types/wp_template-part?context=edit',
|
||||
'/wp/v2/templates?context=edit&per_page=-1',
|
||||
'/wp/v2/template-parts?context=edit&per_page=-1',
|
||||
'/wp/v2/themes?context=edit&status=active',
|
||||
'/wp/v2/global-styles/' . $active_global_styles_id . '?context=edit',
|
||||
'/wp/v2/global-styles/' . $active_global_styles_id,
|
||||
'/wp/v2/global-styles/themes/' . $active_theme,
|
||||
);
|
||||
|
||||
block_editor_rest_api_preload( $preload_paths, $block_editor_context );
|
||||
|
||||
wp_add_inline_script(
|
||||
'wp-blocks',
|
||||
sprintf(
|
||||
'window.wcBlockSettings = %s;',
|
||||
wp_json_encode( $editor_settings )
|
||||
)
|
||||
);
|
||||
|
||||
// Preload server-registered block schemas.
|
||||
wp_add_inline_script(
|
||||
'wp-blocks',
|
||||
'wp.blocks.unstable__bootstrapServerSideBlockDefinitions(' . wp_json_encode( get_block_editor_server_block_settings() ) . ');'
|
||||
);
|
||||
|
||||
wp_add_inline_script(
|
||||
'wp-blocks',
|
||||
sprintf( 'wp.blocks.setCategories( %s );', wp_json_encode( isset( $editor_settings['blockCategories'] ) ? $editor_settings['blockCategories'] : array() ) ),
|
||||
'after'
|
||||
);
|
||||
|
||||
wp_enqueue_script( 'wp-editor' );
|
||||
wp_enqueue_script( 'wp-format-library' ); // Not sure if this is needed.
|
||||
wp_enqueue_script( 'wp-router' );
|
||||
wp_enqueue_style( 'wp-editor' );
|
||||
wp_enqueue_style( 'wp-edit-site' );
|
||||
wp_enqueue_style( 'wp-format-library' );
|
||||
wp_enqueue_media();
|
||||
|
||||
if (
|
||||
current_theme_supports( 'wp-block-styles' ) &&
|
||||
( ! is_array( $editor_styles ) || count( $editor_styles ) === 0 )
|
||||
) {
|
||||
wp_enqueue_style( 'wp-block-library-theme' );
|
||||
}
|
||||
/** This action is documented in wp-admin/edit-form-blocks.php
|
||||
*
|
||||
* @since 8.0.3
|
||||
*/
|
||||
do_action( 'enqueue_block_editor_assets' );
|
||||
|
||||
// Load Jetpack's block editor assets because they are not enqueued by default.
|
||||
if ( class_exists( 'Jetpack_Gutenberg' ) ) {
|
||||
Jetpack_Gutenberg::enqueue_block_editor_assets();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a small style to hide admin bar
|
||||
*
|
||||
* @param bool $show Whether to show the admin bar.
|
||||
*/
|
||||
public function possibly_hide_wp_admin_bar( $show ) {
|
||||
if ( isset( $_GET['cys-hide-admin-bar'] ) ) { // @phpcs:ignore
|
||||
return false;
|
||||
}
|
||||
return $show;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs script and add styles to remove unwanted elements and hide scrollbar
|
||||
* when users are viewing with ?cys-hide-admin-bar=true.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function possibly_remove_unwanted_ui_elements() {
|
||||
if ( isset( $_GET['cys-hide-admin-bar'] ) ) { // @phpcs:ignore
|
||||
echo '
|
||||
<style type="text/css">
|
||||
body { overflow: hidden; }
|
||||
</style>';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\Jetpack\Connection\Manager;
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
use Automattic\WooCommerce\Admin\PluginsHelper;
|
||||
|
||||
/**
|
||||
* Shipping Task
|
||||
*/
|
||||
class ExperimentalShippingRecommendation extends Task {
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'shipping-recommendation';
|
||||
}
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return __( 'Get your products shipped', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_time() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Task completion.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
return self::has_plugins_active() && self::has_jetpack_connected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Task visibility.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view() {
|
||||
return Features::is_enabled( 'shipping-smart-defaults' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Action URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_url() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store has any shipping zones.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_plugins_active() {
|
||||
return PluginsHelper::is_plugin_active( 'woocommerce-services' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Jetpack is connected.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_jetpack_connected() {
|
||||
$jetpack_connection_manager = new Manager( 'woocommerce' );
|
||||
|
||||
return $jetpack_connection_manager->is_connected() && $jetpack_connection_manager->has_connected_owner();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
use Automattic\Jetpack\Connection\Manager; // https://github.com/Automattic/jetpack/blob/trunk/projects/packages/connection/src/class-manager.php .
|
||||
|
||||
/**
|
||||
* Get Mobile App Task
|
||||
*/
|
||||
class GetMobileApp extends Task {
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'get-mobile-app';
|
||||
}
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return __( 'Get the free WooCommerce mobile app', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_time() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Task completion.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
return get_option( 'woocommerce_admin_dismissed_mobile_app_modal' ) === 'yes';
|
||||
}
|
||||
|
||||
/**
|
||||
* Task visibility.
|
||||
* Can view under these conditions:
|
||||
* - Jetpack is installed and connected && current site user has a wordpress.com account connected to jetpack
|
||||
* - Jetpack is not connected && current user is capable of installing plugins
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view() {
|
||||
$jetpack_can_be_installed = current_user_can( 'manage_woocommerce' ) && current_user_can( 'install_plugins' ) && ! self::is_jetpack_connected();
|
||||
$jetpack_is_installed_and_current_user_connected = self::is_current_user_connected();
|
||||
|
||||
return $jetpack_can_be_installed || $jetpack_is_installed_and_current_user_connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if site has any users connected to WordPress.com via JetPack
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function is_jetpack_connected() {
|
||||
if ( class_exists( '\Automattic\Jetpack\Connection\Manager' ) && method_exists( '\Automattic\Jetpack\Connection\Manager', 'is_active' ) ) {
|
||||
$connection = new Manager();
|
||||
return $connection->is_active();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current user is connected to Jetpack.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function is_current_user_connected() {
|
||||
if ( class_exists( '\Automattic\Jetpack\Connection\Manager' ) && method_exists( '\Automattic\Jetpack\Connection\Manager', 'is_user_connected' ) ) {
|
||||
$connection = new Manager();
|
||||
return $connection->is_connection_owner();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_url() {
|
||||
return admin_url( 'admin.php?page=wc-admin&mobileAppModal=true' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
|
||||
/**
|
||||
* Launch Your Store Task
|
||||
*/
|
||||
class LaunchYourStore extends Task {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param TaskList $task_list Parent task list.
|
||||
*/
|
||||
public function __construct( $task_list ) {
|
||||
parent::__construct( $task_list );
|
||||
|
||||
add_action( 'show_admin_bar', array( $this, 'possibly_hide_wp_admin_bar' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'launch-your-store';
|
||||
}
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return __( 'Launch your store', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
return __(
|
||||
"It's time to celebrate – you're ready to launch your store! Woo! Hit the button to preview your store and make it public.",
|
||||
'woocommerce'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_time() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Action URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_url() {
|
||||
return admin_url( 'admin.php?page=wc-admin&path=%2Flaunch-your-store' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Task completion.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
return 'yes' !== get_option( 'woocommerce_coming_soon' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Task visibility.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view() {
|
||||
return Features::is_enabled( 'launch-your-store' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the WP admin bar when the user is previewing the site.
|
||||
*
|
||||
* @param bool $show Whether to show the admin bar.
|
||||
*/
|
||||
public function possibly_hide_wp_admin_bar( $show ) {
|
||||
if ( isset( $_GET['site-preview'] ) ) { // @phpcs:ignore
|
||||
return false;
|
||||
}
|
||||
|
||||
global $wp;
|
||||
$http_referer = wp_get_referer() ?? '';
|
||||
$parsed_url = wp_parse_url( $http_referer, PHP_URL_QUERY );
|
||||
$query_string = is_string( $parsed_url ) ? $parsed_url : '';
|
||||
|
||||
// Check if the user is coming from the site preview link.
|
||||
if ( strpos( $query_string, 'site-preview' ) !== false ) {
|
||||
if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
|
||||
return $show;
|
||||
}
|
||||
|
||||
// Redirect to the current URL with the site-preview query string.
|
||||
$current_url =
|
||||
add_query_arg(
|
||||
array(
|
||||
'site-preview' => 1,
|
||||
),
|
||||
esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) )
|
||||
);
|
||||
wp_safe_redirect( $current_url );
|
||||
exit;
|
||||
}
|
||||
|
||||
return $show;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
use Automattic\WooCommerce\Internal\Admin\RemoteFreeExtensions\Init as RemoteFreeExtensions;
|
||||
|
||||
/**
|
||||
* Marketing Task
|
||||
*/
|
||||
class Marketing extends Task {
|
||||
/**
|
||||
* Used to cache is_complete() method result.
|
||||
*
|
||||
* @var null
|
||||
*/
|
||||
private $is_complete_result = null;
|
||||
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'marketing';
|
||||
}
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return __( 'Grow your business', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
return __(
|
||||
'Add recommended marketing tools to reach new customers and grow your business',
|
||||
'woocommerce'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_time() {
|
||||
return __( '2 minutes', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Task completion.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
if ( null === $this->is_complete_result ) {
|
||||
$this->is_complete_result = self::has_installed_extensions();
|
||||
}
|
||||
|
||||
return $this->is_complete_result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Task visibility.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view() {
|
||||
return Features::is_enabled( 'remote-free-extensions' ) && count( self::get_plugins() ) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the marketing plugins.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_plugins() {
|
||||
$bundles = RemoteFreeExtensions::get_extensions(
|
||||
array(
|
||||
'task-list/reach',
|
||||
'task-list/grow',
|
||||
)
|
||||
);
|
||||
|
||||
return array_reduce(
|
||||
$bundles,
|
||||
function( $plugins, $bundle ) {
|
||||
$visible = array();
|
||||
foreach ( $bundle['plugins'] as $plugin ) {
|
||||
if ( $plugin->is_visible ) {
|
||||
$visible[] = $plugin;
|
||||
}
|
||||
}
|
||||
return array_merge( $plugins, $visible );
|
||||
},
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store has installed marketing extensions.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_installed_extensions() {
|
||||
$plugins = self::get_plugins();
|
||||
$remaining = array();
|
||||
$installed = array();
|
||||
|
||||
foreach ( $plugins as $plugin ) {
|
||||
if ( ! $plugin->is_installed ) {
|
||||
$remaining[] = $plugin;
|
||||
} else {
|
||||
$installed[] = $plugin;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the task has been actioned and a marketing extension has been installed.
|
||||
if ( count( $installed ) > 0 && Task::is_task_actioned( 'marketing' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
use Automattic\WooCommerce\Internal\Admin\WcPayWelcomePage;
|
||||
|
||||
/**
|
||||
* Payments Task
|
||||
*/
|
||||
class Payments extends Task {
|
||||
|
||||
/**
|
||||
* Used to cache is_complete() method result.
|
||||
*
|
||||
* @var null
|
||||
*/
|
||||
private $is_complete_result = null;
|
||||
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'payments';
|
||||
}
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return __( 'Get paid', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
return __(
|
||||
'Choose payment providers and enable payment methods at checkout.',
|
||||
'woocommerce'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_time() {
|
||||
return __( '2 minutes', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Task completion.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
if ( $this->is_complete_result === null ) {
|
||||
$this->is_complete_result = self::has_gateways();
|
||||
}
|
||||
|
||||
return $this->is_complete_result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Task visibility.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view() {
|
||||
$woocommerce_payments = $this->task_list->get_task( 'woocommerce-payments' );
|
||||
return Features::is_enabled( 'payment-gateway-suggestions' ) && ! $woocommerce_payments->can_view();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 && 'woocommerce_payments' !== $gateway->id;
|
||||
}
|
||||
);
|
||||
|
||||
return ! empty( $enabled_gateways );
|
||||
}
|
||||
|
||||
/**
|
||||
* Action URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_url() {
|
||||
// Check if the WooPayments plugin is active and the store is supported.
|
||||
if ( WooCommercePayments::is_supported() && WooCommercePayments::is_wcpay_active() ) {
|
||||
// If WooPayments is connected, point to the WooPayments overview page.
|
||||
if ( WooCommercePayments::is_connected() ) {
|
||||
return add_query_arg( 'from', 'WCADMIN_PAYMENT_TASK', admin_url( 'admin.php?page=wc-admin&path=/payments/overview' ) );
|
||||
}
|
||||
|
||||
// Point to the WooPayments Connect page.
|
||||
return add_query_arg( 'from', 'WCADMIN_PAYMENT_TASK', admin_url( 'admin.php?page=wc-admin&path=/payments/connect' ) );
|
||||
}
|
||||
|
||||
// Check if there is an active WooPayments incentive via the welcome page.
|
||||
if ( WcPayWelcomePage::instance()->must_be_visible() ) {
|
||||
// Point to the WooPayments welcome page.
|
||||
return add_query_arg( 'from', 'WCADMIN_PAYMENT_TASK', admin_url( 'admin.php?page=wc-admin&path=/wc-pay-welcome-page' ) );
|
||||
}
|
||||
|
||||
return admin_url( 'admin.php?page=wc-admin&task=payments' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
use Automattic\WooCommerce\Internal\Admin\WCAdminAssets;
|
||||
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
|
||||
|
||||
/**
|
||||
* Products Task
|
||||
*/
|
||||
class Products extends Task {
|
||||
const PRODUCT_COUNT_TRANSIENT_NAME = 'woocommerce_product_task_product_count_transient';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param TaskList $task_list Parent task list.
|
||||
*/
|
||||
public function __construct( $task_list ) {
|
||||
parent::__construct( $task_list );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'possibly_add_manual_return_notice_script' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'possibly_add_import_return_notice_script' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'possibly_add_load_sample_return_notice_script' ) );
|
||||
|
||||
add_action( 'woocommerce_update_product', array( $this, 'delete_product_count_cache' ) );
|
||||
add_action( 'woocommerce_new_product', array( $this, 'delete_product_count_cache' ) );
|
||||
add_action( 'wp_trash_post', array( $this, 'delete_product_count_cache' ) );
|
||||
add_action( 'untrashed_post', array( $this, 'delete_product_count_cache' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'products';
|
||||
}
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
$onboarding_profile = get_option( OnboardingProfile::DATA_OPTION, array() );
|
||||
|
||||
if ( isset( $onboarding_profile['business_choice'] ) && 'im_already_selling' === $onboarding_profile['business_choice'] ) {
|
||||
return __( 'Import your products', 'woocommerce' );
|
||||
}
|
||||
|
||||
return __( 'Add your products', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
return __(
|
||||
'Start by adding the first product to your store. You can add your products manually, via CSV, or import them from another service.',
|
||||
'woocommerce'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_time() {
|
||||
return __( '1 minute per product', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Task completion.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
return self::has_products();
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_additional_data() {
|
||||
return array(
|
||||
'has_products' => self::has_products(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a return to task list notice when completing the manual product task.
|
||||
*
|
||||
* @param string $hook Page hook.
|
||||
*/
|
||||
public function possibly_add_manual_return_notice_script( $hook ) {
|
||||
global $post;
|
||||
if ( $hook !== 'post.php' || $post->post_type !== 'product' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->is_active() || ! $this->is_complete() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
WCAdminAssets::register_script( 'wp-admin-scripts', 'onboarding-product-notice', true );
|
||||
|
||||
// Clear the active task transient to only show notice once per active session.
|
||||
delete_transient( self::ACTIVE_TASK_TRANSIENT );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a return to task list notice when completing the import product task.
|
||||
*
|
||||
* @param string $hook Page hook.
|
||||
*/
|
||||
public function possibly_add_import_return_notice_script( $hook ) {
|
||||
$step = isset( $_GET['step'] ) ? $_GET['step'] : ''; // phpcs:ignore csrf ok, sanitization ok.
|
||||
|
||||
if ( $hook !== 'product_page_product_importer' || $step !== 'done' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->is_active() || $this->is_complete() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
WCAdminAssets::register_script( 'wp-admin-scripts', 'onboarding-product-import-notice', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a return to task list notice when completing the loading sample products action.
|
||||
*
|
||||
* @param string $hook Page hook.
|
||||
*/
|
||||
public function possibly_add_load_sample_return_notice_script( $hook ) {
|
||||
if ( $hook !== 'edit.php' || get_query_var( 'post_type' ) !== 'product' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$referer = wp_get_referer();
|
||||
if ( ! $referer || strpos( $referer, wc_admin_url() ) !== 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $_GET[ Task::ACTIVE_TASK_TRANSIENT ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$task_id = sanitize_title_with_dashes( wp_unslash( $_GET[ Task::ACTIVE_TASK_TRANSIENT ] ) );
|
||||
if ( $task_id !== $this->get_id() || ! $this->is_complete() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
WCAdminAssets::register_script( 'wp-admin-scripts', 'onboarding-load-sample-products-notice', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the product count transient used in has_products() method to refresh the cache.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function delete_product_count_cache() {
|
||||
delete_transient( self::PRODUCT_COUNT_TRANSIENT_NAME );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store has any user created published products.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_products() {
|
||||
$product_counts = get_transient( self::PRODUCT_COUNT_TRANSIENT_NAME );
|
||||
if ( false !== $product_counts && is_numeric( $product_counts ) ) {
|
||||
return (int) $product_counts > 0;
|
||||
}
|
||||
|
||||
$product_counts = self::count_user_products();
|
||||
set_transient( self::PRODUCT_COUNT_TRANSIENT_NAME, $product_counts );
|
||||
return $product_counts > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of user created products.
|
||||
* Generated products have the _headstart_post meta key.
|
||||
*
|
||||
* @return int The number of user created products.
|
||||
*/
|
||||
private static function count_user_products() {
|
||||
$args = array(
|
||||
'post_type' => 'product',
|
||||
'post_status' => 'publish',
|
||||
'fields' => 'ids',
|
||||
'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
|
||||
'relation' => 'OR',
|
||||
array(
|
||||
'key' => '_headstart_post',
|
||||
'compare' => 'NOT EXISTS',
|
||||
),
|
||||
array(
|
||||
'key' => '_edit_last',
|
||||
'compare' => 'EXISTS',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$products_query = new \WP_Query( $args );
|
||||
|
||||
return $products_query->found_posts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProducts;
|
||||
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingThemes;
|
||||
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
|
||||
/**
|
||||
* Purchase Task
|
||||
*/
|
||||
class Purchase extends Task {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param TaskList $task_list Parent task list.
|
||||
*/
|
||||
public function __construct( $task_list ) {
|
||||
parent::__construct( $task_list );
|
||||
add_action( 'update_option_woocommerce_onboarding_profile', array( $this, 'clear_dismissal' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear dismissal on onboarding product type changes.
|
||||
*
|
||||
* @param array $old_value Old value.
|
||||
* @param array $new_value New value.
|
||||
*/
|
||||
public function clear_dismissal( $old_value, $new_value ) {
|
||||
$product_types = isset( $new_value['product_types'] ) ? (array) $new_value['product_types'] : array();
|
||||
$previous_product_types = isset( $old_value['product_types'] ) ? (array) $old_value['product_types'] : array();
|
||||
|
||||
if ( empty( array_diff( $product_types, $previous_product_types ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->undo_dismiss();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the task arguments.
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'purchase';
|
||||
}
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
$products = $this->get_paid_products_and_themes();
|
||||
$first_product = count( $products['purchaseable'] ) >= 1 ? $products['purchaseable'][0] : false;
|
||||
|
||||
if ( ! $first_product ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$product_label = isset( $first_product['label'] ) ? $first_product['label'] : $first_product['title'];
|
||||
$additional_count = count( $products['purchaseable'] ) - 1;
|
||||
|
||||
if ( $this->get_parent_option( 'use_completed_title' ) && $this->is_complete() ) {
|
||||
return count( $products['purchaseable'] ) === 1
|
||||
? sprintf(
|
||||
/* translators: %1$s: a purchased product name */
|
||||
__(
|
||||
'You added %1$s',
|
||||
'woocommerce'
|
||||
),
|
||||
$product_label
|
||||
)
|
||||
: sprintf(
|
||||
/* translators: %1$s: a purchased product name, %2$d the number of other products purchased */
|
||||
_n(
|
||||
'You added %1$s and %2$d other product',
|
||||
'You added %1$s and %2$d other products',
|
||||
$additional_count,
|
||||
'woocommerce'
|
||||
),
|
||||
$product_label,
|
||||
$additional_count
|
||||
);
|
||||
}
|
||||
|
||||
return count( $products['purchaseable'] ) === 1
|
||||
? sprintf(
|
||||
/* translators: %1$s: a purchaseable product name */
|
||||
__(
|
||||
'Add %s to my store',
|
||||
'woocommerce'
|
||||
),
|
||||
$product_label
|
||||
)
|
||||
: sprintf(
|
||||
/* translators: %1$s: a purchaseable product name, %2$d the number of other products to purchase */
|
||||
_n(
|
||||
'Add %1$s and %2$d more product to my store',
|
||||
'Add %1$s and %2$d more products to my store',
|
||||
$additional_count,
|
||||
'woocommerce'
|
||||
),
|
||||
$product_label,
|
||||
$additional_count
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
$products = $this->get_paid_products_and_themes();
|
||||
|
||||
if ( count( $products['remaining'] ) === 1 ) {
|
||||
return isset( $products['purchaseable'][0]['description'] ) ? $products['purchaseable'][0]['description'] : $products['purchaseable'][0]['excerpt'];
|
||||
}
|
||||
return 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.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_label() {
|
||||
return __( 'Purchase & install now', 'woocommerce' );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_time() {
|
||||
return __( '2 minutes', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Task completion.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
$products = $this->get_paid_products_and_themes();
|
||||
return count( $products['remaining'] ) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismissable.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_dismissable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Task visibility.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view() {
|
||||
$products = $this->get_paid_products_and_themes();
|
||||
return count( $products['purchaseable'] ) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get purchaseable and remaining products.
|
||||
*
|
||||
* @return array purchaseable and remaining products and themes.
|
||||
*/
|
||||
public static function get_paid_products_and_themes() {
|
||||
$relevant_products = OnboardingProducts::get_relevant_products();
|
||||
|
||||
$profiler_data = get_option( OnboardingProfile::DATA_OPTION, array() );
|
||||
$theme = isset( $profiler_data['theme'] ) ? $profiler_data['theme'] : null;
|
||||
$paid_theme = $theme ? OnboardingThemes::get_paid_theme_by_slug( $theme ) : null;
|
||||
if ( $paid_theme ) {
|
||||
|
||||
$relevant_products['purchaseable'][] = $paid_theme;
|
||||
|
||||
if ( isset( $paid_theme['is_installed'] ) && false === $paid_theme['is_installed'] ) {
|
||||
$relevant_products['remaining'][] = $paid_theme['title'];
|
||||
}
|
||||
}
|
||||
return $relevant_products;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
|
||||
/**
|
||||
* Review Shipping Options Task
|
||||
*/
|
||||
class ReviewShippingOptions extends Task {
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'review-shipping';
|
||||
}
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return __( 'Review shipping options', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_time() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Task completion.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
return get_option( 'woocommerce_admin_reviewed_default_shipping_zones' ) === 'yes';
|
||||
}
|
||||
|
||||
/**
|
||||
* Task visibility.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view() {
|
||||
return get_option( 'woocommerce_admin_created_default_shipping_zones' ) === 'yes';
|
||||
}
|
||||
|
||||
/**
|
||||
* Action URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_url() {
|
||||
return admin_url( 'admin.php?page=wc-settings&tab=shipping' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
use WC_Data_Store;
|
||||
|
||||
/**
|
||||
* Shipping Task
|
||||
*/
|
||||
class Shipping extends Task {
|
||||
|
||||
const ZONE_COUNT_TRANSIENT_NAME = 'woocommerce_shipping_task_zone_count_transient';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param TaskList $task_list Parent task list.
|
||||
*/
|
||||
public function __construct( $task_list = null ) {
|
||||
parent::__construct( $task_list );
|
||||
// wp_ajax_woocommerce_shipping_zone_methods_save_changes
|
||||
// and wp_ajax_woocommerce_shipping_zones_save_changes get fired
|
||||
// when a new zone is added or an existing one has been changed.
|
||||
add_action( 'wp_ajax_woocommerce_shipping_zones_save_changes', array( __CLASS__, 'delete_zone_count_transient' ), 9 );
|
||||
add_action( 'wp_ajax_woocommerce_shipping_zone_methods_save_changes', array( __CLASS__, 'delete_zone_count_transient' ), 9 );
|
||||
add_action( 'woocommerce_shipping_zone_method_added', array( __CLASS__, 'delete_zone_count_transient' ), 9 );
|
||||
add_action( 'woocommerce_after_shipping_zone_object_save', array( __CLASS__, 'delete_zone_count_transient' ), 9 );
|
||||
}
|
||||
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'shipping';
|
||||
}
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return __( 'Select your shipping options', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
return __(
|
||||
"Set your store location and where you'll ship to.",
|
||||
'woocommerce'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_time() {
|
||||
return __( '1 minute', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Task completion.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
return self::has_shipping_zones();
|
||||
}
|
||||
|
||||
/**
|
||||
* Task visibility.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view() {
|
||||
if ( Features::is_enabled( 'shipping-smart-defaults' ) ) {
|
||||
if ( 'yes' === get_option( 'woocommerce_admin_created_default_shipping_zones' ) ) {
|
||||
// If the user has already created a default shipping zone, we don't need to show the task.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not display the task when:
|
||||
* - The store sells digital products only
|
||||
* Display the task when:
|
||||
* - We don't know where the store's located
|
||||
* - The store is located in the UK, Australia or Canada
|
||||
*/
|
||||
|
||||
if ( self::is_selling_digital_type_only() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$default_store_country = wc_format_country_state_string( get_option( 'woocommerce_default_country', '' ) )['country'];
|
||||
|
||||
// Check if a store address is set so that we don't default to WooCommerce's default country US.
|
||||
// Similar logic: https://github.com/woocommerce/woocommerce/blob/059d542394b48468587f252dcb6941c6425cd8d3/plugins/woocommerce-admin/client/profile-wizard/steps/store-details/index.js#L511-L516.
|
||||
$store_country = '';
|
||||
if ( ! empty( get_option( 'woocommerce_store_address', '' ) ) || 'US' !== $default_store_country ) {
|
||||
$store_country = $default_store_country;
|
||||
}
|
||||
|
||||
// Unknown country.
|
||||
if ( empty( $store_country ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return in_array( $store_country, array( 'CA', 'AU', 'NZ', 'SG', 'HK', 'GB', 'ES', 'IT', 'DE', 'FR', 'CL', 'AR', 'PE', 'BR', 'UY', 'GT', 'NL', 'AT', 'BE' ), true );
|
||||
}
|
||||
|
||||
return self::has_physical_products();
|
||||
}
|
||||
|
||||
/**
|
||||
* Action URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_url() {
|
||||
return self::has_shipping_zones()
|
||||
? admin_url( 'admin.php?page=wc-settings&tab=shipping' )
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store has any shipping zones.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_shipping_zones() {
|
||||
$zone_count = get_transient( self::ZONE_COUNT_TRANSIENT_NAME );
|
||||
if ( false !== $zone_count ) {
|
||||
return (int) $zone_count > 0;
|
||||
}
|
||||
|
||||
$zone_count = count( WC_Data_Store::load( 'shipping-zone' )->get_zones() );
|
||||
set_transient( self::ZONE_COUNT_TRANSIENT_NAME, $zone_count );
|
||||
|
||||
return $zone_count > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store has physical products.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_physical_products() {
|
||||
$profiler_data = get_option( OnboardingProfile::DATA_OPTION, array() );
|
||||
$product_types = isset( $profiler_data['product_types'] ) ? $profiler_data['product_types'] : array();
|
||||
|
||||
return in_array( 'physical', $product_types, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the zone count transient used in has_shipping_zones() method
|
||||
* to refresh the cache.
|
||||
*/
|
||||
public static function delete_zone_count_transient() {
|
||||
delete_transient( self::ZONE_COUNT_TRANSIENT_NAME );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store sells digital products only.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function is_selling_digital_type_only() {
|
||||
$profiler_data = get_option( OnboardingProfile::DATA_OPTION, array() );
|
||||
$product_types = isset( $profiler_data['product_types'] ) ? $profiler_data['product_types'] : array();
|
||||
|
||||
return array( 'downloads' ) === $product_types;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
|
||||
/**
|
||||
* Connect store to WooCommerce.com Task
|
||||
*/
|
||||
class StoreConnect extends Task {
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'connect-store';
|
||||
}
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return __( 'Manage your WooCommerce.com Marketplace subscriptions', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_time() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Task completion.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
return \WC_Helper::is_site_connected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Always dismissable.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_dismissable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_url() {
|
||||
return admin_url( 'admin.php?page=wc-admin&tab=my-subscriptions&path=/extensions' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Onboarding;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
|
||||
/**
|
||||
* Store Details Task
|
||||
*/
|
||||
class StoreCreation extends Task {
|
||||
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'store_creation';
|
||||
}
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
/* translators: Store name */
|
||||
return sprintf( __( 'You created %s', 'woocommerce' ), get_bloginfo( 'name' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_time() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_url() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Task completion.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if task is disabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_disabled() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
|
||||
/**
|
||||
* Store Details Task
|
||||
*/
|
||||
class StoreDetails extends Task {
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'store_details';
|
||||
}
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
if ( true === $this->get_parent_option( 'use_completed_title' ) ) {
|
||||
if ( $this->is_complete() ) {
|
||||
return __( 'You added store details', 'woocommerce' );
|
||||
}
|
||||
return __( 'Add store details', 'woocommerce' );
|
||||
}
|
||||
return __( 'Store details', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
return __(
|
||||
'Your store address is required to set the origin country for shipping, currencies, and payment options.',
|
||||
'woocommerce'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_time() {
|
||||
return __( '4 minutes', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_url() {
|
||||
return ! $this->is_complete() ? admin_url( 'admin.php?page=wc-settings&tab=general&tutorial=true' ) : admin_url( 'admin.php?page=wc-settings&tab=general' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Task completion.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
$country = WC()->countries->get_base_country();
|
||||
$country_locale = WC()->countries->get_country_locale();
|
||||
$locale = $country_locale[ $country ] ?? array();
|
||||
|
||||
$hide_postcode = $locale['postcode']['hidden'] ?? false;
|
||||
// If postcode is hidden, just check that the store address and city are set.
|
||||
if ( $hide_postcode ) {
|
||||
return get_option( 'woocommerce_store_address', '' ) !== '' && get_option( 'woocommerce_store_city', '' ) !== '';
|
||||
}
|
||||
|
||||
// Mark as completed if the store address, city and postcode are set. We don't need to check the country because it's set by default.
|
||||
return get_option( 'woocommerce_store_address', '' ) !== '' && get_option( 'woocommerce_store_city', '' ) !== '' &&
|
||||
get_option( 'woocommerce_store_postcode', '' ) !== '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Reports\Taxes\Stats\DataStore as TaxDataStore;
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
use Automattic\WooCommerce\Admin\PluginsHelper;
|
||||
use Automattic\WooCommerce\Internal\Admin\WCAdminAssets;
|
||||
|
||||
/**
|
||||
* Tax Task
|
||||
*/
|
||||
class Tax extends Task {
|
||||
|
||||
/**
|
||||
* Used to cache is_complete() method result.
|
||||
* @var null
|
||||
*/
|
||||
private $is_complete_result = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param TaskList $task_list Parent task list.
|
||||
*/
|
||||
public function __construct( $task_list ) {
|
||||
parent::__construct( $task_list );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'possibly_add_return_notice_script' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a return to task list notice when completing the task.
|
||||
*/
|
||||
public function possibly_add_return_notice_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 ( $page !== 'wc-settings' || $tab !== 'tax' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->is_active() || $this->is_complete() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
WCAdminAssets::register_script( 'wp-admin-scripts', 'onboarding-tax-notice', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'tax';
|
||||
}
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return __( 'Collect sales tax', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
return self::can_use_automated_taxes()
|
||||
? __(
|
||||
'Good news! WooCommerce Tax can automate your sales tax calculations for you.',
|
||||
'woocommerce'
|
||||
)
|
||||
: __(
|
||||
'Set your store location and configure tax rate settings.',
|
||||
'woocommerce'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_time() {
|
||||
return __( '1 minute', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Action label.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_label() {
|
||||
return self::can_use_automated_taxes()
|
||||
? __( 'Yes please', 'woocommerce' )
|
||||
: __( "Let's go", 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Task completion.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
if ( $this->is_complete_result === null ) {
|
||||
$wc_connect_taxes_enabled = get_option( 'wc_connect_taxes_enabled' );
|
||||
$is_wc_connect_taxes_enabled = ( $wc_connect_taxes_enabled === 'yes' ) || ( $wc_connect_taxes_enabled === true ); // seems that in some places boolean is used, and other places 'yes' | 'no' is used
|
||||
|
||||
$this->is_complete_result = $is_wc_connect_taxes_enabled ||
|
||||
count( TaxDataStore::get_taxes( array() ) ) > 0 ||
|
||||
get_option( 'woocommerce_no_sales_tax' ) !== false;
|
||||
}
|
||||
|
||||
return $this->is_complete_result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Addtional data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_additional_data() {
|
||||
return array(
|
||||
'avalara_activated' => PluginsHelper::is_plugin_active( 'woocommerce-avatax' ),
|
||||
'tax_jar_activated' => class_exists( 'WC_Taxjar' ),
|
||||
'woocommerce_tax_countries' => self::get_automated_support_countries(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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_support_countries(), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of countries that support automated tax.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_automated_support_countries() {
|
||||
// https://developers.taxjar.com/api/reference/#countries .
|
||||
$tax_supported_countries = array_merge(
|
||||
array( 'US', 'CA', 'AU', 'GB' ),
|
||||
WC()->countries->get_european_union_countries()
|
||||
);
|
||||
|
||||
return $tax_supported_countries;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
|
||||
/**
|
||||
* Tour In-App Marketplace task
|
||||
*/
|
||||
class TourInAppMarketplace extends Task {
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'tour-in-app-marketplace';
|
||||
}
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return __(
|
||||
'Discover ways of extending your store with a tour of the Woo Marketplace',
|
||||
'woocommerce'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_time() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Task completion.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
return get_option( 'woocommerce_admin_dismissed_in_app_marketplace_tour' ) === 'yes';
|
||||
}
|
||||
|
||||
/**
|
||||
* Action URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_url() {
|
||||
return admin_url( 'admin.php?page=wc-admin&path=%2Fextensions&tutorial=true' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if should record event when task is viewed
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_record_view_event(): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
use Automattic\WooCommerce\Admin\PluginsHelper;
|
||||
use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\Init as Suggestions;
|
||||
use Automattic\WooCommerce\Internal\Admin\WCPayPromotion\Init as WCPayPromotionInit;
|
||||
|
||||
/**
|
||||
* WooCommercePayments Task
|
||||
*/
|
||||
class WooCommercePayments extends Task {
|
||||
/**
|
||||
* Used to cache is_complete() method result.
|
||||
*
|
||||
* @var null
|
||||
*/
|
||||
private $is_complete_result = null;
|
||||
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'woocommerce-payments';
|
||||
}
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return __( 'Get paid with WooPayments', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Badge.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_badge() {
|
||||
/**
|
||||
* Filter WooPayments onboarding task badge.
|
||||
*
|
||||
* @param string $badge Badge content.
|
||||
* @since 8.2.0
|
||||
*/
|
||||
return apply_filters( 'woocommerce_admin_woopayments_onboarding_task_badge', '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
return __(
|
||||
"You're only one step away from getting paid. Verify your business details to start managing transactions with WooPayments.",
|
||||
'woocommerce'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_time() {
|
||||
return __( '2 minutes', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Action label.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_label() {
|
||||
return __( 'Finish setup', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Task completion.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
if ( null === $this->is_complete_result ) {
|
||||
$this->is_complete_result = self::is_connected() && ! self::is_account_partially_onboarded();
|
||||
}
|
||||
|
||||
return $this->is_complete_result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Task visibility.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view() {
|
||||
$payments = $this->task_list->get_task( 'payments' );
|
||||
|
||||
return ! $payments->is_complete() && // Do not re-display the task if the "add payments" task has already been completed.
|
||||
self::is_installed() &&
|
||||
self::is_supported();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the WooPayments plugin was requested during onboarding.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_requested() {
|
||||
$profiler_data = get_option( OnboardingProfile::DATA_OPTION, array() );
|
||||
$product_types = isset( $profiler_data['product_types'] ) ? $profiler_data['product_types'] : array();
|
||||
$business_extensions = isset( $profiler_data['business_extensions'] ) ? $profiler_data['business_extensions'] : array();
|
||||
|
||||
$subscriptions_and_us = in_array( 'subscriptions', $product_types, true ) && 'US' === WC()->countries->get_base_country();
|
||||
return in_array( 'woocommerce-payments', $business_extensions, true ) || $subscriptions_and_us;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the WooPayments 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 the WooPayments plugin is active.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_wcpay_active() {
|
||||
return class_exists( '\WC_Payments' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if WooPayments 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 WooPayments needs setup.
|
||||
* Errored data or payments not enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_account_partially_onboarded() {
|
||||
if ( class_exists( '\WC_Payments' ) ) {
|
||||
$wc_payments_gateway = \WC_Payments::get_gateway();
|
||||
return method_exists( $wc_payments_gateway, 'is_account_partially_onboarded' )
|
||||
? $wc_payments_gateway->is_account_partially_onboarded()
|
||||
: false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store is in a supported country.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_supported() {
|
||||
$suggestions = Suggestions::get_suggestions();
|
||||
$suggestion_plugins = array_merge(
|
||||
...array_filter(
|
||||
array_column( $suggestions, 'plugins' ),
|
||||
function( $plugins ) {
|
||||
return is_array( $plugins );
|
||||
}
|
||||
)
|
||||
);
|
||||
$woocommerce_payments_ids = array_search( 'woocommerce-payments', $suggestion_plugins, true );
|
||||
if ( false !== $woocommerce_payments_ids ) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* Evaluates the spec and returns a status.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the specs and returns the visible suggestions.
|
||||
*
|
||||
* @param array $specs payment suggestion spec array.
|
||||
* @return array The visible suggestions and errors.
|
||||
*/
|
||||
public static function evaluate_specs( $specs ) {
|
||||
$suggestions = array();
|
||||
$errors = array();
|
||||
|
||||
foreach ( $specs as $spec ) {
|
||||
try {
|
||||
$suggestion = self::evaluate( $spec );
|
||||
if ( ! property_exists( $suggestion, 'is_visible' ) || $suggestion->is_visible ) {
|
||||
$suggestions[] = $suggestion;
|
||||
}
|
||||
} catch ( \Throwable $e ) {
|
||||
$errors[] = $e;
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'suggestions' => $suggestions,
|
||||
'errors' => $errors,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/**
|
||||
* Handles running payment gateway suggestion specs
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\DefaultPaymentGateways;
|
||||
use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\PaymentGatewaysController;
|
||||
use Automattic\WooCommerce\Admin\RemoteSpecs\RemoteSpecsEngine;
|
||||
|
||||
/**
|
||||
* Remote Payment Methods engine.
|
||||
* This goes through the specs and gets eligible payment gateways.
|
||||
*/
|
||||
class Init extends RemoteSpecsEngine {
|
||||
/**
|
||||
* Option name for dismissed payment method suggestions.
|
||||
*/
|
||||
const RECOMMENDED_PAYMENT_PLUGINS_DISMISS_OPTION = 'woocommerce_setting_payments_recommendations_hidden';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
PaymentGatewaysController::init();
|
||||
add_action( 'update_option_woocommerce_default_country', array( $this, 'delete_specs_transient' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Go through the specs and run them.
|
||||
*
|
||||
* @param array|null $specs payment suggestion spec array.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_suggestions( array $specs = null ) {
|
||||
$locale = get_user_locale();
|
||||
|
||||
$specs = is_array( $specs ) ? $specs : self::get_specs();
|
||||
$results = EvaluateSuggestion::evaluate_specs( $specs );
|
||||
$specs_to_return = $results['suggestions'];
|
||||
$specs_to_save = null;
|
||||
|
||||
if ( empty( $specs_to_return ) ) {
|
||||
// When suggestions is empty, replace it with defaults and save for 3 hours.
|
||||
$specs_to_save = DefaultPaymentGateways::get_all();
|
||||
$specs_to_return = EvaluateSuggestion::evaluate_specs( $specs_to_save )['suggestions'];
|
||||
} elseif ( count( $results['errors'] ) > 0 ) {
|
||||
// When suggestions is not empty but has errors, save it for 3 hours.
|
||||
$specs_to_save = $specs;
|
||||
}
|
||||
|
||||
if ( count( $results['errors'] ) > 0 ) {
|
||||
self::log_errors( $results['errors'] );
|
||||
}
|
||||
|
||||
if ( $specs_to_save ) {
|
||||
PaymentGatewaySuggestionsDataSourcePoller::get_instance()->set_specs_transient( array( $locale => $specs_to_save ), 3 * HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
return $specs_to_return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the specs transient.
|
||||
*/
|
||||
public static function delete_specs_transient() {
|
||||
PaymentGatewaySuggestionsDataSourcePoller::get_instance()->delete_specs_transient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specs or fetch remotely if they don't exist.
|
||||
*/
|
||||
public static function get_specs() {
|
||||
if ( 'no' === get_option( 'woocommerce_show_marketplace_suggestions', 'yes' ) ) {
|
||||
return apply_filters( 'woocommerce_admin_payment_gateway_suggestion_specs', DefaultPaymentGateways::get_all() );
|
||||
}
|
||||
$specs = PaymentGatewaySuggestionsDataSourcePoller::get_instance()->get_specs_from_data_sources();
|
||||
|
||||
// Fetch specs if they don't yet exist.
|
||||
if ( false === $specs || ! is_array( $specs ) || 0 === count( $specs ) ) {
|
||||
return apply_filters( 'woocommerce_admin_payment_gateway_suggestion_specs', DefaultPaymentGateways::get_all() );
|
||||
}
|
||||
|
||||
return apply_filters( 'woocommerce_admin_payment_gateway_suggestion_specs', $specs );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if suggestions should be shown in the settings screen.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function should_display() {
|
||||
if ( 'yes' === get_option( self::RECOMMENDED_PAYMENT_PLUGINS_DISMISS_OPTION, 'no' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( 'no' === get_option( 'woocommerce_show_marketplace_suggestions', 'yes' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return apply_filters( 'woocommerce_allow_payment_recommendations', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss the suggestions.
|
||||
*/
|
||||
public static function dismiss() {
|
||||
return update_option( self::RECOMMENDED_PAYMENT_PLUGINS_DISMISS_OPTION, 'yes' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions;
|
||||
|
||||
use Automattic\WooCommerce\Admin\DataSourcePoller;
|
||||
|
||||
/**
|
||||
* Specs data source poller class for payment gateway suggestions.
|
||||
*/
|
||||
class PaymentGatewaySuggestionsDataSourcePoller extends DataSourcePoller {
|
||||
|
||||
/**
|
||||
* Data Source Poller ID.
|
||||
*/
|
||||
const ID = 'payment_gateway_suggestions';
|
||||
|
||||
/**
|
||||
* Default data sources array.
|
||||
*/
|
||||
const DATA_SOURCES = array(
|
||||
'https://woocommerce.com/wp-json/wccom/payment-gateway-suggestions/2.0/suggestions.json',
|
||||
);
|
||||
|
||||
/**
|
||||
* Class instance.
|
||||
*
|
||||
* @var Analytics instance
|
||||
*/
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* Get class instance.
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( ! self::$instance ) {
|
||||
// Add country query param to data sources.
|
||||
$base_location = wc_get_base_location();
|
||||
$data_sources = array_map(
|
||||
function( $url ) use ( $base_location ) {
|
||||
return add_query_arg(
|
||||
'country',
|
||||
$base_location['country'],
|
||||
$url
|
||||
);
|
||||
},
|
||||
self::DATA_SOURCES
|
||||
);
|
||||
|
||||
self::$instance = new self( self::ID, $data_sources );
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?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 ) . '&_wpnonce=' . wp_create_nonce( 'connection-return' ) );
|
||||
$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() {
|
||||
if (
|
||||
! isset( $_GET['page'] ) ||
|
||||
'wc-admin' !== $_GET['page'] ||
|
||||
! isset( $_GET['task'] ) ||
|
||||
'payments' !== $_GET['task'] ||
|
||||
! isset( $_GET['connection-return'] ) ||
|
||||
! isset( $_GET['_wpnonce'] ) ||
|
||||
! wp_verify_nonce( wc_clean( wp_unslash( $_GET['_wpnonce'] ) ), 'connection-return' )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$gateway_id = sanitize_text_field( wp_unslash( $_GET['connection-return'] ) );
|
||||
|
||||
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,280 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Product Editor Block Registration
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor;
|
||||
|
||||
use Automattic\WooCommerce\Internal\Admin\WCAdminAssets;
|
||||
|
||||
/**
|
||||
* Product block registration and style registration functionality.
|
||||
*/
|
||||
class BlockRegistry {
|
||||
|
||||
/**
|
||||
* Generic blocks directory.
|
||||
*/
|
||||
const GENERIC_BLOCKS_DIR = 'product-editor/blocks/generic';
|
||||
/**
|
||||
* Product fields blocks directory.
|
||||
*/
|
||||
const PRODUCT_FIELDS_BLOCKS_DIR = 'product-editor/blocks/product-fields';
|
||||
/**
|
||||
* Array of all available generic blocks.
|
||||
*/
|
||||
const GENERIC_BLOCKS = array(
|
||||
'woocommerce/conditional',
|
||||
'woocommerce/product-checkbox-field',
|
||||
'woocommerce/product-collapsible',
|
||||
'woocommerce/product-radio-field',
|
||||
'woocommerce/product-pricing-field',
|
||||
'woocommerce/product-section',
|
||||
'woocommerce/product-section-description',
|
||||
'woocommerce/product-subsection',
|
||||
'woocommerce/product-subsection-description',
|
||||
'woocommerce/product-details-section-description',
|
||||
'woocommerce/product-tab',
|
||||
'woocommerce/product-toggle-field',
|
||||
'woocommerce/product-taxonomy-field',
|
||||
'woocommerce/product-text-field',
|
||||
'woocommerce/product-text-area-field',
|
||||
'woocommerce/product-number-field',
|
||||
'woocommerce/product-linked-list-field',
|
||||
'woocommerce/product-select-field',
|
||||
);
|
||||
|
||||
/**
|
||||
* Array of all available product fields blocks.
|
||||
*/
|
||||
const PRODUCT_FIELDS_BLOCKS = array(
|
||||
'woocommerce/product-catalog-visibility-field',
|
||||
'woocommerce/product-custom-fields',
|
||||
'woocommerce/product-custom-fields-toggle-field',
|
||||
'woocommerce/product-description-field',
|
||||
'woocommerce/product-downloads-field',
|
||||
'woocommerce/product-images-field',
|
||||
'woocommerce/product-inventory-email-field',
|
||||
'woocommerce/product-sku-field',
|
||||
'woocommerce/product-name-field',
|
||||
'woocommerce/product-regular-price-field',
|
||||
'woocommerce/product-sale-price-field',
|
||||
'woocommerce/product-schedule-sale-fields',
|
||||
'woocommerce/product-shipping-class-field',
|
||||
'woocommerce/product-shipping-dimensions-fields',
|
||||
'woocommerce/product-summary-field',
|
||||
'woocommerce/product-tag-field',
|
||||
'woocommerce/product-inventory-quantity-field',
|
||||
'woocommerce/product-variation-items-field',
|
||||
'woocommerce/product-password-field',
|
||||
'woocommerce/product-list-field',
|
||||
'woocommerce/product-has-variations-notice',
|
||||
'woocommerce/product-single-variation-notice',
|
||||
);
|
||||
|
||||
/**
|
||||
* Singleton instance.
|
||||
*
|
||||
* @var BlockRegistry
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Get the singleton instance.
|
||||
*/
|
||||
public static function get_instance(): BlockRegistry {
|
||||
if ( ! self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
protected function __construct() {
|
||||
add_filter( 'block_categories_all', array( $this, 'register_categories' ), 10, 2 );
|
||||
$this->register_product_blocks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a file path for a given block file.
|
||||
*
|
||||
* @param string $path File path.
|
||||
* @param string $dir File directory.
|
||||
*/
|
||||
private function get_file_path( $path, $dir ) {
|
||||
return WC_ABSPATH . WCAdminAssets::get_path( 'js' ) . trailingslashit( $dir ) . $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all the product blocks.
|
||||
*/
|
||||
private function register_product_blocks() {
|
||||
foreach ( self::PRODUCT_FIELDS_BLOCKS as $block_name ) {
|
||||
$this->register_block( $block_name, self::PRODUCT_FIELDS_BLOCKS_DIR );
|
||||
}
|
||||
foreach ( self::GENERIC_BLOCKS as $block_name ) {
|
||||
$this->register_block( $block_name, self::GENERIC_BLOCKS_DIR );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register product related block categories.
|
||||
*
|
||||
* @param array[] $block_categories Array of categories for block types.
|
||||
* @param WP_Block_Editor_Context $editor_context The current block editor context.
|
||||
*/
|
||||
public function register_categories( $block_categories, $editor_context ) {
|
||||
if ( INIT::EDITOR_CONTEXT_NAME === $editor_context->name ) {
|
||||
$block_categories[] = array(
|
||||
'slug' => 'woocommerce',
|
||||
'title' => __( 'WooCommerce', 'woocommerce' ),
|
||||
'icon' => null,
|
||||
);
|
||||
}
|
||||
|
||||
return $block_categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the block name without the "woocommerce/" prefix.
|
||||
*
|
||||
* @param string $block_name Block name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function remove_block_prefix( $block_name ) {
|
||||
if ( 0 === strpos( $block_name, 'woocommerce/' ) ) {
|
||||
return substr_replace( $block_name, '', 0, strlen( 'woocommerce/' ) );
|
||||
}
|
||||
|
||||
return $block_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Augment the attributes of a block by adding attributes that are used by the product editor.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
*/
|
||||
private function augment_attributes( $attributes ) {
|
||||
// Note: If you modify this function, also update the client-side
|
||||
// registerWooBlockType function in @woocommerce/block-templates.
|
||||
return array_merge(
|
||||
$attributes,
|
||||
array(
|
||||
'_templateBlockId' => array(
|
||||
'type' => 'string',
|
||||
'__experimentalRole' => 'content',
|
||||
),
|
||||
'_templateBlockOrder' => array(
|
||||
'type' => 'integer',
|
||||
'__experimentalRole' => 'content',
|
||||
),
|
||||
'_templateBlockHideConditions' => array(
|
||||
'type' => 'array',
|
||||
'__experimentalRole' => 'content',
|
||||
),
|
||||
'_templateBlockDisableConditions' => array(
|
||||
'type' => 'array',
|
||||
'__experimentalRole' => 'content',
|
||||
),
|
||||
'disabled' => isset( $attributes['disabled'] ) ? $attributes['disabled'] : array(
|
||||
'type' => 'boolean',
|
||||
'__experimentalRole' => 'content',
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Augment the uses_context of a block by adding attributes that are used by the product editor.
|
||||
*
|
||||
* @param array $uses_context Block uses_context.
|
||||
*/
|
||||
private function augment_uses_context( $uses_context ) {
|
||||
// Note: If you modify this function, also update the client-side
|
||||
// registerProductEditorBlockType function in @woocommerce/product-editor.
|
||||
return array_merge(
|
||||
isset( $uses_context ) ? $uses_context : array(),
|
||||
array(
|
||||
'postType',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a single block.
|
||||
*
|
||||
* @param string $block_name Block name.
|
||||
* @param string $block_dir Block directory.
|
||||
*
|
||||
* @return WP_Block_Type|false The registered block type on success, or false on failure.
|
||||
*/
|
||||
private function register_block( $block_name, $block_dir ) {
|
||||
$block_name = $this->remove_block_prefix( $block_name );
|
||||
$block_json_file = $this->get_file_path( $block_name . '/block.json', $block_dir );
|
||||
|
||||
return $this->register_block_type_from_metadata( $block_json_file );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a block is registered.
|
||||
*
|
||||
* @param string $block_name Block name.
|
||||
*/
|
||||
public function is_registered( $block_name ): bool {
|
||||
$registry = \WP_Block_Type_Registry::get_instance();
|
||||
|
||||
return $registry->is_registered( $block_name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a block.
|
||||
*
|
||||
* @param string $block_name Block name.
|
||||
*/
|
||||
public function unregister( $block_name ) {
|
||||
$registry = \WP_Block_Type_Registry::get_instance();
|
||||
|
||||
if ( $registry->is_registered( $block_name ) ) {
|
||||
$registry->unregister( $block_name );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a block type from metadata stored in the block.json file.
|
||||
*
|
||||
* @param string $file_or_folder Path to the JSON file with metadata definition for the block or
|
||||
* path to the folder where the `block.json` file is located.
|
||||
*
|
||||
* @return \WP_Block_Type|false The registered block type on success, or false on failure.
|
||||
*/
|
||||
public function register_block_type_from_metadata( $file_or_folder ) {
|
||||
$metadata_file = ( ! str_ends_with( $file_or_folder, 'block.json' ) )
|
||||
? trailingslashit( $file_or_folder ) . 'block.json'
|
||||
: $file_or_folder;
|
||||
|
||||
if ( ! file_exists( $metadata_file ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We are dealing with a local file, so we can use file_get_contents.
|
||||
// phpcs:disable WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
|
||||
$metadata = json_decode( file_get_contents( $metadata_file ), true );
|
||||
if ( ! is_array( $metadata ) || ! $metadata['name'] ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->unregister( $metadata['name'] );
|
||||
|
||||
return register_block_type_from_metadata(
|
||||
$metadata_file,
|
||||
array(
|
||||
'attributes' => $this->augment_attributes( isset( $metadata['attributes'] ) ? $metadata['attributes'] : array() ),
|
||||
'uses_context' => $this->augment_uses_context( isset( $metadata['usesContext'] ) ? $metadata['usesContext'] : array() ),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Product Block Editor
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplate;
|
||||
use Automattic\WooCommerce\Admin\PageController;
|
||||
use Automattic\WooCommerce\LayoutTemplates\LayoutTemplateRegistry;
|
||||
|
||||
use Automattic\WooCommerce\Internal\Features\ProductBlockEditor\ProductTemplates\SimpleProductTemplate;
|
||||
use Automattic\WooCommerce\Internal\Features\ProductBlockEditor\ProductTemplates\ProductVariationTemplate;
|
||||
|
||||
use WP_Block_Editor_Context;
|
||||
|
||||
/**
|
||||
* Loads assets related to the product block editor.
|
||||
*/
|
||||
class Init {
|
||||
/**
|
||||
* The context name used to identify the editor.
|
||||
*/
|
||||
const EDITOR_CONTEXT_NAME = 'woocommerce/edit-product';
|
||||
|
||||
/**
|
||||
* Supported product types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $supported_product_types = array( 'simple' );
|
||||
|
||||
/**
|
||||
* Registered product templates.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $product_templates = array();
|
||||
|
||||
/**
|
||||
* Redirection controller.
|
||||
*
|
||||
* @var RedirectionController
|
||||
*/
|
||||
private $redirection_controller;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
array_push( $this->supported_product_types, 'variable' );
|
||||
array_push( $this->supported_product_types, 'external' );
|
||||
array_push( $this->supported_product_types, 'grouped' );
|
||||
|
||||
$this->redirection_controller = new RedirectionController();
|
||||
|
||||
if ( \Automattic\WooCommerce\Utilities\FeaturesUtil::feature_is_enabled( 'product_block_editor' ) ) {
|
||||
if ( ! Features::is_enabled( 'new-product-management-experience' ) ) {
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_styles' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'dequeue_conflicting_styles' ), 100 );
|
||||
add_action( 'get_edit_post_link', array( $this, 'update_edit_product_link' ), 10, 2 );
|
||||
}
|
||||
add_filter( 'woocommerce_admin_get_user_data_fields', array( $this, 'add_user_data_fields' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
|
||||
add_filter( 'woocommerce_register_post_type_product_variation', array( $this, 'enable_rest_api_for_product_variation' ) );
|
||||
|
||||
add_action( 'current_screen', array( $this, 'set_current_screen_to_block_editor_if_wc_admin' ) );
|
||||
|
||||
add_action( 'rest_api_init', array( $this, 'register_layout_templates' ) );
|
||||
add_action( 'rest_api_init', array( $this, 'register_user_metas' ) );
|
||||
|
||||
add_filter( 'register_block_type_args', array( $this, 'register_metadata_attribute' ) );
|
||||
add_filter( 'woocommerce_get_block_types', array( $this, 'get_block_types' ), 999, 1 );
|
||||
|
||||
// Make sure the block registry is initialized so that core blocks are registered.
|
||||
BlockRegistry::get_instance();
|
||||
|
||||
$tracks = new Tracks();
|
||||
$tracks->init();
|
||||
|
||||
$this->register_product_templates();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue scripts needed for the product form block editor.
|
||||
*/
|
||||
public function enqueue_scripts() {
|
||||
if ( ! PageController::is_admin_or_embed_page() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$editor_settings = $this->get_product_editor_settings();
|
||||
|
||||
$script_handle = 'wc-admin-edit-product';
|
||||
wp_register_script( $script_handle, '', array(), '0.1.0', true );
|
||||
wp_enqueue_script( $script_handle );
|
||||
wp_add_inline_script(
|
||||
$script_handle,
|
||||
'var productBlockEditorSettings = productBlockEditorSettings || ' . wp_json_encode( $editor_settings ) . ';',
|
||||
'before'
|
||||
);
|
||||
wp_add_inline_script(
|
||||
$script_handle,
|
||||
sprintf( 'wp.blocks.setCategories( %s );', wp_json_encode( $editor_settings['blockCategories'] ) ),
|
||||
'before'
|
||||
);
|
||||
wp_tinymce_inline_scripts();
|
||||
wp_enqueue_media();
|
||||
wp_register_style( 'wc-global-presets', false ); // phpcs:ignore
|
||||
wp_add_inline_style( 'wc-global-presets', wp_get_global_stylesheet( array( 'presets' ) ) );
|
||||
wp_enqueue_style( 'wc-global-presets' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue styles needed for the rich text editor.
|
||||
*/
|
||||
public function enqueue_styles() {
|
||||
if ( ! PageController::is_admin_or_embed_page() ) {
|
||||
return;
|
||||
}
|
||||
wp_enqueue_style( 'wp-edit-blocks' );
|
||||
wp_enqueue_style( 'wp-format-library' );
|
||||
wp_enqueue_editor();
|
||||
/**
|
||||
* Enqueue any block editor related assets.
|
||||
*
|
||||
* @since 7.1.0
|
||||
*/
|
||||
do_action( 'enqueue_block_editor_assets' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Dequeue conflicting styles.
|
||||
*/
|
||||
public function dequeue_conflicting_styles() {
|
||||
if ( ! PageController::is_admin_or_embed_page() ) {
|
||||
return;
|
||||
}
|
||||
// Dequeing this to avoid conflicts, until we remove the 'woocommerce-page' class.
|
||||
wp_dequeue_style( 'woocommerce-blocktheme' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the edit product links when the new experience is enabled.
|
||||
*
|
||||
* @param string $link The edit link.
|
||||
* @param int $post_id Post ID.
|
||||
* @return string
|
||||
*/
|
||||
public function update_edit_product_link( $link, $post_id ) {
|
||||
$product = wc_get_product( $post_id );
|
||||
|
||||
if ( ! $product ) {
|
||||
return $link;
|
||||
}
|
||||
|
||||
if ( $product->get_type() === 'simple' ) {
|
||||
return admin_url( 'admin.php?page=wc-admin&path=/product/' . $product->get_id() );
|
||||
}
|
||||
|
||||
return $link;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables variation post type in REST API.
|
||||
*
|
||||
* @param array $args Array of post type arguments.
|
||||
* @return array Array of post type arguments.
|
||||
*/
|
||||
public function enable_rest_api_for_product_variation( $args ) {
|
||||
$args['show_in_rest'] = true;
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds fields so that we can store user preferences for the variations block.
|
||||
*
|
||||
* @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(
|
||||
'variable_product_block_tour_shown',
|
||||
'local_attributes_notice_dismissed_ids',
|
||||
'variable_items_without_price_notice_dismissed',
|
||||
'product_advice_card_dismissed',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current screen to the block editor if a wc-admin page.
|
||||
*/
|
||||
public function set_current_screen_to_block_editor_if_wc_admin() {
|
||||
$screen = get_current_screen();
|
||||
|
||||
// phpcs:ignore Squiz.PHP.CommentedOutCode.Found
|
||||
// (no idea why I need that phpcs:ignore above, but I'm tired trying to re-write this comment to get it to pass)
|
||||
// we can't check the 'path' query param because client-side routing is used within wc-admin,
|
||||
// so this action handler is only called on the initial page load from the server, which might
|
||||
// not be the product edit page (it mostly likely isn't).
|
||||
if ( PageController::is_admin_page() ) {
|
||||
$screen->is_block_editor( true );
|
||||
|
||||
wp_add_inline_script(
|
||||
'wp-blocks',
|
||||
'wp.blocks && wp.blocks.unstable__bootstrapServerSideBlockDefinitions && wp.blocks.unstable__bootstrapServerSideBlockDefinitions(' . wp_json_encode( get_block_editor_server_block_settings() ) . ');'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product editor settings.
|
||||
*/
|
||||
private function get_product_editor_settings() {
|
||||
$editor_settings['productTemplates'] = array_map(
|
||||
function ( $product_template ) {
|
||||
return $product_template->to_json();
|
||||
},
|
||||
$this->product_templates
|
||||
);
|
||||
|
||||
$block_editor_context = new WP_Block_Editor_Context( array( 'name' => self::EDITOR_CONTEXT_NAME ) );
|
||||
|
||||
return get_block_editor_settings( $editor_settings, $block_editor_context );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default product templates.
|
||||
*
|
||||
* @return array The default templates.
|
||||
*/
|
||||
private function get_default_product_templates() {
|
||||
$templates = array();
|
||||
$templates[] = new ProductTemplate(
|
||||
array(
|
||||
'id' => 'standard-product-template',
|
||||
'title' => __( 'Standard product', 'woocommerce' ),
|
||||
'description' => __( 'A single physical or virtual product, e.g. a t-shirt or an eBook.', 'woocommerce' ),
|
||||
'order' => 10,
|
||||
'icon' => 'shipping',
|
||||
'layout_template_id' => 'simple-product',
|
||||
'product_data' => array(
|
||||
'type' => 'simple',
|
||||
),
|
||||
)
|
||||
);
|
||||
$templates[] = new ProductTemplate(
|
||||
array(
|
||||
'id' => 'grouped-product-template',
|
||||
'title' => __( 'Grouped product', 'woocommerce' ),
|
||||
'description' => __( 'A set of products that go well together, e.g. camera kit.', 'woocommerce' ),
|
||||
'order' => 20,
|
||||
'icon' => 'group',
|
||||
'layout_template_id' => 'simple-product',
|
||||
'product_data' => array(
|
||||
'type' => 'grouped',
|
||||
),
|
||||
)
|
||||
);
|
||||
$templates[] = new ProductTemplate(
|
||||
array(
|
||||
'id' => 'affiliate-product-template',
|
||||
'title' => __( 'Affiliate product', 'woocommerce' ),
|
||||
'description' => __( 'A link to a product sold on a different website, e.g. brand collab.', 'woocommerce' ),
|
||||
'order' => 30,
|
||||
'icon' => 'link',
|
||||
'layout_template_id' => 'simple-product',
|
||||
'product_data' => array(
|
||||
'type' => 'external',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
return $templates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create default product template by custom product type if it does not have a
|
||||
* template associated yet.
|
||||
*
|
||||
* @param array $templates The registered product templates.
|
||||
* @return array The new templates.
|
||||
*/
|
||||
private function create_default_product_template_by_custom_product_type( array $templates ) {
|
||||
// Getting the product types registered via the classic editor.
|
||||
$registered_product_types = wc_get_product_types();
|
||||
|
||||
$custom_product_types = array_filter(
|
||||
$registered_product_types,
|
||||
function ( $product_type ) {
|
||||
return ! in_array( $product_type, $this->supported_product_types, true );
|
||||
},
|
||||
ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
|
||||
$templates_with_product_type = array_filter(
|
||||
$templates,
|
||||
function ( $template ) {
|
||||
$product_data = $template->get_product_data();
|
||||
return ! is_null( $product_data ) && array_key_exists( 'type', $product_data );
|
||||
}
|
||||
);
|
||||
|
||||
$custom_product_types_on_templates = array_map(
|
||||
function ( $template ) {
|
||||
$product_data = $template->get_product_data();
|
||||
return $product_data['type'];
|
||||
},
|
||||
$templates_with_product_type
|
||||
);
|
||||
|
||||
foreach ( $custom_product_types as $product_type => $title ) {
|
||||
if ( in_array( $product_type, $custom_product_types_on_templates, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$templates[] = new ProductTemplate(
|
||||
array(
|
||||
'id' => $product_type . '-product-template',
|
||||
'title' => $title,
|
||||
'product_data' => array(
|
||||
'type' => $product_type,
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $templates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register layout templates.
|
||||
*/
|
||||
public function register_layout_templates() {
|
||||
$layout_template_registry = wc_get_container()->get( LayoutTemplateRegistry::class );
|
||||
|
||||
if ( ! $layout_template_registry->is_registered( 'simple-product' ) ) {
|
||||
$layout_template_registry->register(
|
||||
'simple-product',
|
||||
'product-form',
|
||||
SimpleProductTemplate::class
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! $layout_template_registry->is_registered( 'product-variation' ) ) {
|
||||
$layout_template_registry->register(
|
||||
'product-variation',
|
||||
'product-form',
|
||||
ProductVariationTemplate::class
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register product templates.
|
||||
*/
|
||||
public function register_product_templates() {
|
||||
/**
|
||||
* Allows for new product template registration.
|
||||
*
|
||||
* @since 8.5.0
|
||||
*/
|
||||
$this->product_templates = apply_filters( 'woocommerce_product_editor_product_templates', $this->get_default_product_templates() );
|
||||
$this->product_templates = $this->create_default_product_template_by_custom_product_type( $this->product_templates );
|
||||
|
||||
usort(
|
||||
$this->product_templates,
|
||||
function ( $a, $b ) {
|
||||
return $a->get_order() - $b->get_order();
|
||||
}
|
||||
);
|
||||
|
||||
$this->redirection_controller->set_product_templates( $this->product_templates );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register user metas.
|
||||
*/
|
||||
public function register_user_metas() {
|
||||
register_rest_field(
|
||||
'user',
|
||||
'metaboxhidden_product',
|
||||
array(
|
||||
'get_callback' => function ( $object, $attr ) {
|
||||
$hidden = get_user_meta( $object['id'], $attr, true );
|
||||
|
||||
if ( is_array( $hidden ) ) {
|
||||
// Ensures to always return a string array.
|
||||
return array_values( $hidden );
|
||||
}
|
||||
|
||||
return array( 'postcustom' );
|
||||
},
|
||||
'update_callback' => function ( $value, $object, $attr ) {
|
||||
// Update the field/meta value.
|
||||
update_user_meta( $object->ID, $attr, $value );
|
||||
},
|
||||
'schema' => array(
|
||||
'type' => 'array',
|
||||
'description' => __( 'The metaboxhidden_product meta from the user metas.', 'woocommerce' ),
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => 'wp_parse_list',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the metadata block attribute for all block types.
|
||||
* This is a fallback/temporary solution until
|
||||
* the Gutenberg core version registers the metadata attribute.
|
||||
*
|
||||
* @see https://github.com/WordPress/gutenberg/blob/6aaa3686ae67adc1a6a6b08096d3312859733e1b/lib/compat/wordpress-6.5/blocks.php#L27-L47
|
||||
* To do: Remove this method once the Gutenberg core version registers the metadata attribute.
|
||||
*
|
||||
* @param array $args Array of arguments for registering a block type.
|
||||
* @return array $args
|
||||
*/
|
||||
public function register_metadata_attribute( $args ) {
|
||||
// Setup attributes if needed.
|
||||
if ( ! isset( $args['attributes'] ) || ! is_array( $args['attributes'] ) ) {
|
||||
$args['attributes'] = array();
|
||||
}
|
||||
|
||||
// Add metadata attribute if it doesn't exist.
|
||||
if ( ! array_key_exists( 'metadata', $args['attributes'] ) ) {
|
||||
$args['attributes']['metadata'] = array(
|
||||
'type' => 'object',
|
||||
);
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters woocommerce block types.
|
||||
*
|
||||
* @param string[] $block_types Array of woocommerce block types.
|
||||
* @return array
|
||||
*/
|
||||
public function get_block_types( $block_types ) {
|
||||
if ( PageController::is_admin_page() ) {
|
||||
// Ignore all woocommerce blocks.
|
||||
return array();
|
||||
}
|
||||
|
||||
return $block_types;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Product Block Editor
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor;
|
||||
|
||||
/**
|
||||
* The Product Template that represents the relation between the Product and
|
||||
* the LayoutTemplate (ProductFormTemplateInterface)
|
||||
*
|
||||
* @see ProductFormTemplateInterface
|
||||
*/
|
||||
class ProductTemplate {
|
||||
/**
|
||||
* The template id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* The template title.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $title;
|
||||
|
||||
/**
|
||||
* The product data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $product_data;
|
||||
|
||||
/**
|
||||
* The template order.
|
||||
*
|
||||
* @var Integer
|
||||
*/
|
||||
private $order = 999;
|
||||
|
||||
/**
|
||||
* The layout template id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $layout_template_id = null;
|
||||
|
||||
/**
|
||||
* The template description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $description = null;
|
||||
|
||||
/**
|
||||
* The template icon.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $icon = null;
|
||||
|
||||
/**
|
||||
* If the template is directly selectable through the UI.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private $is_selectable_by_user = true;
|
||||
|
||||
/**
|
||||
* ProductTemplate constructor
|
||||
*
|
||||
* @param array $data The data.
|
||||
*/
|
||||
public function __construct( array $data ) {
|
||||
$this->id = $data['id'];
|
||||
$this->title = $data['title'];
|
||||
$this->product_data = $data['product_data'];
|
||||
|
||||
if ( isset( $data['order'] ) ) {
|
||||
$this->order = $data['order'];
|
||||
}
|
||||
|
||||
if ( isset( $data['layout_template_id'] ) ) {
|
||||
$this->layout_template_id = $data['layout_template_id'];
|
||||
}
|
||||
|
||||
if ( isset( $data['description'] ) ) {
|
||||
$this->description = $data['description'];
|
||||
}
|
||||
|
||||
if ( isset( $data['icon'] ) ) {
|
||||
$this->icon = $data['icon'];
|
||||
}
|
||||
|
||||
if ( isset( $data['is_selectable_by_user'] ) ) {
|
||||
$this->is_selectable_by_user = $data['is_selectable_by_user'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template ID.
|
||||
*
|
||||
* @return string The ID.
|
||||
*/
|
||||
public function get_id() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template title.
|
||||
*
|
||||
* @return string The title.
|
||||
*/
|
||||
public function get_title() {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the layout template ID.
|
||||
*
|
||||
* @return string The layout template ID.
|
||||
*/
|
||||
public function get_layout_template_id() {
|
||||
return $this->layout_template_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the layout template ID.
|
||||
*
|
||||
* @param string $layout_template_id The layout template ID.
|
||||
*/
|
||||
public function set_layout_template_id( string $layout_template_id ) {
|
||||
$this->layout_template_id = $layout_template_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product data.
|
||||
*
|
||||
* @return array The product data.
|
||||
*/
|
||||
public function get_product_data() {
|
||||
return $this->product_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template description.
|
||||
*
|
||||
* @return string The description.
|
||||
*/
|
||||
public function get_description() {
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the template description.
|
||||
*
|
||||
* @param string $description The template description.
|
||||
*/
|
||||
public function set_description( string $description ) {
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template icon.
|
||||
*
|
||||
* @return string The icon.
|
||||
*/
|
||||
public function get_icon() {
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the template icon.
|
||||
*
|
||||
* @see https://github.com/WordPress/gutenberg/tree/trunk/packages/icons.
|
||||
*
|
||||
* @param string $icon The icon name from the @wordpress/components or a url for an external image resource.
|
||||
*/
|
||||
public function set_icon( string $icon ) {
|
||||
$this->icon = $icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template order.
|
||||
*
|
||||
* @return int The order.
|
||||
*/
|
||||
public function get_order() {
|
||||
return $this->order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selectable attribute.
|
||||
*
|
||||
* @return boolean Selectable.
|
||||
*/
|
||||
public function get_is_selectable_by_user() {
|
||||
return $this->is_selectable_by_user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the template order.
|
||||
*
|
||||
* @param int $order The template order.
|
||||
*/
|
||||
public function set_order( int $order ) {
|
||||
$this->order = $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product template as JSON like.
|
||||
*
|
||||
* @return array The JSON.
|
||||
*/
|
||||
public function to_json() {
|
||||
return array(
|
||||
'id' => $this->get_id(),
|
||||
'title' => $this->get_title(),
|
||||
'description' => $this->get_description(),
|
||||
'icon' => $this->get_icon(),
|
||||
'order' => $this->get_order(),
|
||||
'layoutTemplateId' => $this->get_layout_template_id(),
|
||||
'productData' => $this->get_product_data(),
|
||||
'isSelectableByUser' => $this->get_is_selectable_by_user(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates;
|
||||
|
||||
use Automattic\WooCommerce\Admin\BlockTemplates\BlockContainerInterface;
|
||||
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
|
||||
|
||||
/**
|
||||
* Interface for group containers, which contain sections and blocks.
|
||||
*/
|
||||
interface GroupInterface extends BlockContainerInterface {
|
||||
|
||||
/**
|
||||
* Adds a new section to the group
|
||||
*
|
||||
* @param array $block_config block config.
|
||||
* @return SectionInterface new block section.
|
||||
*/
|
||||
public function add_section( array $block_config ): SectionInterface;
|
||||
|
||||
/**
|
||||
* Adds a new block to the group.
|
||||
*
|
||||
* @param array $block_config block config.
|
||||
*/
|
||||
public function add_block( array $block_config ): BlockInterface;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates;
|
||||
|
||||
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
|
||||
use Automattic\WooCommerce\Admin\BlockTemplates\BlockTemplateInterface;
|
||||
|
||||
/**
|
||||
* Interface for block containers.
|
||||
*/
|
||||
interface ProductFormTemplateInterface extends BlockTemplateInterface {
|
||||
|
||||
/**
|
||||
* Adds a new group block.
|
||||
*
|
||||
* @param array $block_config block config.
|
||||
* @return GroupInterface new group block.
|
||||
*/
|
||||
public function add_group( array $block_config ): GroupInterface;
|
||||
|
||||
/**
|
||||
* Gets Group block by id.
|
||||
*
|
||||
* @param string $group_id group id.
|
||||
* @return GroupInterface|null
|
||||
*/
|
||||
public function get_group_by_id( string $group_id ): ?GroupInterface;
|
||||
|
||||
/**
|
||||
* Gets Section block by id.
|
||||
*
|
||||
* @param string $section_id section id.
|
||||
* @return SectionInterface|null
|
||||
*/
|
||||
public function get_section_by_id( string $section_id ): ?SectionInterface;
|
||||
|
||||
/**
|
||||
* Gets subsection block by id.
|
||||
*
|
||||
* @param string $subsection_id subsection id.
|
||||
* @return SubsectionInterface|null
|
||||
*/
|
||||
public function get_subsection_by_id( string $subsection_id ): ?SubsectionInterface;
|
||||
|
||||
/**
|
||||
* Gets Block by id.
|
||||
*
|
||||
* @param string $block_id block id.
|
||||
* @return BlockInterface|null
|
||||
*/
|
||||
public function get_block_by_id( string $block_id ): ?BlockInterface;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates;
|
||||
|
||||
use Automattic\WooCommerce\Admin\BlockTemplates\BlockContainerInterface;
|
||||
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
|
||||
|
||||
|
||||
/**
|
||||
* Interface for section containers, which contain sub-sections and blocks.
|
||||
*/
|
||||
interface SectionInterface extends BlockContainerInterface {
|
||||
|
||||
/**
|
||||
* Adds a new sub-section to the section.
|
||||
*
|
||||
* @param array $block_config block config.
|
||||
* @return SubsectionInterface new block sub-section.
|
||||
*/
|
||||
public function add_subsection( array $block_config ): SubsectionInterface;
|
||||
|
||||
/**
|
||||
* Adds a new block to the section.
|
||||
*
|
||||
* @param array $block_config block config.
|
||||
*/
|
||||
public function add_block( array $block_config ): BlockInterface;
|
||||
|
||||
/**
|
||||
* Adds a new sub-section to the section.
|
||||
*
|
||||
* @deprecated 8.6.0
|
||||
*
|
||||
* @param array $block_config The block data.
|
||||
*/
|
||||
public function add_section( array $block_config ): SubsectionInterface;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates;
|
||||
|
||||
use Automattic\WooCommerce\Admin\BlockTemplates\BlockContainerInterface;
|
||||
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
|
||||
|
||||
/**
|
||||
* Interface for subsection containers, which contain sub-sections and blocks.
|
||||
*/
|
||||
interface SubsectionInterface extends BlockContainerInterface {
|
||||
/**
|
||||
* Adds a new block to the sub-section.
|
||||
*
|
||||
* @param array $block_config block config.
|
||||
*/
|
||||
public function add_block( array $block_config ): BlockInterface;
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Product Editor Redirection Controller
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Internal\Admin\WCAdminAssets;
|
||||
|
||||
/**
|
||||
* Handle redirecting to the old or new editor based on features and support.
|
||||
*/
|
||||
class RedirectionController {
|
||||
/**
|
||||
* Registered product templates.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $product_templates = array();
|
||||
|
||||
/**
|
||||
* Set up the hooks used for redirection.
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( \Automattic\WooCommerce\Utilities\FeaturesUtil::feature_is_enabled( 'product_block_editor' ) ) {
|
||||
add_action( 'current_screen', array( $this, 'maybe_redirect_to_new_editor' ), 30, 0 );
|
||||
add_action( 'current_screen', array( $this, 'redirect_non_supported_product_types' ), 30, 0 );
|
||||
} else {
|
||||
add_action( 'current_screen', array( $this, 'maybe_redirect_to_old_editor' ), 30, 0 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current screen is the legacy add product screen.
|
||||
*/
|
||||
protected function is_legacy_add_new_screen(): bool {
|
||||
$screen = get_current_screen();
|
||||
return 'post' === $screen->base && 'product' === $screen->post_type && 'add' === $screen->action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current screen is the legacy edit product screen.
|
||||
*/
|
||||
protected function is_legacy_edit_screen(): bool {
|
||||
$screen = get_current_screen();
|
||||
return 'post' === $screen->base
|
||||
&& 'product' === $screen->post_type
|
||||
&& isset( $_GET['post'] )
|
||||
&& isset( $_GET['action'] )
|
||||
&& 'edit' === $_GET['action'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a product is supported by the new experience.
|
||||
*
|
||||
* @param integer $product_id Product ID.
|
||||
*/
|
||||
protected function is_product_supported( $product_id ): bool {
|
||||
$product = $product_id ? wc_get_product( $product_id ) : null;
|
||||
|
||||
if ( is_null( $product ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$digital_product = $product->is_downloadable() || $product->is_virtual();
|
||||
$product_template_id = $product->get_meta( '_product_template_id' );
|
||||
|
||||
foreach ( $this->product_templates as $product_template ) {
|
||||
if ( is_null( $product_template->get_layout_template_id() ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$product_data = $product_template->get_product_data();
|
||||
$product_data_type = $product_data['type'];
|
||||
// Treat a variable product as a simple product since there is not a product template
|
||||
// for variable products.
|
||||
$product_type = $product->get_type() === 'variable' ? 'simple' : $product->get_type();
|
||||
|
||||
if ( isset( $product_data_type ) && $product_data_type !== $product_type ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( isset( $product_template_id ) && $product_template_id === $product_template->get_id() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( isset( $product_data_type ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a product is supported by the new experience.
|
||||
*
|
||||
* @param array $product_templates The registered product teamplates.
|
||||
*/
|
||||
public function set_product_templates( array $product_templates ): void {
|
||||
$this->product_templates = $product_templates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects from old product form to the new product form if the
|
||||
* feature `product_block_editor` is enabled.
|
||||
*/
|
||||
public function maybe_redirect_to_new_editor(): void {
|
||||
if ( $this->is_legacy_add_new_screen() ) {
|
||||
wp_safe_redirect( admin_url( 'admin.php?page=wc-admin&path=/add-product' ) );
|
||||
exit();
|
||||
}
|
||||
|
||||
if ( $this->is_legacy_edit_screen() ) {
|
||||
$product_id = isset( $_GET['post'] ) ? absint( $_GET['post'] ) : null;
|
||||
if ( ! $this->is_product_supported( $product_id ) ) {
|
||||
return;
|
||||
}
|
||||
wp_safe_redirect( admin_url( 'admin.php?page=wc-admin&path=/product/' . $product_id ) );
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects from new product form to the old product form if the
|
||||
* feature `product_block_editor` is enabled.
|
||||
*/
|
||||
public function maybe_redirect_to_old_editor(): void {
|
||||
$route = $this->get_parsed_route();
|
||||
|
||||
if ( 'add-product' === $route['page'] ) {
|
||||
wp_safe_redirect( admin_url( 'post-new.php?post_type=product' ) );
|
||||
exit();
|
||||
}
|
||||
|
||||
if ( 'product' === $route['page'] ) {
|
||||
wp_safe_redirect( admin_url( 'post.php?post=' . $route['product_id'] . '&action=edit' ) );
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parsed WooCommerce Admin path.
|
||||
*/
|
||||
protected function get_parsed_route(): array {
|
||||
if ( ! \Automattic\WooCommerce\Admin\PageController::is_admin_page() || ! isset( $_GET['path'] ) ) {
|
||||
return array(
|
||||
'page' => null,
|
||||
'product_id' => null,
|
||||
);
|
||||
}
|
||||
|
||||
$path = esc_url_raw( wp_unslash( $_GET['path'] ) );
|
||||
$path_pieces = explode( '/', wp_parse_url( $path, PHP_URL_PATH ) );
|
||||
|
||||
return array(
|
||||
'page' => $path_pieces[1],
|
||||
'product_id' => 'product' === $path_pieces[1] ? absint( $path_pieces[2] ) : null,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Redirect non supported product types to legacy editor.
|
||||
*/
|
||||
public function redirect_non_supported_product_types(): void {
|
||||
$route = $this->get_parsed_route();
|
||||
$product_id = $route['product_id'];
|
||||
|
||||
if ( 'product' === $route['page'] && ! $this->is_product_supported( $product_id ) ) {
|
||||
wp_safe_redirect( admin_url( 'post.php?post=' . $route['product_id'] . '&action=edit' ) );
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Product Block Editor
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor;
|
||||
|
||||
/**
|
||||
* Add tracks for the product block editor.
|
||||
*/
|
||||
class Tracks {
|
||||
|
||||
/**
|
||||
* Initialize the tracks.
|
||||
*/
|
||||
public function init() {
|
||||
add_filter( 'woocommerce_product_source', array( $this, 'add_product_source' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a URL is a product editor page.
|
||||
*
|
||||
* @param string $url Url to check.
|
||||
* @return boolean
|
||||
*/
|
||||
protected function is_product_editor_page( $url ) {
|
||||
$query_string = wp_parse_url( wp_get_referer(), PHP_URL_QUERY );
|
||||
parse_str( $query_string, $query );
|
||||
|
||||
if ( ! isset( $query['page'] ) || 'wc-admin' !== $query['page'] || ! isset( $query['path'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$path_pieces = explode( '/', $query['path'] );
|
||||
$route = $path_pieces[1];
|
||||
|
||||
return 'add-product' === $route || 'product' === $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the product source if we're on the product editor page.
|
||||
*
|
||||
* @param string $source Source of product.
|
||||
* @return string
|
||||
*/
|
||||
public function add_product_source( $source ) {
|
||||
if ( $this->is_product_editor_page( wp_get_referer() ) ) {
|
||||
return 'product-block-editor-v1';
|
||||
}
|
||||
|
||||
return $source;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\ShippingPartnerSuggestions;
|
||||
|
||||
/**
|
||||
* Default Shipping Partners
|
||||
*/
|
||||
class DefaultShippingPartners {
|
||||
|
||||
/**
|
||||
* Get default specs.
|
||||
*
|
||||
* @return array Default specs.
|
||||
*/
|
||||
public static function get_all() {
|
||||
$asset_base_url = WC()->plugin_url() . '/assets/images/shipping_partners/';
|
||||
$column_layout_features = array(
|
||||
array(
|
||||
'icon' => $asset_base_url . 'timer.svg',
|
||||
'title' => __( 'Save time', 'woocommerce' ),
|
||||
'description' => __(
|
||||
'Automatically import order information to quickly print your labels.',
|
||||
'woocommerce'
|
||||
),
|
||||
),
|
||||
array(
|
||||
'icon' => $asset_base_url . 'discount.svg',
|
||||
'title' => __( 'Save money', 'woocommerce' ),
|
||||
'description' => __(
|
||||
'Shop for the best shipping rates, and access pre-negotiated discounted rates.',
|
||||
'woocommerce'
|
||||
),
|
||||
),
|
||||
array(
|
||||
'icon' => $asset_base_url . 'star.svg',
|
||||
'title' => __( 'Wow your shoppers', 'woocommerce' ),
|
||||
'description' => __(
|
||||
'Keep your customers informed with tracking notifications.',
|
||||
'woocommerce'
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$check_icon = $asset_base_url . 'check.svg';
|
||||
|
||||
return array(
|
||||
array(
|
||||
'id' => 'woocommerce-shipstation-integration',
|
||||
'name' => 'ShipStation',
|
||||
'slug' => 'woocommerce-shipstation-integration',
|
||||
'description' => __( 'Powerful yet easy-to-use solution:', 'woocommerce' ),
|
||||
'layout_column' => array(
|
||||
'image' => $asset_base_url . 'shipstation-column.svg',
|
||||
'features' => $column_layout_features,
|
||||
),
|
||||
'layout_row' => array(
|
||||
'image' => $asset_base_url . 'shipstation-row.svg',
|
||||
'features' => array(
|
||||
array(
|
||||
'icon' => $check_icon,
|
||||
'description' => __(
|
||||
'Print labels from Royal Mail, Parcel Force, DPD, and many more',
|
||||
'woocommerce'
|
||||
),
|
||||
),
|
||||
array(
|
||||
'icon' => $check_icon,
|
||||
'description' => __(
|
||||
'Shop for the best rates, in real-time',
|
||||
'woocommerce'
|
||||
),
|
||||
),
|
||||
array(
|
||||
'icon' => $check_icon,
|
||||
'description' => __( 'Connect selling channels easily', 'woocommerce' ),
|
||||
),
|
||||
array(
|
||||
'icon' => $check_icon,
|
||||
'description' => __( 'Advance automated workflows', 'woocommerce' ),
|
||||
),
|
||||
array(
|
||||
'icon' => $check_icon,
|
||||
'description' => __( '30-days free trial', 'woocommerce' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
'learn_more_link' => 'https://wordpress.org/plugins/woocommerce-shipstation-integration/',
|
||||
'is_visible' => array(
|
||||
self::get_rules_for_countries( array( 'AU', 'CA', 'GB' ) ),
|
||||
),
|
||||
'available_layouts' => array( 'row', 'column' ),
|
||||
),
|
||||
array(
|
||||
'id' => 'skydropx-cotizador-y-envios',
|
||||
'name' => 'Skydropx',
|
||||
'slug' => 'skydropx-cotizador-y-envios',
|
||||
'layout_column' => array(
|
||||
'image' => $asset_base_url . 'skydropx-column.svg',
|
||||
'features' => $column_layout_features,
|
||||
),
|
||||
'description' => '',
|
||||
'learn_more_link' => 'https://wordpress.org/plugins/skydropx-cotizador-y-envios/',
|
||||
'is_visible' => array(
|
||||
self::get_rules_for_countries( array() ), // No countries eligible for SkydropX promotion at this time.
|
||||
),
|
||||
'available_layouts' => array( 'column' ),
|
||||
),
|
||||
array(
|
||||
'id' => 'envia',
|
||||
'name' => 'Envia',
|
||||
'slug' => '',
|
||||
'description' => '',
|
||||
'layout_column' => array(
|
||||
'image' => $asset_base_url . 'envia-column.svg',
|
||||
'features' => $column_layout_features,
|
||||
),
|
||||
'learn_more_link' => 'https://woocommerce.com/products/envia-shipping-and-fulfillment/',
|
||||
'is_visible' => array(
|
||||
self::get_rules_for_countries( array( 'CL', 'AR', 'PE', 'BR', 'UY', 'GT' ) ),
|
||||
),
|
||||
'available_layouts' => array( 'column' ),
|
||||
),
|
||||
array(
|
||||
'id' => 'easyship-woocommerce-shipping-rates',
|
||||
'name' => 'Easyship',
|
||||
'slug' => 'easyship-woocommerce-shipping-rates',
|
||||
'description' => __( 'Simplified shipping with: ', 'woocommerce' ),
|
||||
'layout_column' => array(
|
||||
'image' => $asset_base_url . 'easyship-column.svg',
|
||||
'features' => $column_layout_features,
|
||||
),
|
||||
'layout_row' => array(
|
||||
'image' => $asset_base_url . 'easyship-row.svg',
|
||||
'features' => array(
|
||||
array(
|
||||
'icon' => $check_icon,
|
||||
'description' => __( 'Highly discounted shipping rates', 'woocommerce' ),
|
||||
),
|
||||
array(
|
||||
'icon' => $check_icon,
|
||||
'description' => __(
|
||||
'Seamless order sync and label printing',
|
||||
'woocommerce'
|
||||
),
|
||||
),
|
||||
array(
|
||||
'icon' => $check_icon,
|
||||
'description' => __( 'Branded tracking experience', 'woocommerce' ),
|
||||
),
|
||||
array(
|
||||
'icon' => $check_icon,
|
||||
'description' => __( 'Built-in Tax & Duties paperwork', 'woocommerce' ),
|
||||
),
|
||||
array(
|
||||
'icon' => $check_icon,
|
||||
'description' => __( 'Free Plan Available', 'woocommerce' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
'learn_more_link' => 'https://woocommerce.com/products/easyship-shipping-rates/',
|
||||
'is_visible' => array(
|
||||
self::get_rules_for_countries( array( 'SG', 'HK', 'AU', 'NZ' ) ),
|
||||
),
|
||||
'available_layouts' => array( 'row', 'column' ),
|
||||
),
|
||||
array(
|
||||
'id' => 'sendcloud-shipping',
|
||||
'name' => 'Sendcloud',
|
||||
'slug' => 'sendcloud-shipping',
|
||||
'description' => __( 'All-in-one shipping tool:', 'woocommerce' ),
|
||||
'layout_column' => array(
|
||||
'image' => $asset_base_url . 'sendcloud-column.svg',
|
||||
'features' => $column_layout_features,
|
||||
),
|
||||
'layout_row' => array(
|
||||
'image' => $asset_base_url . 'sendcloud-row.svg',
|
||||
'features' => array(
|
||||
array(
|
||||
'icon' => $check_icon,
|
||||
'description' => __( 'Print labels from 80+ carriers', 'woocommerce' ),
|
||||
),
|
||||
array(
|
||||
'icon' => $check_icon,
|
||||
'description' => __(
|
||||
'Process orders in just a few clicks',
|
||||
'woocommerce'
|
||||
),
|
||||
),
|
||||
array(
|
||||
'icon' => $check_icon,
|
||||
'description' => __( 'Customize checkout options', 'woocommerce' ),
|
||||
),
|
||||
|
||||
array(
|
||||
'icon' => $check_icon,
|
||||
'description' => __( 'Self-service tracking & returns', 'woocommerce' ),
|
||||
),
|
||||
array(
|
||||
'icon' => $check_icon,
|
||||
'description' => __( 'Start with a free plan', 'woocommerce' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
'learn_more_link' => 'https://wordpress.org/plugins/sendcloud-shipping/',
|
||||
'is_visible' => array(
|
||||
self::get_rules_for_countries( array( 'NL', 'AT', 'BE', 'FR', 'DE', 'ES', 'GB', 'IT' ) ),
|
||||
),
|
||||
'available_layouts' => array( 'row', 'column' ),
|
||||
),
|
||||
array(
|
||||
'id' => 'packlink-pro-shipping',
|
||||
'name' => 'Packlink',
|
||||
'slug' => 'packlink-pro-shipping',
|
||||
'description' => __( 'Optimize your full shipping process:', 'woocommerce' ),
|
||||
'layout_column' => array(
|
||||
'image' => $asset_base_url . 'packlink-column.svg',
|
||||
'features' => $column_layout_features,
|
||||
),
|
||||
'layout_row' => array(
|
||||
'image' => $asset_base_url . 'packlink-row.svg',
|
||||
'features' => array(
|
||||
array(
|
||||
'icon' => $check_icon,
|
||||
'description' => __(
|
||||
'Automated, real-time order import',
|
||||
'woocommerce'
|
||||
),
|
||||
),
|
||||
array(
|
||||
'icon' => $check_icon,
|
||||
'description' => __(
|
||||
'Direct access to leading carriers',
|
||||
'woocommerce'
|
||||
),
|
||||
),
|
||||
array(
|
||||
'icon' => $check_icon,
|
||||
'description' => __(
|
||||
'Access competitive shipping prices',
|
||||
'woocommerce'
|
||||
),
|
||||
),
|
||||
array(
|
||||
'icon' => $check_icon,
|
||||
'description' => __( 'Quickly bulk print labels', 'woocommerce' ),
|
||||
),
|
||||
array(
|
||||
'icon' => $check_icon,
|
||||
'description' => __( 'Free shipping platform', 'woocommerce' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
'learn_more_link' => 'https://wordpress.org/plugins/packlink-pro-shipping/',
|
||||
'is_visible' => array(
|
||||
self::get_rules_for_countries( array( 'FR', 'DE', 'ES', 'IT' ) ),
|
||||
),
|
||||
'available_layouts' => array( 'row', 'column' ),
|
||||
),
|
||||
array(
|
||||
'id' => 'woocommerce-services',
|
||||
'name' => 'WooCommerce Shipping',
|
||||
'slug' => 'woocommerce-services',
|
||||
'description' => __( 'Save time and money by printing your shipping labels right from your computer with WooCommerce Shipping. Try WooCommerce Shipping for free.', 'woocommerce' ),
|
||||
'dependencies' => array( 'jetpack' ),
|
||||
'layout_column' => array(
|
||||
'image' => $asset_base_url . 'wcs-column.svg',
|
||||
'features' => array(
|
||||
array(
|
||||
'icon' => $asset_base_url . 'printer.svg',
|
||||
'title' => __( 'Buy postage when you need it', 'woocommerce' ),
|
||||
'description' => __( 'No need to wonder where that stampbook went.', 'woocommerce' ),
|
||||
),
|
||||
array(
|
||||
'icon' => $asset_base_url . 'paper.svg',
|
||||
'title' => __( 'Print at home', 'woocommerce' ),
|
||||
'description' => __( 'Pick up an order, then just pay, print, package and post.', 'woocommerce' ),
|
||||
),
|
||||
array(
|
||||
'icon' => $asset_base_url . 'discount.svg',
|
||||
'title' => __( 'Discounted rates', 'woocommerce' ),
|
||||
'description' => __( 'Access discounted shipping rates with DHL and USPS.', 'woocommerce' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
'learn_more_link' => 'https://woocommerce.com/products/shipping/',
|
||||
'is_visible' => array(
|
||||
self::get_rules_for_countries( array( 'US' ) ),
|
||||
),
|
||||
'available_layouts' => array( 'column' ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\ShippingPartnerSuggestions;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\EvaluateSuggestion;
|
||||
use Automattic\WooCommerce\Admin\RemoteSpecs\RemoteSpecsEngine;
|
||||
|
||||
/**
|
||||
* Class ShippingPartnerSuggestions
|
||||
*/
|
||||
class ShippingPartnerSuggestions extends RemoteSpecsEngine {
|
||||
|
||||
/**
|
||||
* Go through the specs and run them.
|
||||
*
|
||||
* @param array|null $specs shipping partner suggestion spec array.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_suggestions( array $specs = null ) {
|
||||
$locale = get_user_locale();
|
||||
|
||||
$specs = is_array( $specs ) ? $specs : self::get_specs();
|
||||
$results = EvaluateSuggestion::evaluate_specs( $specs );
|
||||
$specs_to_return = $results['suggestions'];
|
||||
$specs_to_save = null;
|
||||
|
||||
if ( empty( $specs_to_return ) ) {
|
||||
// When suggestions is empty, replace it with defaults and save for 3 hours.
|
||||
$specs_to_save = DefaultShippingPartners::get_all();
|
||||
$specs_to_return = EvaluateSuggestion::evaluate_specs( $specs_to_save )['suggestions'];
|
||||
} elseif ( count( $results['errors'] ) > 0 ) {
|
||||
// When suggestions is not empty but has errors, save it for 3 hours.
|
||||
$specs_to_save = $specs;
|
||||
}
|
||||
|
||||
if ( $specs_to_save ) {
|
||||
ShippingPartnerSuggestionsDataSourcePoller::get_instance()->set_specs_transient( array( $locale => $specs_to_save ), 3 * HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
return $specs_to_return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specs or fetch remotely if they don't exist.
|
||||
*/
|
||||
public static function get_specs() {
|
||||
if ( 'no' === get_option( 'woocommerce_show_marketplace_suggestions', 'yes' ) ) {
|
||||
/**
|
||||
* It can be used to modify shipping partner suggestions spec.
|
||||
*
|
||||
* @since 7.4.1
|
||||
*/
|
||||
return apply_filters( 'woocommerce_admin_shipping_partner_suggestions_specs', DefaultShippingPartners::get_all() );
|
||||
}
|
||||
$specs = ShippingPartnerSuggestionsDataSourcePoller::get_instance()->get_specs_from_data_sources();
|
||||
|
||||
// Fetch specs if they don't yet exist.
|
||||
if ( false === $specs || ! is_array( $specs ) || 0 === count( $specs ) ) {
|
||||
/**
|
||||
* It can be used to modify shipping partner suggestions spec.
|
||||
*
|
||||
* @since 7.4.1
|
||||
*/
|
||||
return apply_filters( 'woocommerce_admin_shipping_partner_suggestions_specs', DefaultShippingPartners::get_all() );
|
||||
}
|
||||
|
||||
/**
|
||||
* It can be used to modify shipping partner suggestions spec.
|
||||
*
|
||||
* @since 7.4.1
|
||||
*/
|
||||
return apply_filters( 'woocommerce_admin_shipping_partner_suggestions_specs', $specs );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\ShippingPartnerSuggestions;
|
||||
|
||||
use Automattic\WooCommerce\Admin\RemoteSpecs\DataSourcePoller;
|
||||
|
||||
/**
|
||||
* Specs data source poller class for shipping partner suggestions.
|
||||
*/
|
||||
class ShippingPartnerSuggestionsDataSourcePoller extends DataSourcePoller {
|
||||
|
||||
/**
|
||||
* Data Source Poller ID.
|
||||
*/
|
||||
const ID = 'shipping_partner_suggestions';
|
||||
|
||||
/**
|
||||
* Default data sources array.
|
||||
*/
|
||||
const DATA_SOURCES = array(
|
||||
'https://woocommerce.com/wp-json/wccom/shipping-partner-suggestions/2.0/suggestions.json',
|
||||
);
|
||||
|
||||
/**
|
||||
* Class instance.
|
||||
*
|
||||
* @var ShippingPartnerSuggestionsDataSourcePoller instance
|
||||
*/
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* Get class instance.
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( ! self::$instance ) {
|
||||
self::$instance = new self( self::ID, self::DATA_SOURCES );
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Transient Notices
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features;
|
||||
|
||||
use Automattic\WooCommerce\Internal\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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user