<?php
/**
* Features loader for features developed in WooCommerce Admin.
*/
namespace Automattic\WooCommerce\Admin\Features;
use Automattic\WooCommerce\Admin\Loader;
/**
* Features Class.
*/
class Features {
/**
* Class instance.
*
* @var Loader instance
*/
protected static $instance = null;
/**
* Get class instance.
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor.
*/
public function __construct() {
// Load feature before WooCommerce update hooks.
add_action( 'init', array( __CLASS__, 'load_features' ), 4 );
add_filter( 'woocommerce_get_sections_advanced', array( __CLASS__, 'add_features_section' ) );
add_filter( 'woocommerce_get_settings_advanced', array( __CLASS__, 'add_features_settings' ), 10, 2 );
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'maybe_load_beta_features_modal' ) );
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'load_scripts' ), 15 );
add_filter( 'admin_body_class', array( __CLASS__, 'add_admin_body_classes' ) );
add_filter( 'update_option_woocommerce_allow_tracking', array( __CLASS__, 'maybe_disable_features' ), 10, 2 );
}
/**
* Gets a build configured array of enabled WooCommerce Admin features/sections.
*
* @return array Enabled Woocommerce Admin features/sections.
*/
public static function get_features() {
return apply_filters( 'woocommerce_admin_features', array() );
}
/**
* Gets the beta feature options as an associative array that can be toggled on or off.
*
* @return array
*/
public static function get_beta_feature_options() {
$features = [];
$navigation_class = self::get_feature_class( 'navigation' );
$settings_class = self::get_feature_class( 'settings' );
if ( $navigation_class ) {
$features['navigation'] = $navigation_class::TOGGLE_OPTION_NAME;
$features['settings'] = $settings_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();
}
}
}
/**
* Check if a feature is enabled. Defaults to true for all features unless they are in beta.
*
* @param string $feature Feature slug.
* @return bool
*/
public static function is_enabled( $feature ) {
if ( ! self::exists( $feature ) ) {
return false;
}
$features = self::get_beta_feature_options();
if ( isset( $features[ $feature ] ) ) {
$feature_option = $features[ $feature ];
// Check if the feature is currently being enabled.
/* phpcs:disable WordPress.Security.NonceVerification */
if ( isset( $_POST[ $feature_option ] ) && '1' === $_POST[ $feature_option ] ) {
return true;
}
return 'yes' === get_option( $feature_option, 'no' );
}
return true;
}
/**
* Enable a toggleable beta feature.
*
* @param string $feature Feature name.
* @return bool
*/
public static function enable( $feature ) {
$features = self::get_beta_feature_options();
if ( isset( $features[ $feature ] ) ) {
update_option( $features[ $feature ], 'yes' );
return true;
}
return false;
}
/**
* Disable a toggleable beta feature.
*
* @param string $feature Feature name.
* @return bool
*/
public static function disable( $feature ) {
$features = self::get_beta_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::get_features() as $feature ) {
self::disable( $feature );
}
}
/**
* Adds the Features section to the advanced tab of WooCommerce Settings
*
* @param array $sections Sections.
* @return array
*/
public static function add_features_section( $sections ) {
$features = apply_filters(
'woocommerce_settings_features',
array()
);
if ( empty( $features ) ) {
return $sections;
}
$sections['features'] = __( 'Features', 'woocommerce' );
return $sections;
}
/**
* Adds the Features settings.
*
* @param array $settings Settings.
* @param string $current_section Current section slug.
* @return array
*/
public static function add_features_settings( $settings, $current_section ) {
if ( 'features' !== $current_section ) {
return $settings;
}
$features = apply_filters(
'woocommerce_settings_features',
array()
);
if ( empty( $features ) ) {
return $settings;
}
return array_merge(
array(
array(
'title' => __( 'Features', 'woocommerce' ),
'type' => 'title',
'desc' => __( 'Start using new features that are being progressively rolled out to improve the store management experience.', 'woocommerce' ),
'id' => 'features_options',
),
),
$features,
array(
array(
'type' => 'sectionend',
'id' => 'features_options',
),
)
);
}
/**
* Conditionally loads the beta features tracking modal.
*
* @param string $hook Page hook.
*/
public static function maybe_load_beta_features_modal( $hook ) {
if (
'woocommerce_page_wc-settings' !== $hook ||
! isset( $_GET['tab'] ) || 'advanced' !== $_GET['tab'] || // phpcs:ignore CSRF ok.
! isset( $_GET['section'] ) || 'features' !== $_GET['section'] // phpcs:ignore CSRF ok.
) {
return;
}
$tracking_enabled = get_option( 'woocommerce_allow_tracking', 'no' );
if ( 'yes' === $tracking_enabled ) {
return;
}
$rtl = is_rtl() ? '.rtl' : '';
wp_enqueue_style(
'wc-admin-beta-features-tracking-modal',
Loader::get_url( "beta-features-tracking-modal/style{$rtl}", 'css' ),
array( 'wp-components' ),
Loader::get_file_version( 'css' )
);
wp_enqueue_script(
'wc-admin-beta-features-tracking-modal',
Loader::get_url( 'wp-admin-scripts/beta-features-tracking-modal', 'js' ),
array( 'wp-i18n', 'wp-element', WC_ADMIN_APP ),
Loader::get_file_version( 'js' ),
true
);
}
/**
* Loads the required scripts on the correct pages.
*/
public static function load_scripts() {
if ( ! Loader::is_admin_or_embed_page() ) {
return;
}
$features = self::get_features();
$enabled_features = array();
foreach ( $features as $key ) {
$enabled_features[ $key ] = self::is_enabled( $key );
}
wp_add_inline_script( WC_ADMIN_APP, 'window.wcAdminFeatures = ' . wp_json_encode( $enabled_features ), 'before' );
}
/**
* Adds body classes to the main wp-admin wrapper, allowing us to better target elements in specific scenarios.
*
* @param string $admin_body_class Body class to add.
*/
public static function add_admin_body_classes( $admin_body_class = '' ) {
if ( ! Loader::is_admin_or_embed_page() ) {
return $admin_body_class;
}
$classes = explode( ' ', trim( $admin_body_class ) );
$features = self::get_features();
foreach ( $features as $feature_key ) {
$classes[] = sanitize_html_class( 'woocommerce-feature-enabled-' . $feature_key );
}
$admin_body_class = implode( ' ', array_unique( $classes ) );
return " $admin_body_class ";
}
}