<?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 "; } }