Файловый менеджер - Редактировать - /home/tuudkjt/globeasy/wp-includes/ID3/Features.tar
Назад
Features.php 0000777 00000030630 15253027022 0007042 0 ustar 00 <?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; use Automattic\WooCommerce\Utilities\FeaturesUtil; /** * Features Class. */ class Features { /** * Class instance. * * @var Loader instance */ protected static $instance = null; /** * Optional features * * @var array */ protected static $optional_features = array( 'analytics' => array( 'default' => 'yes' ), 'remote-inbox-notifications' => array( 'default' => 'yes' ), ); /** * Beta features * * @var array */ protected static $beta_features = array( '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(); if ( ! self::should_load_features() ) { return; } // 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; $should_autoload_class = self::should_load_features(); if ( class_exists( $feature_class, $should_autoload_class ) ) { return $feature_class; } // Handle features contained in subdirectory. if ( class_exists( $feature_class . '\\Init', $should_autoload_class ) ) { return $feature_class . '\\Init'; } return null; } /** * Class loader for enabled WooCommerce Admin features/sections. */ public static function load_features() { if ( ! self::should_load_features() ) { return; } $features = self::get_features(); foreach ( $features as $feature ) { $feature_class = self::get_feature_class( $feature ); if ( $feature_class ) { new $feature_class(); } } if ( FeaturesUtil::feature_is_enabled( 'blueprint' ) ) { new \Automattic\WooCommerce\Admin\Features\Blueprint\Init(); } } /** * 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, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ), '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\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 ); } } /** * Check if we're in an admin context where features should be loaded. * * @return boolean */ private static function should_load_features() { $should_load = ( is_admin() || wp_doing_ajax() || wp_doing_cron() || ( defined( 'WP_CLI' ) && WP_CLI ) || ( WC()->is_rest_api_request() && ! WC()->is_store_api_request() ) || // Allow features to be loaded in frontend for admin users. This is needed for the use case such as the coming soon footer banner. current_user_can( 'manage_woocommerce' ) ); /** * Filter to determine if admin features should be loaded. * * @since 9.6.0 * @param boolean $should_load Whether admin features should be loaded. It defaults to true when the current request is in an admin context. */ return apply_filters( 'woocommerce_admin_should_load_features', $should_load ); } } MarketingRecommendations/MarketingRecommendationsDataSourcePoller.php 0000777 00000002177 15253027022 0022404 0 ustar 00 <?php namespace Automattic\WooCommerce\Admin\Features\MarketingRecommendations; use Automattic\WooCommerce\Admin\RemoteSpecs\DataSourcePoller; use WC_Helper; /** * Specs data source poller class for marketing recommendations. */ class MarketingRecommendationsDataSourcePoller extends DataSourcePoller { /** * Data Source Poller ID. */ const ID = 'marketing_recommendations'; /** * Default data sources array. * * @deprecated since 9.5.0. Use get_data_sources() instead. */ const DATA_SOURCES = array(); /** * Class instance. * * @var MarketingRecommendationsDataSourcePoller instance */ protected static $instance = null; /** * Get class instance. */ public static function get_instance() { if ( ! self::$instance ) { self::$instance = new self( self::ID, self::get_data_sources(), array( 'spec_key' => 'product', ) ); } return self::$instance; } /** * Get data sources. * * @return array */ public static function get_data_sources() { return array( WC_Helper::get_woocommerce_com_base_url() . 'wp-json/wccom/marketing-tab/1.3/recommendations.json', ); } } MarketingRecommendations/MiscRecommendationsDataSourcePoller.php 0000777 00000002514 15253027022 0021351 0 ustar 00 <?php declare(strict_types=1); namespace Automattic\WooCommerce\Admin\Features\MarketingRecommendations; use Automattic\WooCommerce\Admin\RemoteSpecs\DataSourcePoller; use WC_Helper; /** * Specs data source poller class for misc recommendations. * * The misc recommendations are fetched from the WooCommerce.com API, the data structure looks like this: * * [ * { * "id": "woocommerce-analytics", * "order_attribution_promotion_percentage": [ * [ "9.7", 100 ], * [ "9.6", 60 ], * [ "9.5", 10 ] * ] * } * ] * * @since 9.5.0 */ class MiscRecommendationsDataSourcePoller extends DataSourcePoller { /** * Data Source Poller ID. */ const ID = 'misc_recommendations'; /** * Class instance. * * @var MiscRecommendationsDataSourcePoller instance */ protected static $instance = null; /** * Get class instance. */ public static function get_instance() { if ( ! self::$instance ) { self::$instance = new self( self::ID, self::get_data_sources(), array( 'transient_expiry' => DAY_IN_SECONDS, ) ); } return self::$instance; } /** * Get data sources. * * @return array */ public static function get_data_sources() { return array( WC_Helper::get_woocommerce_com_base_url() . 'wp-json/wccom/marketing-tab/misc/recommendations.json', ); } } MarketingRecommendations/Init.php 0000777 00000015604 15253027022 0013164 0 ustar 00 <?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(); MiscRecommendationsDataSourcePoller::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; } /** * Get misc recommendations specs or fetch remotely if they don't exist. * * @since 9.5.0 */ public static function get_misc_recommendations_specs() { if ( 'no' === get_option( 'woocommerce_show_marketplace_suggestions', 'yes' ) ) { return array(); } $specs = MiscRecommendationsDataSourcePoller::get_instance()->get_specs_from_data_sources(); // Return empty specs if they don't yet exist. if ( ! is_array( $specs ) ) { return array(); } 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 ); } ); } /** * Load misc recommendations from WooCommerce.com * * @since 9.5.0 * @return array */ public static function get_misc_recommendations(): array { $specs = self::get_misc_recommendations_specs(); $results = self::evaluate_specs( $specs ); $specs_to_return = $results['suggestions']; $specs_to_save = null; if ( empty( $specs_to_return ) ) { // When misc_recommendations is empty, replace it with defaults and save for 3 hours. $specs_to_save = array(); } elseif ( count( $results['errors'] ) > 0 ) { // When misc_recommendations is not empty but has errors, save it for 3 hours. $specs_to_save = $specs; } if ( $specs_to_save ) { MiscRecommendationsDataSourcePoller::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; } /** * 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; } } MarketingRecommendations/DefaultMarketingRecommendations.php 0000777 00000040623 15253027022 0020556 0 ustar 00 <?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 for WooCommerce', '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' => 'Blaze Ads', 'description' => __( 'The quickest way to grow your business by advertising to over 100 million users across Tumblr and WordPress, starting at just \$5/day.', 'woocommerce' ), 'url' => "https://woocommerce.com/products/blaze-ads/{$utm_string}", 'direct_install' => true, 'icon' => "{$icon_dir_url}/blaze.svg", 'product' => 'blaze-ads', 'plugin' => 'blaze-ads/blaze-ads.php', 'categories' => array( $marketing, ), 'subcategories' => array( $sales_channels, ), 'tags' => array( $built_by_woocommerce, ), ), array( 'title' => 'Facebook for WooCommerce', 'description' => __( 'List products and create ads on Facebook and Instagram.', 'woocommerce' ), 'url' => "https://woocommerce.com/products/facebook/{$utm_string}", 'direct_install' => true, 'icon' => "{$icon_dir_url}/facebook.svg", 'product' => 'facebook-for-woocommerce', 'plugin' => 'facebook-for-woocommerce/facebook-for-woocommerce.php', 'categories' => array( $marketing, ), 'subcategories' => array( $sales_channels, ), 'tags' => array(), ), array( 'title' => 'Meta Ads and Pixel by Kliken', 'description' => __( 'Automate Facebook & Instagram marketing with Kliken. Launch ads and schedule a month of posts in 5 minutes—first 5 free! Plans start at just $20/mo.', 'woocommerce' ), 'url' => "https://woocommerce.com/products/meta-ads-and-pixel/{$utm_string}", 'direct_install' => true, 'icon' => "{$icon_dir_url}/kliken.svg", 'product' => 'kliken-ads-pixel-for-meta', 'plugin' => 'kliken-ads-pixel-for-meta/kliken-ads-pixel-for-meta.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(), ), ); } } Settings/Transformer.php 0000777 00000023363 15253027022 0011373 0 ustar 00 <?php /** * WooCommerce Settings Data Transformer. */ declare( strict_types = 1 ); namespace Automattic\WooCommerce\Admin\Features\Settings; /** * Transforms WooCommerce settings data into a structured format with logical groupings. */ class Transformer { /** * Current group being processed. * * @var array|null */ private ?array $current_group = null; /** * Current checkbox group being processed. * * @var array|null */ private ?array $current_checkbox_group = null; /** * Transform settings data. * * @param array $raw_settings Raw settings data. * * @return array Transformed settings data. */ public function transform( array $raw_settings ): array { $transformed = array(); foreach ( $raw_settings as $tab_id => $tab ) { // If the tab doesn't have sections, or the sections aren't an array, skip it. if ( ! isset( $tab['sections'] ) || ! is_array( $tab['sections'] ) ) { $transformed[ $tab_id ] = $tab; continue; } $transformed[ $tab_id ] = $tab; $transformed[ $tab_id ]['sections'] = $this->transform_sections( $tab['sections'] ); } return $transformed; } /** * Transform sections within a tab. * * @param array $sections Sections to transform. * * @return array Transformed sections. */ private function transform_sections( array $sections ): array { $transformed_sections = array(); foreach ( $sections as $section_id => $section ) { // If the section doesn't have settings, or the settings aren't an array, skip it. if ( ! isset( $section['settings'] ) || ! is_array( $section['settings'] ) ) { $transformed_sections[ $section_id ] = $section; continue; } $transformed_sections[ $section_id ] = $section; $transformed_sections[ $section_id ]['settings'] = $this->transform_section_settings( $section['settings'] ); } return $transformed_sections; } /** * Transform settings within a section. * * @param array $settings Settings to transform. * * @return array Transformed settings. */ private function transform_section_settings( array $settings ): array { $this->reset_state(); $transformed_settings = array(); foreach ( $settings as $setting ) { $this->process_setting( $setting, $transformed_settings ); } $this->finalize_transformation( $transformed_settings ); return $transformed_settings; } /** * Process individual setting. * * @param array $setting Setting to process. * @param array $transformed_settings Transformed settings array. */ private function process_setting( ?array $setting, array &$transformed_settings ): void { if ( ! isset( $setting ) ) { return; } $type = $setting['type'] ?? ''; if ( $this->current_checkbox_group && 'checkbox' !== $type ) { // It's expected that a checkbox group will always be closed before a non-checkbox setting. // If not, it's likely a checkbox group was not closed properly so we flush the current checkbox group and add the setting as-is. $this->flush_current_checkbox_group(); } switch ( $type ) { case 'title': $this->handle_group_start( $setting, $transformed_settings ); break; case 'sectionend': $this->handle_group_end( $setting, $transformed_settings ); break; case 'checkbox': $this->handle_checkbox_setting( $setting, $transformed_settings ); break; case 'info': if ( ! empty( $setting['text'] ) ) { $setting['text'] = wp_kses_post( wpautop( wptexturize( $setting['text'] ) ) ); } if ( ! empty( $setting['row_class'] ) && substr( $setting['row_class'], 0, 16 ) !== 'wc-settings-row-' ) { $setting['row_class'] = 'wc-settings-row-' . $setting['row_class']; } $this->add_setting( $setting, $transformed_settings ); break; default: $this->add_setting( $setting, $transformed_settings ); break; } } /** * Handle the start of a new group. * * @param array $setting Setting to add. * @param array $transformed_settings Transformed settings array. */ private function handle_group_start( array $setting, array &$transformed_settings ): void { // If we already have a group, flush it to settings before starting a new one. if ( $this->current_group ) { $this->flush_current_group( $transformed_settings ); } $this->current_group = array( $setting ); } /** * Handle the end of a group. * * @param array $setting Setting to add. * @param array $transformed_settings Transformed settings array. */ private function handle_group_end( array $setting, array &$transformed_settings ): void { $ids_match = $this->current_group && isset( $this->current_group[0]['id'] ) && isset( $setting['id'] ) && $this->current_group[0]['id'] === $setting['id']; $ids_match_undefined = $this->current_group && ! isset( $this->current_group[0]['id'] ) && ! isset( $setting['id'] ); // If IDs match, add the group and close it. if ( $ids_match || $ids_match_undefined ) { // Compose the group setting. $title_setting = array_shift( $this->current_group ); $title_setting['id'] = $title_setting['id'] ?? wp_unique_prefixed_id( 'setting_group' ); $transformed_settings[] = array_merge( $title_setting, array( 'type' => 'group', 'settings' => $this->current_group, ) ); $this->current_group = null; return; } // If IDs don't match, we don't need to transform anything so flush the current group. $this->flush_current_group( $transformed_settings ); $this->add_setting( $setting, $transformed_settings ); } /** * Flush current group to transformed settings. * * @param array $transformed_settings Transformed settings array. */ private function flush_current_group( array &$transformed_settings ): void { if ( is_array( $this->current_group ) && ! empty( $this->current_group ) ) { $this->current_group[0]['id'] = $this->current_group[0]['id'] ?? wp_unique_prefixed_id( 'setting_title' ); $transformed_settings = array_merge( $transformed_settings, $this->current_group ); } $this->current_group = null; } /** * Handle checkbox setting and grouping. * * @param array $setting Setting to add. * @param array $transformed_settings Transformed settings array. */ private function handle_checkbox_setting( array $setting, array &$transformed_settings ): void { $checkboxgroup = $setting['checkboxgroup'] ?? ''; switch ( $checkboxgroup ) { case 'start': $this->start_checkbox_group( $setting ); break; case 'end': $this->end_checkbox_group( $setting, $transformed_settings ); break; default: $this->handle_checkbox_group_item( $setting, $transformed_settings ); break; } } /** * Start a new checkbox group. * * @param array $setting Setting to add. */ private function start_checkbox_group( array $setting ): void { // If we already have an open checkbox group, flush it to settings before starting a new one. if ( is_array( $this->current_checkbox_group ) ) { $this->flush_current_checkbox_group(); } $this->current_checkbox_group = array( $setting ); } /** * End current checkbox group. * * @param array $setting Setting to add. * @param array $transformed_settings Transformed settings array. */ private function end_checkbox_group( array $setting, array &$transformed_settings ): void { if ( empty( $this->current_checkbox_group ) ) { // If we don't have an open checkbox group, add the setting as-is. $this->add_setting( $setting, $transformed_settings ); return; } $this->current_checkbox_group[] = $setting; $first_setting = $this->current_checkbox_group[0]; $checkbox_group_setting = array( 'id' => wp_unique_prefixed_id( 'setting_checkboxgroup' ), 'type' => 'checkboxgroup', 'title' => $first_setting['title'] ?? '', 'settings' => $this->current_checkbox_group, ); $this->add_setting( $checkbox_group_setting, $transformed_settings ); $this->current_checkbox_group = null; } /** * Handle checkbox within a group. * * @param array $setting Setting to add. * @param array $transformed_settings Transformed settings array. */ private function handle_checkbox_group_item( array $setting, array &$transformed_settings ): void { if ( is_array( $this->current_checkbox_group ) ) { $this->current_checkbox_group[] = $setting; return; } // If we don't have an open checkbox group, add the setting as-is. $this->add_setting( $setting, $transformed_settings ); } /** * Flush current checkbox group to transformed settings. */ private function flush_current_checkbox_group(): void { if ( is_array( $this->current_checkbox_group ) ) { if ( is_array( $this->current_group ) ) { $this->current_group = array_merge( $this->current_group, $this->current_checkbox_group ); } else { $this->current_group = $this->current_checkbox_group; } $this->current_checkbox_group = null; } } /** * Add setting to current context (group or root). * * @param array $setting Setting to add. * @param array $transformed_settings Transformed settings array. */ private function add_setting( array $setting, array &$transformed_settings ): void { $setting['id'] = $setting['id'] ?? wp_unique_prefixed_id( 'setting_field' ); if ( is_array( $this->current_group ) ) { $this->current_group[] = $setting; return; } $transformed_settings[] = $setting; } /** * Finalize the transformation process. * * @param array &$transformed_settings Transformed settings array. */ private function finalize_transformation( array &$transformed_settings ): void { $this->flush_current_checkbox_group(); $this->flush_current_group( $transformed_settings ); } /** * Reset the state to its initial values. */ public function reset_state(): void { $this->current_group = null; $this->current_checkbox_group = null; } } Settings/Init.php 0000777 00000016065 15253027022 0007775 0 ustar 00 <?php /** * WooCommerce Settings. */ declare( strict_types = 1 ); namespace Automattic\WooCommerce\Admin\Features\Settings; use Automattic\WooCommerce\Internal\Admin\WCAdminAssets; /** * Contains backend logic for the Settings feature. */ class Init { /** * Class instance. * * @var Init instance */ protected static $instance = null; /** * Get class instance. */ public static function get_instance() { if ( ! self::$instance ) { self::$instance = new self(); } return self::$instance; } /** * Hook into WooCommerce. */ public function __construct() { if ( ! is_admin() ) { return; } add_filter( 'woocommerce_admin_shared_settings', array( __CLASS__, 'add_component_settings' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_settings_editor_scripts' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_settings_editor_styles' ) ); } /** * Check if the current screen is the WooCommerce settings page. * * @return bool */ public function is_settings_page() { $screen = get_current_screen(); return $screen && 'woocommerce_page_wc-settings' === $screen->id; } /** * Enqueue styles for the settings editor. */ public function enqueue_settings_editor_styles() { if ( ! self::get_instance()->is_settings_page() ) { return; } $style_name = 'wc-admin-edit-settings'; $style_path_name = 'settings'; $style_assets_filename = WCAdminAssets::get_script_asset_filename( $style_path_name, 'style' ); $style_assets = require WC_ADMIN_ABSPATH . WC_ADMIN_DIST_JS_FOLDER . $style_path_name . '/' . $style_assets_filename; // Settings Editor styles. wp_register_style( $style_name, WCAdminAssets::get_url( $style_path_name . '/style', 'css' ), // Manually set dependencies for now, because the asset file is not being generated correctly. // See plugins/woocommerce/assets/client/admin/settings-editor/style.asset.php. Should be: `isset( $style_assets['dependencies'] ) ? $style_assets['dependencies'] : array(),`. array( 'wp-components', 'wc-components' ), WCAdminAssets::get_file_version( 'css', $style_assets['version'] ), ); wp_enqueue_style( $style_name ); // Global presets styles. 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 scripts for the settings editor. */ public function enqueue_settings_editor_scripts() { if ( ! self::get_instance()->is_settings_page() ) { return; } // Make sure the Settings Editor package is loaded. wp_enqueue_script( 'wc-settings-editor' ); wp_enqueue_style( 'wc-settings-editor' ); $script_name = 'wc-admin-edit-settings'; $script_path_name = 'settings'; $script_assets_filename = WCAdminAssets::get_script_asset_filename( $script_path_name, 'index' ); $script_assets = require WC_ADMIN_ABSPATH . WC_ADMIN_DIST_JS_FOLDER . $script_path_name . '/' . $script_assets_filename; wp_enqueue_script( $script_name, WCAdminAssets::get_url( $script_path_name . '/index', 'js' ), $script_assets['dependencies'], WCAdminAssets::get_file_version( 'js', $script_assets['version'] ), true ); wp_set_script_translations( 'wc-admin-' . $script_name, 'woocommerce' ); } /** * Add the necessary data to initially load the WooCommerce Settings pages. * * @param array $settings Array of component settings. * @return array Array of component settings. */ public static function add_component_settings( $settings ) { if ( ! self::get_instance()->is_settings_page() ) { return $settings; } global $wp_scripts; // Set the scripts that all settings pages should have. $ignored_settings_scripts = array( 'wc-admin-app', 'woocommerce_admin', 'wc-settings-editor', 'wc-admin-edit-settings', 'woo-tracks', 'woocommerce-admin-test-helper', 'woocommerce-beta-tester-live-branches', 'WCPAY_DASH_APP', ); $default_scripts_handles = array_diff( $wp_scripts->queue, $ignored_settings_scripts, ); $settings['settingsScripts']['_default'] = self::get_script_urls( $default_scripts_handles ); // Add the settings data to the settings array. $setting_pages = \WC_Admin_Settings::get_settings_pages(); $settings = self::get_page_data( $settings, $setting_pages ); return $settings; } /** * Get the page data for the settings editor. * * @param array $settings The settings array. * @param array $setting_pages The setting pages. * @return array The settings array. */ public static function get_page_data( $settings, $setting_pages ) { global $wp_scripts; /** * Filters the settings tabs array. * * @since 2.5.0 * * @param array $available_pages The available pages. */ $available_pages = apply_filters( 'woocommerce_settings_tabs_array', array() ); $pages = array(); foreach ( $setting_pages as $setting_page ) { // If any page has removed itself from the tabs array, avoid adding this page to the settings editor. if ( ! in_array( $setting_page->get_id(), array_keys( $available_pages ), true ) ) { continue; } $scripts_before_adding_settings = $wp_scripts->queue; $pages = $setting_page->add_settings_page_data( $pages ); $settings_scripts_handles = array_diff( $wp_scripts->queue, $scripts_before_adding_settings ); $settings['settingsScripts'][ $setting_page->get_id() ] = self::get_script_urls( $settings_scripts_handles ); } $transformer = new Transformer(); $settings['settingsData']['pages'] = $transformer->transform( $pages ); $settings['settingsData']['start'] = $setting_pages[0]->get_custom_view( 'woocommerce_settings_start' ); $settings['settingsData']['_wpnonce'] = wp_create_nonce( 'wp_rest' ); return $settings; } /** * Retrieve the script URLs from the provided script handles. * This will also filter out scripts from WordPress core since they only need to be loaded once. * * @param array $script_handles Array of script handles. * @return array Array of script URLs. */ private static function get_script_urls( $script_handles ) { global $wp_scripts; $script_urls = array(); foreach ( $script_handles as $script ) { $registered_script = $wp_scripts->registered[ $script ]; if ( ! isset( $registered_script->src ) ) { continue; } // Skip scripts from WordPress core since they only need to be loaded once. if ( strpos( $registered_script->src, '/' . WPINC . '/js' ) === 0 || strpos( $registered_script->src, '/wp-admin/js' ) === 0 ) { continue; } $src = $registered_script->src; $ver = $registered_script->ver ? $registered_script->ver : false; // Add version query parameter. if ( $ver ) { $src = add_query_arg( 'ver', $ver, $src ); } // Add home URL if the src is a relative path. if ( strpos( $src, '/' ) === 0 ) { $script_urls[] = home_url( $src ); } else { $script_urls[] = $src; } } return $script_urls; } } Onboarding.php 0000777 00000005176 15253027022 0007355 0 ustar 00 <?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' ); return array(); } /** * 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' ); return array(); } /** * 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' ); return array(); } /** * 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' ); return array(); } } ShippingPartnerSuggestions/ShippingPartnerSuggestionsDataSourcePoller.php 0000777 00000002133 15253027022 0023332 0 ustar 00 <?php namespace Automattic\WooCommerce\Admin\Features\ShippingPartnerSuggestions; use Automattic\WooCommerce\Admin\RemoteSpecs\DataSourcePoller; use WC_Helper; /** * 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. * * @deprecated since 9.5.0. Use get_data_sources() instead. */ const DATA_SOURCES = array(); /** * 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::get_data_sources() ); } return self::$instance; } /** * Get data sources. * * @return array */ public static function get_data_sources() { return array( WC_Helper::get_woocommerce_com_base_url() . 'wp-json/wccom/shipping-partner-suggestions/2.0/suggestions.json', ); } } ShippingPartnerSuggestions/ShippingPartnerSuggestions.php 0000777 00000005035 15253027022 0020205 0 ustar 00 <?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, array( 'source' => 'wc-shipping-partner-suggestions' ) ); $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, array( 'source' => 'wc-shipping-partner-suggestions' ) )['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 ); } } ShippingPartnerSuggestions/DefaultShippingPartners.php 0000777 00000021570 15253027022 0017444 0 ustar 00 <?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' => __( 'Discounted labels from top global carriers', 'woocommerce' ), ), array( 'icon' => $check_icon, 'description' => __( 'Sync all your selling channels in one place', 'woocommerce' ), ), array( 'icon' => $check_icon, 'description' => __( 'Advanced automated workflows and customs', 'woocommerce' ), ), array( 'icon' => $check_icon, 'description' => __( 'Instantly send tracking to your customers', 'woocommerce' ), ), array( 'icon' => $check_icon, 'description' => __( '30-day 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' => '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-shipping', 'name' => 'WooCommerce Shipping', 'slug' => 'woocommerce-shipping', 'description' => __( 'Save time and money by printing your shipping labels right from your computer with WooCommerce Shipping. Try WooCommerce Shipping for free.', 'woocommerce' ), '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 USPS, UPS, and DHL.', 'woocommerce' ), ), ), ), 'learn_more_link' => 'https://woocommerce.com/products/shipping/', 'is_visible' => array( self::get_rules_for_countries( array( 'US' ) ), (object) array( 'type' => 'not', 'operand' => array( (object) array( 'type' => 'plugins_activated', 'plugins' => array( 'woocommerce-shipping' ), ), ), ), ), '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 ) { return (object) array( 'type' => 'base_location_country', 'operation' => 'in', 'value' => $countries, ); } } Blueprint/Exporters/ExportWCSettingsShipping.php 0000777 00000012621 15253027022 0016161 0 ustar 00 <?php declare(strict_types=1); namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters; use Automattic\WooCommerce\Blueprint\Steps\RunSql; use Automattic\WooCommerce\Blueprint\Steps\SetSiteOptions; use Automattic\WooCommerce\Blueprint\Util; /** * Class ExportWCSettingsShipping * * Exports WooCommerce settings on the Shipping page. * * @package Automattic\WooCommerce\Admin\Features\Blueprint\Exporters */ class ExportWCSettingsShipping extends ExportWCSettings { /** * Export WooCommerce shipping settings. * * @return array Array of RunSql|SetSiteOptions instances. */ public function export(): array { $shipping_settings = parent::export(); $steps = array_merge( array( $shipping_settings ), $this->get_steps_for_classes_and_terms(), $this->get_steps_for_zones(), $this->get_steps_for_locations(), $this->get_steps_for_methods_and_options() ); $steps[] = $this->get_step_for_local_pickup(); return $steps; } /** * Retrieve term data based on provided classes. * * @param array $classes List of classes with term IDs. * @return array Retrieved term data. */ protected function get_terms( array $classes ): array { global $wpdb; $term_ids = array_map( fn( $term ) => (int) $term['term_id'], $classes ); $term_ids = implode( ', ', $term_ids ); return ! empty( $term_ids ) ? $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}terms WHERE term_id IN (%s)", $term_ids ), ARRAY_A ) : array(); } /** * Retrieve shipping classes and related terms. * * @return array Steps for shipping classes and terms. */ protected function get_steps_for_classes_and_terms(): array { global $wpdb; $classes = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}term_taxonomy WHERE taxonomy = 'product_shipping_class'", ARRAY_A ); $classes_steps = array_map( fn( $class_row ) => new RunSql( Util::array_to_insert_sql( $class_row, $wpdb->prefix . 'term_taxonomy', 'replace into' ) ), $classes ); $terms = array_map( fn( $term ) => new RunSql( Util::array_to_insert_sql( $term, $wpdb->prefix . 'terms', 'replace into' ) ), $this->get_terms( $classes ) ); return array_merge( $classes_steps, $terms ); } /** * Get the name of the step. * * @return string */ public function get_step_name(): string { return RunSql::get_step_name(); } /** * Return label used in the frontend. * * @return string */ public function get_label(): string { return __( 'Shipping', 'woocommerce' ); } /** * Return description used in the frontend. * * @return string */ public function get_description(): string { return __( 'Includes all settings in WooCommerce | Settings | Shipping.', 'woocommerce' ); } /** * Get the alias. * * @return string */ public function get_alias(): string { return 'setWCShipping'; } /** * Retrieve shipping zones from the database. * * @return array Steps for shipping zones. */ private function get_steps_for_zones(): array { global $wpdb; return array_map( fn( $zone ) => new RunSql( Util::array_to_insert_sql( $zone, $wpdb->prefix . 'woocommerce_shipping_zones', 'replace into' ) ), $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}woocommerce_shipping_zones", ARRAY_A ) ); } /** * Retrieve shipping zone locations. * * @return array Steps for shipping zone locations. */ private function get_steps_for_locations(): array { global $wpdb; return array_map( fn( $location ) => new RunSql( Util::array_to_insert_sql( $location, $wpdb->prefix . 'woocommerce_shipping_zone_locations', 'replace into' ) ), $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}woocommerce_shipping_zone_locations", ARRAY_A ) ); } /** * Retrieve shipping methods and options. * * @return array Steps for shipping methods and options. */ private function get_steps_for_methods_and_options(): array { global $wpdb; $methods = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}woocommerce_shipping_zone_methods", ARRAY_A ); $method_options = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}options WHERE option_name LIKE 'woocommerce_flat_rate_%_settings' OR option_name LIKE 'woocommerce_free_shipping_%_settings'", ARRAY_A ); return array_merge( array_map( fn( $method ) => new RunSql( Util::array_to_insert_sql( $method, $wpdb->prefix . 'woocommerce_shipping_zone_methods', 'replace into' ) ), $methods ), array_map( fn( $option ) => new RunSql( Util::array_to_insert_sql( $option, $wpdb->prefix . 'options', 'replace into' ) ), $method_options ) ); } /** * Retrieve local pickup settings. * * @return SetSiteOptions Local pickup settings step. */ private function get_step_for_local_pickup(): SetSiteOptions { return new SetSiteOptions( array( 'woocommerce_pickup_location_settings' => get_option( 'woocommerce_pickup_location_settings', array() ), 'pickup_location_pickup_locations' => get_option( 'pickup_location_pickup_locations', array() ), ) ); } /** * Check if the current user has the required capabilities for this step. * * @return bool True if the user has the required capabilities. False otherwise. */ public function check_step_capabilities(): bool { return current_user_can( 'manage_woocommerce' ); } /** * Get the page ID for the settings page. * * @return string */ public function get_page_id(): string { return 'shipping'; } } Blueprint/Exporters/ExportWCSettingsSiteVisibility.php 0000777 00000003544 15253027022 0017360 0 ustar 00 <?php declare( strict_types = 1); namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters; use Automattic\WooCommerce\Blueprint\Exporters\HasAlias; use Automattic\WooCommerce\Blueprint\Exporters\StepExporter; use Automattic\WooCommerce\Blueprint\Steps\SetSiteOptions; use Automattic\WooCommerce\Blueprint\UseWPFunctions; /** * Class ExportWCSettingsSiteVisibility * * This class exports WooCommerce settings on the Site Visibility page. * * @package Automattic\WooCommerce\Admin\Features\Blueprint\Exporters */ class ExportWCSettingsSiteVisibility implements StepExporter, HasAlias { use UseWPFunctions; /** * Export Site Visibility settings. * * @return SetSiteOptions */ public function export() { return new SetSiteOptions( array( 'woocommerce_coming_soon' => $this->wp_get_option( 'woocommerce_coming_soon' ), 'woocommerce_store_pages_only' => $this->wp_get_option( 'woocommerce_store_pages_only' ), ) ); } /** * Get the alias for this exporter. * * @return string */ public function get_alias() { return 'setWCSettingsSiteVisibility'; } /** * Return label used in the frontend. * * @return string */ public function get_label() { return __( 'Site Visibility', 'woocommerce' ); } /** * Return description used in the frontend. * * @return string */ public function get_description() { return __( 'Includes all settings in WooCommerce | Settings | Visibility.', 'woocommerce' ); } /** * Get the name of the step. * * @return string */ public function get_step_name() { return 'setSiteOptions'; } /** * Check if the current user has the required capabilities for this step. * * @return bool True if the user has the required capabilities. False otherwise. */ public function check_step_capabilities(): bool { return current_user_can( 'manage_woocommerce' ); } } Blueprint/Exporters/ExportWCSettingsAdvanced.php 0000777 00000002055 15253027022 0016105 0 ustar 00 <?php declare( strict_types = 1); namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters; use Automattic\WooCommerce\Blueprint\UseWPFunctions; /** * Class ExportWCSettingsAdvanced * * This class exports WooCommerce settings on the Advanced page. * * @package Automattic\WooCommerce\Admin\Features\Blueprint\Exporters */ class ExportWCSettingsAdvanced extends ExportWCSettings { use UseWPFunctions; /** * Get the alias for this exporter. * * @return string */ public function get_alias() { return 'setWCSettingsAdvanced'; } /** * Return label used in the frontend. * * @return string */ public function get_label() { return __( 'Advanced', 'woocommerce' ); } /** * Return description used in the frontend. * * @return string */ public function get_description() { return __( 'Includes all settings in WooCommerce | Settings | Advanced.', 'woocommerce' ); } /** * Get the page ID for the settings page. * * @return string */ protected function get_page_id(): string { return 'advanced'; } } Blueprint/Exporters/ExportWCSettings.php 0000777 00000004376 15253027022 0014467 0 ustar 00 <?php declare( strict_types = 1); namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters; use Automattic\WooCommerce\Admin\Features\Blueprint\SettingOptions; use Automattic\WooCommerce\Blueprint\Exporters\HasAlias; use Automattic\WooCommerce\Blueprint\Exporters\StepExporter; use Automattic\WooCommerce\Blueprint\Steps\SetSiteOptions; use Automattic\WooCommerce\Blueprint\UseWPFunctions; /** * Class ExportWCSettings * * This abstract class provides the functionality for exporting WooCommerce settings on a specific page. * * @package Automattic\WooCommerce\Admin\Features\Blueprint\Exporters */ abstract class ExportWCSettings implements StepExporter, HasAlias { use UseWPFunctions; /** * The setting options class. * * @var SettingOptions */ protected $setting_options; /** * Constructor. * * @param SettingOptions|null $setting_options The setting options class. */ public function __construct( ?SettingOptions $setting_options = null ) { $this->setting_options = $setting_options ?? new SettingOptions(); } /** * Return a page I.D to export. * * @return string The page ID. */ abstract protected function get_page_id(): string; /** * Export WooCommerce settings. * * @return SetSiteOptions */ public function export() { return new SetSiteOptions( $this->setting_options->get_page_options( $this->get_page_id() ) ); } /** * Get the name of the step. * * @return string */ public function get_step_name() { return 'setSiteOptions'; } /** * Get the alias for this exporter. * * @return string */ public function get_alias() { return 'setWCSettingsGeneral'; } /** * Return label used in the frontend. * * @return string */ public function get_label() { return __( 'General', 'woocommerce' ); } /** * Return description used in the frontend. * * @return string */ public function get_description() { return __( 'Includes all settings in WooCommerce | Settings | General.', 'woocommerce' ); } /** * Check if the current user has the required capabilities for this step. * * @return bool True if the user has the required capabilities. False otherwise. */ public function check_step_capabilities(): bool { return current_user_can( 'manage_woocommerce' ); } } Blueprint/Exporters/ExportWCSettingsTax.php 0000777 00000004463 15253027022 0015141 0 ustar 00 <?php declare( strict_types = 1); namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters; use Automattic\WooCommerce\Blueprint\UseWPFunctions; use Automattic\WooCommerce\Blueprint\Steps\RunSql; use Automattic\WooCommerce\Blueprint\Util; use Automattic\WooCommerce\Admin\Features\Blueprint\SettingOptions; /** * Class ExportWCSettingsTax * * This class exports WooCommerce settings on the Tax page. * * @package Automattic\WooCommerce\Admin\Features\Blueprint\Exporters */ class ExportWCSettingsTax extends ExportWCSettings { use UseWPFunctions; /** * Constructor. * * @param SettingOptions|null $setting_options The setting options class. */ public function __construct( ?SettingOptions $setting_options = null ) { // phpcs:ignore Generic.CodeAnalysis.UselessOverridingMethod.Found parent::__construct( $setting_options ); } /** * Get the alias for this exporter. * * @return string */ public function get_alias() { return 'setWCSettingsTax'; } /** * Export WooCommerce tax rates. * * @return array array of steps */ public function export(): array { $basic_tax_settings = parent::export(); return array( $basic_tax_settings, ...$this->generateTaxRateSteps( 'wc_tax_rate_classes' ), ...$this->generateTaxRateSteps( 'woocommerce_tax_rates' ), ...$this->generateTaxRateSteps( 'woocommerce_tax_rate_locations' ), ); } /** * Return label used in the frontend. * * @return string */ public function get_label() { return __( 'Tax', 'woocommerce' ); } /** * Return description used in the frontend. * * @return string */ public function get_description() { return __( 'Includes all settings in WooCommerce | Settings | Tax.', 'woocommerce' ); } /** * Get the page ID for the settings page. * * @return string */ protected function get_page_id(): string { return 'tax'; } /** * Generate SQL steps for exporting data. * * @param string $table Table identifier. * @return array Array of RunSql steps. */ private function generateTaxRateSteps( string $table ): array { global $wpdb; $table = $wpdb->prefix . $table; return array_map( fn( $record ) => new RunSql( Util::array_to_insert_sql( $record, $table, 'replace into' ) ), $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i', $table ), ARRAY_A ), ); } } Blueprint/Exporters/ExportWCPaymentGateways.php 0000777 00000004226 15253027022 0016003 0 ustar 00 <?php declare( strict_types = 1); namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters; use Automattic\WooCommerce\Blueprint\Exporters\StepExporter; use Automattic\WooCommerce\Blueprint\Steps\SetSiteOptions; use Automattic\WooCommerce\Blueprint\Steps\Step; /** * ExportWCPaymentGateways class */ class ExportWCPaymentGateways implements StepExporter { /** * Payment gateway IDs to exclude from export * * @var array|string[] Payment gateway IDs to exclude from export */ protected array $exclude_ids = array( 'pre_install_woocommerce_payments_promotion' ); /** * Export the step * * @return Step */ public function export(): Step { $options = array(); $this->maybe_hide_wcpay_gateways(); foreach ( $this->get_wc_payment_gateways() as $id => $payment_gateway ) { if ( in_array( $id, $this->exclude_ids, true ) ) { continue; } $options[ 'woocommerce_' . $id . '_settings' ] = $payment_gateway->settings; } return new SetSiteOptions( $options ); } /** * Return the payment gateways resgietered in WooCommerce * * @return string */ public function get_wc_payment_gateways() { return WC()->payment_gateways->payment_gateways(); } /** * Get the step name * * @return string */ public function get_step_name() { return 'wcPaymentGateways'; } /** * Maybe hide WooCommerce Payments gateways * * @return void */ protected function maybe_hide_wcpay_gateways() { if ( class_exists( 'WC_Payments' ) ) { \WC_Payments::hide_gateways_on_settings_page(); } } /** * Return label used in the frontend. * * @return string */ public function get_label() { return __( 'Payments', 'woocommerce' ); } /** * Return description used in the frontend. * * @return string */ public function get_description() { return __( 'Includes all settings in WooCommerce | Settings | Payments.', 'woocommerce' ); } /** * Check if the current user has the required capabilities for this step. * * @return bool True if the user has the required capabilities. False otherwise. */ public function check_step_capabilities(): bool { return current_user_can( 'manage_woocommerce' ); } } Blueprint/Exporters/ExportWCTaskOptions.php 0000777 00000003567 15253027022 0015146 0 ustar 00 <?php declare( strict_types = 1); namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters; use Automattic\WooCommerce\Blueprint\Exporters\HasAlias; use Automattic\WooCommerce\Blueprint\Exporters\StepExporter; use Automattic\WooCommerce\Blueprint\Steps\SetSiteOptions; use Automattic\WooCommerce\Blueprint\UseWPFunctions; /** * Class ExportWCTaskOptions * * This class exports WooCommerce task options. * * @package Automattic\WooCommerce\Admin\Features\Blueprint\Exporters */ class ExportWCTaskOptions implements StepExporter, HasAlias { use UseWPFunctions; /** * Export WooCommerce task options. * * @return SetSiteOptions */ public function export() { return new SetSiteOptions( array( 'woocommerce_admin_customize_store_completed' => $this->wp_get_option( 'woocommerce_admin_customize_store_completed', 'no' ), 'woocommerce_task_list_tracked_completed_actions' => $this->wp_get_option( 'woocommerce_task_list_tracked_completed_actions', array() ), ) ); } /** * Get the name of the step. * * @return string */ public function get_step_name() { return 'setOptions'; } /** * Get the alias for this exporter. * * @return string */ public function get_alias() { return 'setWCTaskOptions'; } /** * Return label used in the frontend. * * @return string */ public function get_label() { return __( 'Task Configurations', 'woocommerce' ); } /** * Return description used in the frontend. * * @return string */ public function get_description() { return __( 'Includes the task configurations for WooCommerce.', 'woocommerce' ); } /** * Check if the current user has the required capabilities for this step. * * @return bool True if the user has the required capabilities. False otherwise. */ public function check_step_capabilities(): bool { return current_user_can( 'manage_woocommerce' ); } } Blueprint/Exporters/ExportWCSettingsProducts.php 0000777 00000002055 15253027022 0016203 0 ustar 00 <?php declare( strict_types = 1); namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters; use Automattic\WooCommerce\Blueprint\UseWPFunctions; /** * Class ExportWCSettingsProducts * * This class exports WooCommerce settings on the Products page. * * @package Automattic\WooCommerce\Admin\Features\Blueprint\Exporters */ class ExportWCSettingsProducts extends ExportWCSettings { use UseWPFunctions; /** * Get the alias for this exporter. * * @return string */ public function get_alias() { return 'setWCSettingsProducts'; } /** * Return label used in the frontend. * * @return string */ public function get_label() { return __( 'Products', 'woocommerce' ); } /** * Return description used in the frontend. * * @return string */ public function get_description() { return __( 'Includes all settings in WooCommerce | Settings | Products.', 'woocommerce' ); } /** * Get the page ID for the settings page. * * @return string */ protected function get_page_id(): string { return 'products'; } } Blueprint/Exporters/ExportWCSettingsAccount.php 0000777 00000002112 15253027022 0015766 0 ustar 00 <?php declare( strict_types = 1); namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters; use Automattic\WooCommerce\Blueprint\UseWPFunctions; /** * Class ExportWCSettingsAccount * * This class exports WooCommerce settings on the Account and Privacy page. * * @package Automattic\WooCommerce\Admin\Features\Blueprint\Exporters */ class ExportWCSettingsAccount extends ExportWCSettings { use UseWPFunctions; /** * Get the alias for this exporter. * * @return string */ public function get_alias() { return 'setWCSettingsAccount'; } /** * Return label used in the frontend. * * @return string */ public function get_label() { return __( 'Account and Privacy', 'woocommerce' ); } /** * Return description used in the frontend. * * @return string */ public function get_description() { return __( 'Includes all settings in WooCommerce | Settings | Account and Privacy.', 'woocommerce' ); } /** * Get the page ID for the settings page. * * @return string */ protected function get_page_id(): string { return 'account'; } } Blueprint/Exporters/ExportWCCoreProfilerOptions.php 0000777 00000003515 15253027022 0016630 0 ustar 00 <?php declare( strict_types = 1); namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters; use Automattic\WooCommerce\Blueprint\Exporters\StepExporter; use Automattic\WooCommerce\Blueprint\Exporters\HasAlias; use Automattic\WooCommerce\Blueprint\Steps\SetSiteOptions; use Automattic\WooCommerce\Blueprint\UseWPFunctions; /** * ExportWCCoreProfilerOptions class */ class ExportWCCoreProfilerOptions implements StepExporter, HasAlias { use UseWPFunctions; /** * Export the step * * @return SetSiteOptions */ public function export() { return new SetSiteOptions( array( 'blogname' => $this->wp_get_option( 'blogname' ), 'woocommerce_allow_tracking' => $this->wp_get_option( 'woocommerce_allow_tracking' ), 'woocommerce_onboarding_profile' => $this->wp_get_option( 'woocommerce_onboarding_profile', array() ), 'woocommerce_default_country' => $this->wp_get_option( 'woocommerce_default_country' ), ) ); } /** * Get the step name * * @return string */ public function get_step_name() { return 'setSiteOptions'; } /** * Get the alias * * @return string */ public function get_alias() { return 'setWCCoreProfilerOptions'; } /** * Return label used in the frontend. * * @return string */ public function get_label() { return __( 'Onboarding Configuration', 'woocommerce' ); } /** * Return description used in the frontend. * * @return string */ public function get_description() { return __( 'Includes onboarding configuration options', 'woocommerce' ); } /** * Check if the current user has the required capabilities for this step. * * @return bool True if the user has the required capabilities. False otherwise. */ public function check_step_capabilities(): bool { return current_user_can( 'manage_woocommerce' ); } } Blueprint/Exporters/ExportWCSettingsGeneral.php 0000777 00000002046 15253027022 0015755 0 ustar 00 <?php declare( strict_types = 1); namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters; use Automattic\WooCommerce\Blueprint\UseWPFunctions; /** * Class ExportWCSettingsGeneral * * This class exports WooCommerce settings on the General page. * * @package Automattic\WooCommerce\Admin\Features\Blueprint\Exporters */ class ExportWCSettingsGeneral extends ExportWCSettings { use UseWPFunctions; /** * Get the alias for this exporter. * * @return string */ public function get_alias() { return 'setWCSettingsGeneral'; } /** * Return label used in the frontend. * * @return string */ public function get_label() { return __( 'General', 'woocommerce' ); } /** * Return description used in the frontend. * * @return string */ public function get_description() { return __( 'Includes all settings in WooCommerce | Settings | General.', 'woocommerce' ); } /** * Get the page ID for the settings page. * * @return string */ protected function get_page_id(): string { return 'general'; } } Blueprint/Exporters/ExportWCSettingsEmails.php 0000777 00000003271 15253027022 0015613 0 ustar 00 <?php declare( strict_types = 1); namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters; use Automattic\WooCommerce\Admin\Features\Blueprint\SettingOptions; use Automattic\WooCommerce\Blueprint\UseWPFunctions; use Automattic\WooCommerce\Blueprint\Steps\SetSiteOptions; /** * Class ExportWCSettingsEmails * * This class exports WooCommerce settings on the Emails page. * * @package Automattic\WooCommerce\Admin\Features\Blueprint\Exporters */ class ExportWCSettingsEmails extends ExportWCSettings { use UseWPFunctions; /** * Get the alias for this exporter. * * @return string */ public function get_alias() { return 'setWCSettingsEmails'; } /** * Export WooCommerce settings. * * @return SetSiteOptions */ public function export() { $emails = \WC_Emails::instance(); $setting_options = new SettingOptions(); $email_settings = $setting_options->get_page_options( $this->get_page_id() ); // Get sub-settings for each email. foreach ( $emails->get_emails() as $email ) { $email_settings = array_merge( $email_settings, $setting_options->get_page_options( 'email_' . $email->id ) ); } return new SetSiteOptions( $email_settings ); } /** * Return label used in the frontend. * * @return string */ public function get_label() { return __( 'Emails', 'woocommerce' ); } /** * Return description used in the frontend. * * @return string */ public function get_description() { return __( 'Includes all settings in WooCommerce | Settings | Emails.', 'woocommerce' ); } /** * Get the page ID for the settings page. * * @return string */ protected function get_page_id(): string { return 'email'; } } Blueprint/Exporters/ExportWCSettingsIntegrations.php 0000777 00000003161 15253027022 0017045 0 ustar 00 <?php declare( strict_types = 1); namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters; use Automattic\WooCommerce\Blueprint\Steps\SetSiteOptions; use Automattic\WooCommerce\Blueprint\UseWPFunctions; /** * Class ExportWCSettingsIntegrations * * This class exports WooCommerce settings on the Integrations page. * * @package Automattic\WooCommerce\Admin\Features\Blueprint\Exporters */ class ExportWCSettingsIntegrations extends ExportWCSettings { use UseWPFunctions; /** * Get the alias for this exporter. * * @return string */ public function get_alias() { return 'setWCSettingsIntegrations'; } /** * Return label used in the frontend. * * @return string */ public function get_label() { return __( 'Integrations', 'woocommerce' ); } /** * Export WooCommerce settings. * * @return SetSiteOptions */ public function export() { if ( ! isset( WC()->integrations ) ) { return new SetSiteOptions( array() ); } $integrations = WC()->integrations->get_integrations(); $settings = array(); foreach ( $integrations as $integration ) { $option_key = $integration->get_option_key(); $settings[ $option_key ] = get_option( $option_key, null ); } return new SetSiteOptions( $settings ); } /** * Return description used in the frontend. * * @return string */ public function get_description() { return __( 'Includes all settings in WooCommerce | Settings | Integrations.', 'woocommerce' ); } /** * Get the page ID for the settings page. * * @return string */ protected function get_page_id(): string { return 'integration'; } } Blueprint/SettingOptions.php 0000777 00000003224 15253027022 0012220 0 ustar 00 <?php declare( strict_types = 1 ); namespace Automattic\WooCommerce\Admin\Features\Blueprint; /** * Handles getting options from WooCommerce settings pages. * * Class SettingOptions */ class SettingOptions { /** * Setting option controller. * * @var \WC_REST_Setting_Options_Controller */ private $setting_option_controller; /** * Ignore setting types. * * @var array */ private $ignore_setting_types = array( 'title', 'sectionend', 'slotfill_placeholder', 'hidden' ); /** * Constructor. */ public function __construct() { $this->setting_option_controller = new \WC_REST_Setting_Options_Controller(); } /** * Get options for a specific settings page. * * @param string $page_id The page ID. * @return array * * @throws \Exception If the settings page is not found. */ public function get_page_options( $page_id ) { $settings = $this->setting_option_controller->get_group_settings( $page_id ); if ( is_wp_error( $settings ) ) { throw new \Exception( esc_html( $settings->get_error_message() ) ); } $page_options = array(); foreach ( $settings as $setting ) { // Skip if the setting type is not valid. if ( in_array( $setting['type'], $this->ignore_setting_types, true ) || ! isset( $setting['id'] ) ) { continue; } $key = is_array( $setting['option_key'] ) ? $setting['option_key'][0] : $setting['option_key']; // Skip if the option key is already in the page options. if ( in_array( $key, $page_options, true ) ) { continue; } $default_value = $setting['default'] ?? null; $page_options[ $key ] = get_option( $key, $default_value ); } return $page_options; } } Blueprint/RestApi.php 0000777 00000025567 15253027022 0010614 0 ustar 00 <?php declare( strict_types = 1 ); namespace Automattic\WooCommerce\Admin\Features\Blueprint; use Automattic\WooCommerce\Blueprint\Exporters\ExportInstallPluginSteps; use Automattic\WooCommerce\Blueprint\Exporters\ExportInstallThemeSteps; use Automattic\WooCommerce\Blueprint\ExportSchema; use Automattic\WooCommerce\Blueprint\ImportStep; use Automattic\WooCommerce\Internal\ComingSoon\ComingSoonHelper; use WP_Error; /** * Class RestApi * * This class handles the REST API endpoints for importing and exporting WooCommerce Blueprints. * * @package Automattic\WooCommerce\Admin\Features\Blueprint */ class RestApi { /** * Maximum allowed file size in bytes (50MB) */ const MAX_FILE_SIZE = 52428800; // 50 * 1024 * 1024 /** * Endpoint namespace. * * @var string */ protected $namespace = 'wc-admin'; /** * ComingSoonHelper instance. * * @var ComingSoonHelper */ protected $coming_soon_helper; /** * Constructor. */ public function __construct() { $this->coming_soon_helper = new ComingSoonHelper(); } /** * Get maximum allowed file size for blueprint uploads. * * @return int Maximum file size in bytes */ protected function get_max_file_size() { /** * Filters the maximum allowed file size for blueprint uploads. * * @since 9.3.0 * @param int $max_size Maximum file size in bytes. */ return apply_filters( 'woocommerce_blueprint_upload_max_file_size', self::MAX_FILE_SIZE ); } /** * Register routes. * * @since 9.3.0 */ public function register_routes() { register_rest_route( $this->namespace, '/blueprint/export', array( array( 'methods' => \WP_REST_Server::CREATABLE, 'callback' => array( $this, 'export' ), 'permission_callback' => array( $this, 'check_export_permission' ), 'args' => array( 'steps' => array( 'description' => __( 'A list of plugins to install', 'woocommerce' ), 'type' => 'object', 'properties' => array( 'settings' => array( 'type' => 'array', 'items' => array( 'type' => 'string', ), ), 'plugins' => array( 'type' => 'array', 'items' => array( 'type' => 'string', ), ), 'themes' => array( 'type' => 'array', 'items' => array( 'type' => 'string', ), ), ), 'default' => array(), 'required' => true, ), ), ), ) ); register_rest_route( $this->namespace, '/blueprint/import-step', array( array( 'methods' => \WP_REST_Server::CREATABLE, 'callback' => array( $this, 'import_step' ), 'permission_callback' => array( $this, 'check_import_permission' ), 'args' => array( 'step_definition' => array( 'description' => __( 'The step definition to import', 'woocommerce' ), 'type' => 'object', 'required' => true, ), ), ), 'schema' => array( $this, 'get_import_step_response_schema' ), ) ); register_rest_route( $this->namespace, '/blueprint/import-allowed', array( array( 'methods' => \WP_REST_Server::READABLE, 'callback' => array( $this, 'get_import_allowed' ), 'permission_callback' => function () { return current_user_can( 'manage_woocommerce' ); }, ), 'schema' => array( $this, 'get_import_allowed_schema' ), ) ); } /** * General permission check for export requests. * * @return bool|\WP_Error */ public function check_export_permission() { if ( ! current_user_can( 'manage_woocommerce' ) ) { return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot export WooCommerce Blueprints.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * General permission check for import requests. * * @return bool|\WP_Error */ public function check_import_permission() { if ( ! current_user_can( 'manage_woocommerce' ) || ! current_user_can( 'manage_options' ) ) { return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot import WooCommerce Blueprints.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Handle the export request. * * @param \WP_REST_Request $request The request object. * @return \WP_HTTP_Response The response object. */ public function export( $request ) { $payload = $request->get_param( 'steps' ); $steps = $this->steps_payload_to_blueprint_steps( $payload ); $exporter = new ExportSchema(); if ( isset( $payload['plugins'] ) ) { $exporter->on_before_export( 'installPlugin', function ( ExportInstallPluginSteps $exporter ) use ( $payload ) { $exporter->filter( function ( array $plugins ) use ( $payload ) { return array_intersect_key( $plugins, array_flip( $payload['plugins'] ) ); } ); } ); } if ( isset( $payload['themes'] ) ) { $exporter->on_before_export( 'installTheme', function ( ExportInstallThemeSteps $exporter ) use ( $payload ) { $exporter->filter( function ( array $plugins ) use ( $payload ) { return array_intersect_key( $plugins, array_flip( $payload['themes'] ) ); } ); } ); } $data = $exporter->export( $steps ); if ( is_wp_error( $data ) ) { return new \WP_REST_Response( $data, 400 ); } return new \WP_HTTP_Response( array( 'data' => $data, 'type' => 'json', ) ); } /** * Convert step list from the frontend to the backend format. * * From: * { * "settings": ["setWCSettings", "setWCShippingZones", "setWCShippingMethods", "setWCShippingRates"], * "plugins": ["akismet/akismet.php], * "themes": ["approach], * } * * To: * * ["setWCSettings", "setWCShippingZones", "setWCShippingMethods", "setWCShippingRates", "installPlugin", "installTheme"] * * @param array $steps steps payload from the frontend. * * @return array */ private function steps_payload_to_blueprint_steps( $steps ) { $blueprint_steps = array(); if ( isset( $steps['settings'] ) && count( $steps['settings'] ) > 0 ) { $blueprint_steps = array_merge( $blueprint_steps, $steps['settings'] ); } if ( isset( $steps['plugins'] ) && count( $steps['plugins'] ) > 0 ) { $blueprint_steps[] = 'installPlugin'; } if ( isset( $steps['themes'] ) && count( $steps['themes'] ) > 0 ) { $blueprint_steps[] = 'installTheme'; } return $blueprint_steps; } /** * Import a single step. * * @param \WP_REST_Request $request The request object. * * @return \WP_REST_Response|array */ public function import_step( \WP_REST_Request $request ) { $session_token = $request->get_header( 'X-Blueprint-Import-Session' ); // If no session token, this is the first step: generate and store a new token. if ( ! $session_token ) { $session_token = function_exists( 'wp_generate_uuid4' ) ? wp_generate_uuid4() : uniqid( 'bp_', true ); } if ( ! $this->can_import_blueprint( $session_token ) ) { return array( 'success' => false, 'messages' => array( array( 'message' => __( 'Blueprint imports are disabled', 'woocommerce' ), 'type' => 'error', ), ), ); } if ( false === get_transient( 'blueprint_import_session_' . $session_token ) ) { set_transient( 'blueprint_import_session_' . $session_token, true, 10 * MINUTE_IN_SECONDS ); } // Get the raw body size. $body_size = strlen( $request->get_body() ); if ( $body_size > $this->get_max_file_size() ) { return array( 'success' => false, 'messages' => array( array( 'message' => sprintf( // Translators: %s is the maximum file size in megabytes. __( 'Blueprint step definition size exceeds maximum limit of %s MB', 'woocommerce' ), ( $this->get_max_file_size() / ( 1024 * 1024 ) ) ), 'type' => 'error', ), ), ); } // Make sure we're dealing with object. $step_definition = json_decode( wp_json_encode( $request->get_param( 'step_definition' ) ) ); $step_importer = new ImportStep( $step_definition ); $result = $step_importer->import(); $response = new \WP_REST_Response( array( 'success' => $result->is_success(), 'messages' => $result->get_messages(), ) ); $response->header( 'X-Blueprint-Import-Session', $session_token ); return $response; } /** * Check if blueprint imports are allowed based on site status, configuration, and session token. * * @param string|null $session_token Optional session token for import session. * @return bool Returns true if imports are allowed, false otherwise. */ private function can_import_blueprint( $session_token = null ) { // Allow import if a valid session token is present so when a site is turned into live during the import process, the import can continue. if ( $session_token && get_transient( 'blueprint_import_session_' . $session_token ) ) { return true; } // Check if override constant is defined and true. if ( defined( 'ALLOW_BLUEPRINT_IMPORT_IN_LIVE_MODE' ) && ALLOW_BLUEPRINT_IMPORT_IN_LIVE_MODE ) { return true; } // Only allow imports in coming soon mode. if ( $this->coming_soon_helper->is_site_live() ) { return false; } return true; } /** * Get whether blueprint imports are allowed. * * @return \WP_REST_Response */ public function get_import_allowed() { $can_import = $this->can_import_blueprint(); return rest_ensure_response( array( 'import_allowed' => $can_import, ) ); } /** * Get the schema for the import-allowed endpoint. * * @return array */ public function get_import_allowed_schema() { return array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'blueprint-import-allowed', 'type' => 'object', 'properties' => array( 'import_allowed' => array( 'description' => __( 'Whether blueprint imports are currently allowed', 'woocommerce' ), 'type' => 'boolean', 'context' => array( 'view' ), 'readonly' => true, ), ), ); } /** * Get the schema for the import-step endpoint. * * @return array */ public function get_import_step_response_schema() { $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'import-step', 'type' => 'object', 'properties' => array( 'success' => array( 'type' => 'boolean', ), 'messages' => array( 'type' => 'array', 'items' => array( 'type' => 'object', 'properties' => array( 'message' => array( 'type' => 'string', ), 'type' => array( 'type' => 'string', ), ), 'required' => array( 'message', 'type' ), ), ), ), 'required' => array( 'success', 'messages' ), ); return $schema; } } Blueprint/Init.php 0000777 00000026224 15253027022 0010137 0 ustar 00 <?php declare( strict_types = 1 ); namespace Automattic\WooCommerce\Admin\Features\Blueprint; use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCPaymentGateways; use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCSettingsAccount; use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCSettingsAdvanced; use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCSettingsEmails; use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCSettingsGeneral; use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCSettingsTax; use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCSettingsIntegrations; use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCSettingsProducts; use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCSettingsSiteVisibility; use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCSettingsShipping; use Automattic\WooCommerce\Admin\PageController; use Automattic\WooCommerce\Blueprint\Exporters\HasAlias; use Automattic\WooCommerce\Blueprint\Exporters\StepExporter; use Automattic\WooCommerce\Blueprint\UseWPFunctions; /** * Class Init * * This class initializes the Blueprint feature for WooCommerce. */ class Init { use UseWPFunctions; const INSTALLED_WP_ORG_PLUGINS_TRANSIENT = 'woocommerce_blueprint_installed_wp_org_plugins'; const INSTALLED_WP_ORG_THEMES_TRANSIENT = 'woocommerce_blueprint_installed_wp_org_themes'; /** * Array of initialized exporters. * * @var StepExporter[] */ private array $initialized_exporters = array(); /** * Init constructor. */ public function __construct() { add_action( 'rest_api_init', array( $this, 'init_rest_api' ) ); add_filter( 'woocommerce_admin_shared_settings', array( $this, 'add_js_vars' ) ); add_filter( 'wooblueprint_export_landingpage', function () { return '/wp-admin/admin.php?page=wc-admin'; } ); add_filter( 'wooblueprint_exporters', array( $this, 'add_woo_exporters' ) ); add_action( 'upgrader_process_complete', array( $this, 'clear_installed_wp_org_plugins_transient' ), 10, 2 ); add_action( 'deleted_plugin', array( $this, 'clear_installed_wp_org_plugins_transient' ), 10, 2 ); add_action( 'upgrader_process_complete', array( $this, 'clear_installed_wp_org_themes_transient' ), 10, 2 ); add_action( 'switch_theme', array( $this, 'clear_installed_wp_org_themes_transient' ) ); add_action( 'deleted_theme', array( $this, 'clear_installed_wp_org_themes_transient' ) ); } /** * Register REST API routes. * * @return void */ public function init_rest_api() { ( new RestApi() )->register_routes(); } /** * Return Woo Exporter classnames. * * @return StepExporter[] */ public function get_woo_exporters() { $classnames = array( ExportWCSettingsGeneral::class, ExportWCSettingsProducts::class, ExportWCSettingsTax::class, ExportWCSettingsShipping::class, ExportWCPaymentGateways::class, ExportWCSettingsAccount::class, ExportWCSettingsEmails::class, ExportWCSettingsIntegrations::class, ExportWCSettingsSiteVisibility::class, ExportWCSettingsAdvanced::class, ); $exporters = array(); foreach ( $classnames as $classname ) { $exporters[ $classname ] = $this->initialized_exporters[ $classname ] ?? new $classname(); $this->initialized_exporters[ $classname ] = $exporters[ $classname ]; } return array_values( $exporters ); } /** * Add Woo Specific Exporters. * * @param StepExporter[] $exporters Array of step exporters. * * @return StepExporter[] */ public function add_woo_exporters( array $exporters ) { return array_merge( $exporters, $this->get_woo_exporters() ); } /** * Get plugins for export group. * * @return array|array[] $plugins */ public function get_plugins_for_export_group() { $plugins = $this->get_installed_wp_org_plugins(); // Get active plugins from WordPress options and transform plugins array into export format. $active_plugins = $this->wp_get_option( 'active_plugins', array() ); $plugins = array_map( function ( $key, $plugin ) use ( $active_plugins ) { return array( 'id' => $key, 'label' => $plugin['Name'], 'checked' => in_array( $key, $active_plugins, true ), ); }, array_keys( $plugins ), $plugins ); usort( $plugins, function ( $a, $b ) { return $b['checked'] <=> $a['checked']; } ); return $plugins; } /** * Clear the installed WordPress.org plugins transient. */ public function clear_installed_wp_org_plugins_transient() { delete_transient( self::INSTALLED_WP_ORG_PLUGINS_TRANSIENT ); } /** * Clear the installed WordPress.org themes transient. */ public function clear_installed_wp_org_themes_transient() { delete_transient( self::INSTALLED_WP_ORG_THEMES_TRANSIENT ); } /** * Get themes for export group. * * @return array $themes */ public function get_themes_for_export_group() { $themes = $this->get_installed_wp_org_themes(); $active_theme = $this->wp_get_theme(); $themes = array_map( function ( $theme ) use ( $active_theme ) { return array( 'id' => $theme->get_stylesheet(), 'label' => $theme->get( 'Name' ), 'checked' => $theme->get_stylesheet() === $active_theme->get_stylesheet(), ); }, $themes ); usort( $themes, function ( $a, $b ) { return $b['checked'] <=> $a['checked']; } ); return array_values( $themes ); } /** * Return step groups for JS. * * This is used to populate exportable items on the blueprint settings page. * * @return array */ public function get_step_groups_for_js() { return array( array( 'id' => 'settings', 'description' => __( 'Includes all the items featured in WooCommerce | Settings.', 'woocommerce' ), 'label' => __( 'WooCommerce Settings', 'woocommerce' ), 'icon' => 'settings', 'items' => array_map( function ( $exporter ) { return array( 'id' => $exporter instanceof HasAlias ? $exporter->get_alias() : $exporter->get_step_name(), 'label' => $exporter->get_label(), 'description' => $exporter->get_description(), 'checked' => true, ); }, $this->get_woo_exporters() ), ), array( 'id' => 'plugins', 'description' => __( 'Includes all the installed plugins.', 'woocommerce' ), 'label' => __( 'Plugins', 'woocommerce' ), 'icon' => 'plugins', 'items' => $this->get_plugins_for_export_group(), ), array( 'id' => 'themes', 'description' => __( 'Includes all the installed themes.', 'woocommerce' ), 'label' => __( 'Themes', 'woocommerce' ), 'icon' => 'layout', 'items' => $this->get_themes_for_export_group(), ), ); } /** * Add shared JS vars. * * @param array $settings shared settings. * * @return mixed */ public function add_js_vars( $settings ) { if ( ! is_admin() ) { return $settings; } if ( 'woocommerce_page_wc-settings-advanced-blueprint' === PageController::get_instance()->get_current_screen_id() ) { // Used on the settings page. // wcSettings.admin.blueprint_step_groups. $settings['blueprint_step_groups'] = $this->get_step_groups_for_js(); $settings['blueprint_max_step_size_bytes'] = RestApi::MAX_FILE_SIZE; } return $settings; } /** * Get all installed WordPress.org plugins. * * @return array */ private function get_installed_wp_org_plugins() { // Try to get cached plugin list. $wp_org_plugins = get_transient( self::INSTALLED_WP_ORG_PLUGINS_TRANSIENT ); if ( is_array( $wp_org_plugins ) ) { return $wp_org_plugins; } // Get all installed plugins. $all_plugins = $this->wp_get_plugins(); $plugin_slugs = array(); // Build a map of plugin file => slug. foreach ( $all_plugins as $key => $plugin ) { $slug = dirname( $key ); /** * Apply the WP Core "wp_plugin_dependencies_slug" filter to get the correct plugin slug. */ $slug = apply_filters( 'wp_plugin_dependencies_slug', $slug ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingSinceComment $plugin_slugs[] = $slug; $all_plugins[ $key ]['slug'] = $slug; } $api_response = $this->wp_plugins_api( 'plugin_information', array( 'fields' => array( 'short_description' => false, 'sections' => false, 'description' => false, 'tested' => false, 'requires' => false, 'rating' => false, 'ratings' => false, 'downloaded' => false, 'downloadlink' => false, 'last_updated' => false, 'added' => false, 'tags' => false, 'compatibility' => false, 'homepage' => false, 'versions' => false, 'donate_link' => false, 'reviews' => false, 'banners' => false, 'icons' => false, 'active_installs' => false, ), 'slugs' => $plugin_slugs, ) ); // If API fails, return all plugins. if ( is_wp_error( $api_response ) ) { return $all_plugins; } // Filter plugins: only keep those with a valid API response (no 'error' for their slug). $wp_org_plugins = array_filter( $all_plugins, function ( $plugin ) use ( $api_response ) { $slug = $plugin['slug']; return isset( $api_response->{$slug} ) && ! isset( $api_response->{$slug}['error'] ); } ); set_transient( self::INSTALLED_WP_ORG_PLUGINS_TRANSIENT, $wp_org_plugins ); return $wp_org_plugins; } /** * Get all installed WordPress.org themes. * * @return array */ private function get_installed_wp_org_themes() { // Try to get cached theme list. $wp_org_themes = get_transient( self::INSTALLED_WP_ORG_THEMES_TRANSIENT ); if ( is_array( $wp_org_themes ) ) { return $wp_org_themes; } // Get all installed themes. $all_themes = $this->wp_get_themes(); $theme_slugs = array(); // Build an array of installed theme slugs. foreach ( $all_themes as $key => $theme ) { if ( is_string( $key ) ) { $theme_slugs[] = strtolower( $key ); } } $api_response = $this->wp_themes_api( 'theme_information', array( 'fields' => array( 'downloadlink' => true, 'sections' => false, 'description' => false, 'rating' => false, 'ratings' => false, 'downloaded' => false, 'last_updated' => false, 'tags' => false, 'homepage' => false, 'screenshots' => false, 'screenshot_url' => false, 'parent' => false, 'versions' => false, 'extended_author' => false, ), 'slugs' => $theme_slugs, ) ); // If the API fails, return all installed themes. if ( is_wp_error( $api_response ) ) { return $all_themes; } $wp_org_themes = array_filter( $all_themes, function ( $theme ) use ( $api_response ) { $slug = $theme->get_stylesheet(); return isset( $api_response->{$slug}['download_link'] ); } ); set_transient( self::INSTALLED_WP_ORG_THEMES_TRANSIENT, $wp_org_themes ); return $wp_org_themes; } } Navigation/RemovedDeprecated.php 0000777 00000002446 15253027022 0012751 0 ustar 00 <?php declare(strict_types=1); namespace Automattic\WooCommerce\Admin\Features\Navigation; use WC_Tracks; /** * Handle calls to deprecated methods. */ class RemovedDeprecated { /** * Handle deprecated method calls. * * @param string $name The name of the deprecated method. */ private static function handle_deprecated_method_call( $name ) { $logger = wc_get_logger(); if ( $logger ) { $logger->warning( "The WooCommerce Admin Navigation feature and its classes (Screen, Menu, CoreMenu) are deprecated since 9.3 with no alternative. Please remove the call to $name." ); } if ( class_exists( 'WC_Tracks' ) ) { WC_Tracks::record_event( 'deprecated_navigation_method_called' ); } } /** * Handle calls to deprecated methods. * * @param string $name The name of the deprecated method. * @param array $arguments The arguments passed to the deprecated method. */ public function __call( $name, $arguments ) { self::handle_deprecated_method_call( $name ); } /** * Handle static calls to deprecated methods. * * @param string $name The name of the deprecated method. * @param array $arguments The arguments passed to the deprecated method. */ public static function __callStatic( $name, $arguments ) { self::handle_deprecated_method_call( $name ); } } AsyncProductEditorCategoryField/Init.php 0000777 00000004641 15253027022 0014421 0 ustar 00 <?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' ); } } ProductDataViews/Init.php 0000777 00000006766 15253027022 0011434 0 ustar 00 <?php /** * WooCommerce Product Data Views */ declare( strict_types = 1 ); namespace Automattic\WooCommerce\Admin\Features\ProductDataViews; use Automattic\Jetpack\Constants; use Automattic\WooCommerce\Blocks\Utils\Utils; use Automattic\WooCommerce\Internal\Admin\WCAdminAssets; /** * Loads assets related to the product block editor. */ class Init { /** * Constructor */ public function __construct() { add_action( 'admin_menu', array( $this, 'woocommerce_add_new_products_dashboard' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_styles' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); if ( $this->is_product_data_view_page() ) { add_filter( 'admin_body_class', static function ( $classes ) { return "$classes"; } ); } } /** * Returns true if we are on a JS powered admin page. */ public static function is_product_data_view_page() { // phpcs:disable WordPress.Security.NonceVerification return isset( $_GET['page'] ) && 'woocommerce-products-dashboard' === $_GET['page']; // phpcs:enable WordPress.Security.NonceVerification } /** * Enqueue styles needed for the rich text editor. */ public function enqueue_styles() { if ( ! $this->is_product_data_view_page() ) { return; } wp_enqueue_style( 'wc-product-editor' ); } /** * Enqueue scripts needed for the product form block editor. */ public function enqueue_scripts() { if ( ! $this->is_product_data_view_page() ) { return; } $script_handle = 'wc-admin-edit-product'; wp_register_script( $script_handle, '', array( 'wp-blocks' ), '0.1.0', true ); wp_enqueue_script( $script_handle ); 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' ); } /** * Replaces the default posts menu item with the new posts dashboard. */ public function woocommerce_add_new_products_dashboard() { $gutenberg_experiments = get_option( 'gutenberg-experiments' ); if ( ! $gutenberg_experiments ) { return; } $ptype_obj = get_post_type_object( 'product' ); add_submenu_page( 'edit.php?post_type=product', $ptype_obj->labels->name, esc_html__( 'All Products ( new )', 'woocommerce' ), 'manage_woocommerce', 'woocommerce-products-dashboard', array( $this, 'woocommerce_products_dashboard' ), 1 ); } /** * Renders the new posts dashboard page. */ public function woocommerce_products_dashboard() { $suffix = Constants::is_true( 'SCRIPT_DEBUG' ) ? '' : '.min'; $version = Constants::get_constant( 'WC_VERSION' ); if ( function_exists( 'gutenberg_url' ) ) { // phpcs:disable WordPress.WP.EnqueuedResourceParameters.MissingVersion wp_register_style( 'wp-gutenberg-posts-dashboard', gutenberg_url( 'build/edit-site/posts.css', __FILE__ ), array( 'wp-components' ), ); // phpcs:enable WordPress.WP.EnqueuedResourceParameters.MissingVersion wp_enqueue_style( 'wp-gutenberg-posts-dashboard' ); } WCAdminAssets::get_instance(); wp_enqueue_script( 'wc-admin-product-editor', WC()->plugin_url() . '/assets/js/admin/product-editor' . $suffix . '.js', array( 'wc-product-editor' ), $version, false ); wp_add_inline_script( 'wp-edit-site', 'window.wc.productEditor.initializeProductsDashboard( "woocommerce-products-dashboard" );', 'after' ); wp_enqueue_script( 'wp-edit-site' ); echo '<div id="woocommerce-products-dashboard"></div>'; } } OnboardingTasks/TaskLists.php 0000777 00000025540 15253027022 0012301 0 ustar 00 <?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\Tasks\ReviewShippingOptions; use Automattic\WooCommerce\Utilities\FeaturesUtil; /** * 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', '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( 'StoreDetails', 'Products', 'Payments', 'CustomizeStore', 'Tax', 'Shipping', 'LaunchYourStore', ); if ( Features::is_enabled( 'core-profiler' ) ) { $key = array_search( 'StoreDetails', $tasks, true ); if ( false !== $key ) { unset( $tasks[ $key ] ); } } 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( 'Marketing', 'ExtendStore', '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 ( $task_list ) use ( $ids ) { return in_array( $task_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 * * This is not updated immediately when a task is completed, but rather when task is marked as complete in the database to reduce performance impact. * * @return int|null */ public static function setup_tasks_remaining() { $setup_list = self::get_list( 'setup' ); if ( ! $setup_list || $setup_list->is_hidden() || $setup_list->has_previously_completed() ) { return; } $viewable_tasks = $setup_list->get_viewable_tasks(); $completed_tasks = get_option( Task::COMPLETED_OPTION, array() ); if ( ! is_array( $completed_tasks ) ) { $completed_tasks = array(); } return count( array_filter( $viewable_tasks, function ( $task ) use ( $completed_tasks ) { return ! in_array( $task->get_id(), $completed_tasks, true ); } ) ); } /** * 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'] = self::all_hidden() ? array() : array_keys( self::get_visible() ); $settings['completedTaskListIds'] = get_option( TaskList::COMPLETED_OPTION, array() ); return $settings; } /** * Check if all task lists are hidden. * * @return bool */ public static function all_hidden() { $hidden_lists = get_option( TaskList::HIDDEN_OPTION, array() ); return count( $hidden_lists ) === count( self::get_lists() ); } } OnboardingTasks/DeprecatedExtendedTask.php 0000777 00000006322 15253027022 0014721 0 ustar 00 <?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; } } OnboardingTasks/TaskTraits.php 0000777 00000001713 15253027022 0012445 0 ustar 00 <?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; } } OnboardingTasks/Task.php 0000777 00000031414 15253027022 0011257 0 ustar 00 <?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 millisecond 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 and is complete. * * @return void */ 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 is in progress. * * @return bool */ public function is_in_progress() { return false; } /** * The task in progress label. * * @return string */ public function in_progress_label() { return esc_html__( 'In progress', 'woocommerce' ); } /** * If a task is always accessible, relevant for when a task list is hidden but a task can still be viewed. * * @return bool */ public function is_always_accessible() { return false; } /** * 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() { $is_complete = $this->is_complete(); if ( $is_complete ) { $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' => $is_complete, 'isInProgress' => $this->is_in_progress(), 'inProgressLabel' => $this->in_progress_label(), '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; } } OnboardingTasks/Tasks/ExtendStore.php 0000777 00000002156 15253027022 0013707 0 ustar 00 <?php namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks; use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task; /** * ExtendStore Task */ class ExtendStore extends Task { /** * ID. * * @return string */ public function get_id() { return 'extend-store'; } /** * Title. * * @return string */ public function get_title() { return __( 'Enhance your store with extensions', 'woocommerce' ); } /** * Content. * * @return string */ public function get_content() { return ''; } /** * Additional info. * * @return string */ public function get_additional_info() { return ''; } /** * Time. * * @return string */ public function get_time() { return ''; } /** * Task completion. * * @return bool */ public function is_complete() { return $this->is_visited(); } /** * Always dismissable. * * @return bool */ public function is_dismissable() { return false; } /** * Action URL. * * @return string */ public function get_action_url() { return admin_url( 'admin.php?page=wc-admin&path=/extensions' ); } } OnboardingTasks/Tasks/StoreCreation.php 0000777 00000002044 15253027022 0014220 0 ustar 00 <?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; } } OnboardingTasks/Tasks/Appearance.php 0000777 00000002513 15253027022 0013477 0 ustar 00 <?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' ); } } OnboardingTasks/Tasks/TourInAppMarketplace.php 0000777 00000002277 15253027022 0015501 0 ustar 00 <?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; } } OnboardingTasks/Tasks/Payments.php 0000777 00000032172 15253027022 0013244 0 ustar 00 <?php declare( strict_types=1 ); namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks; use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task; use Automattic\WooCommerce\Internal\Admin\Settings\Payments as SettingsPaymentsService; use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\DefaultPaymentGateways; use Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders; use Automattic\WooCommerce\Internal\Admin\Suggestions\PaymentsExtensionSuggestions; use WC_Gateway_BACS; use WC_Gateway_Cheque; use WC_Gateway_COD; /** * 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 __( 'Set up payments', '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 __( '5 minutes', 'woocommerce' ); } /** * Task completion. * * @return bool */ public function is_complete() { if ( null === $this->is_complete_result ) { if ( $this->is_woopayments_active() ) { // If WooPayments is active, check if it is fully onboarded with a live account. $this->is_complete_result = $this->is_woopayments_onboarded() && ! $this->has_woopayments_test_account(); } else { // If WooPayments is not active, check if there are any enabled gateways. $this->is_complete_result = self::has_gateways(); } } return $this->is_complete_result; } /** * Task visibility. * * @return bool */ public function can_view() { // The task is always visible. return true; } /** * Check if the store has any enabled gateways. * * @return bool */ public static function has_gateways() { $gateways = WC()->payment_gateways()->payment_gateways; $enabled_gateways = array_filter( $gateways, function ( $gateway ) { return 'yes' === $gateway->enabled; } ); return ! empty( $enabled_gateways ); } /** * Check if the task is in progress. * * @return bool */ public function is_in_progress() { // If the task is already complete, it's not in progress. if ( $this->is_complete() ) { return false; } return ( $this->has_woopayments_live_account_in_progress() || $this->has_woopayments_test_account() ); } /** * The task in progress label. * * @return string */ public function in_progress_label() { // If WooPayments live account onboarding is in progress, show "Action needed" label. if ( $this->has_woopayments_live_account_in_progress() ) { return esc_html__( 'Action needed', 'woocommerce' ); } return esc_html__( 'Test account', 'woocommerce' ); } /** * The task action URL. * * Empty string means the JS logic will handle the task linking. * * @return string */ public function get_action_url() { // Link to the Payments settings page. return admin_url( 'admin.php?page=wc-settings&tab=checkout&from=' . SettingsPaymentsService::FROM_PAYMENTS_TASK ); } /** * Additional data to be passed to the front-end JS logic. * * Primarily used to inform the behavior of the Payments task in the LYS context. * * @return array */ public function get_additional_data() { return array( 'wooPaymentsIsActive' => $this->is_woopayments_active(), 'wooPaymentsIsInstalled' => $this->is_woopayments_installed(), 'wooPaymentsSettingsCountryIsSupported' => $this->is_woopayments_supported_country( $this->get_payments_settings_country() ), 'wooPaymentsIsOnboarded' => $this->is_woopayments_onboarded(), 'wooPaymentsHasTestAccount' => $this->has_woopayments_test_account(), 'wooPaymentsHasOtherProvidersEnabled' => $this->has_providers_enabled_other_than_woopayments(), 'wooPaymentsHasOtherProvidersNeedSetup' => $this->has_providers_needing_setup_other_than_woopayments(), 'wooPaymentsHasOnlineGatewaysEnabled' => $this->has_online_gateways(), ); } /** * Check if the WooPayments plugin is active. * * @return bool */ private function is_woopayments_active(): bool { return class_exists( '\WC_Payments' ); } /** * Check if the WooPayments plugin is installed. * * @return bool */ private function is_woopayments_installed(): bool { if ( $this->is_woopayments_active() ) { // If it is active, it is also installed. return true; } $woopayments_suggestion = $this->get_woopayments_suggestion(); // We should have the WooPayments suggestion, but if not, return false. if ( ! $woopayments_suggestion ) { return false; } // Check if the suggestion has its plugin installed. if ( ! empty( $woopayments_suggestion['plugin']['status'] ) && PaymentsProviders::EXTENSION_INSTALLED === $woopayments_suggestion['plugin']['status'] ) { return true; } return false; } /** * Check if WooPayments is completely onboarded. * * @return bool */ private function is_woopayments_onboarded(): bool { if ( ! $this->is_woopayments_active() ) { return false; } $woopayments_provider = $this->get_woopayments_provider(); // We should have the WooPayments provider, but if not, return false. if ( ! $woopayments_provider ) { return false; } // Check the provider's state to determine if it is onboarded. if ( ! empty( $woopayments_provider['onboarding']['state']['completed'] ) ) { return true; } return false; } /** * Check if WooPayments has a live account onboarding in progress. * * @return bool */ private function has_woopayments_live_account_in_progress() { if ( $this->is_woopayments_onboarded() ) { return false; } $woopayments_provider = $this->get_woopayments_provider(); // We should have the WooPayments provider, but if not, return false. if ( ! $woopayments_provider ) { return false; } // If we have a test account, we are not in live account onboarding. if ( $this->has_woopayments_test_account() ) { return false; } // Check the provider's state to determine if a live account onboarding is started. if ( ! empty( $woopayments_provider['onboarding']['state']['started'] ) ) { return true; } return false; } /** * Check if WooPayments is onboarded and has a test [drive] account. * * @return bool */ private function has_woopayments_test_account(): bool { if ( ! $this->is_woopayments_onboarded() ) { return false; } $woopayments_provider = $this->get_woopayments_provider(); // We should have the WooPayments provider, but if not, return false. if ( ! $woopayments_provider ) { return false; } // Check the provider's state to determine if a test [drive] account is in use. if ( ! empty( $woopayments_provider['onboarding']['state']['test_drive_account'] ) ) { return true; } return false; } /** * Check if the store is in a WooPayments-supported geography. * * @param string $country_code Country code to check. If not provided, uses store base country. * * @return bool Whether the country is supported by WooPayments. */ private function is_woopayments_supported_country( string $country_code ): bool { if ( class_exists( '\WC_Payments_Utils' ) && is_callable( array( '\WC_Payments_Utils', 'supported_countries' ) ) ) { $supported_countries = array_keys( \WC_Payments_Utils::supported_countries() ); return in_array( $country_code, $supported_countries, true ); } // WooPayments is not installed and active, use core's list of supported countries. $supported_countries = DefaultPaymentGateways::get_wcpay_countries(); return in_array( $country_code, $supported_countries, true ); } /** * Check if the store has any enabled providers other than WooPayments. * * @return bool */ public function has_providers_enabled_other_than_woopayments(): bool { $providers = $this->get_payments_providers(); foreach ( $providers as $provider ) { // Check if the provider is enabled and is not WooPayments. if ( ! empty( $provider['state']['enabled'] ) && ! empty( $provider['id'] ) && 'woocommerce_payments' !== $provider['id'] ) { return true; } } return false; } /** * Check if any non-WooPayments providers need setup. * * @return bool */ private function has_providers_needing_setup_other_than_woopayments(): bool { $providers = $this->get_payments_providers(); foreach ( $providers as $provider ) { // Check if the provider needs setup and is not WooPayments. if ( ! empty( $provider['state']['needs_setup'] ) && ! empty( $provider['id'] ) && 'woocommerce_payments' !== $provider['id'] ) { return true; } } return false; } /** * Check if the store has any enabled online gateways. * * @return bool */ private function has_online_gateways(): bool { $providers = $this->get_payments_providers(); foreach ( $providers as $provider ) { // Check if the provider is enabled and is not an offline payment method. if ( ! empty( $provider['state']['enabled'] ) && ! empty( $provider['id'] ) && ! in_array( $provider['id'], array( WC_Gateway_BACS::ID, WC_Gateway_Cheque::ID, WC_Gateway_COD::ID ), true ) ) { return true; } } return false; } /** * Get the store's business registration country/location as it is used on the Payments Settings page. * * @return string The business registration country/location code. */ private function get_payments_settings_country(): string { try { /** * The Payments Settings [page] service. * * @var SettingsPaymentsService $settings_payments_service */ $settings_payments_service = wc_get_container()->get( SettingsPaymentsService::class ); return $settings_payments_service->get_country(); } catch ( \Throwable $e ) { // In case of any error, return the WooCommerce base country. return WC()->countries->get_base_country(); } } /** * Get the list of payments providers as it is used on the Payments Settings page. * * The list can include payments extension suggestions, the same as on the Payments Settings page. * * @return array The list of payments providers. */ private function get_payments_providers(): array { try { /** * The Payments Settings [page] service. * * @var SettingsPaymentsService $settings_payments_service */ $settings_payments_service = wc_get_container()->get( SettingsPaymentsService::class ); // Get the raw list of payment providers, including suggestions, but remove shells. // This way we prevent shell gateways that are (wrongly) reported as enabled from affecting the task completion. return $settings_payments_service->get_payment_providers( $settings_payments_service->get_country(), false, true ); } catch ( \Throwable $e ) { // In case of any error, return an empty array. return array(); } } /** * Get the list of payments extension suggestions as it is used on the Payments Settings page. * * @return array The list of payments extension suggestions. */ private function get_payments_extension_suggestions(): array { try { /** * The Payments Settings [page] service. * * @var SettingsPaymentsService $settings_payments_service */ $settings_payments_service = wc_get_container()->get( SettingsPaymentsService::class ); return $settings_payments_service->get_payment_extension_suggestions( $settings_payments_service->get_country() ); } catch ( \Throwable $e ) { // In case of any error, return an empty array. return array(); } } /** * Get the WooPayments provider details from the list used on the Payments Settings page. * * @return array|null The WooPayments provider details or null if not found. */ private function get_woopayments_provider(): ?array { $providers = $this->get_payments_providers(); foreach ( $providers as $provider ) { if ( ! empty( $provider['id'] ) && PaymentsProviders\WooPayments\WooPaymentsService::GATEWAY_ID === $provider['id'] ) { return $provider; } } return null; } /** * Get the WooPayments payments extension suggestion details from the lists used on the Payments Settings page. * * @return array|null The WooPayments suggestion details or null if not found. */ private function get_woopayments_suggestion(): ?array { // First, check the payments providers list. $providers = $this->get_payments_providers(); foreach ( $providers as $provider ) { if ( ! empty( $provider['_type'] ) && PaymentsProviders::TYPE_SUGGESTION === $provider['_type'] && ! empty( $provider['_suggestion_id'] ) && PaymentsExtensionSuggestions::WOOPAYMENTS === $provider['_suggestion_id'] ) { return $provider; } } // If not found in the main list, check the payments extension suggestions list. $suggestions = $this->get_payments_extension_suggestions(); foreach ( $suggestions as $suggestion ) { if ( ! empty( $suggestion['id'] ) && PaymentsExtensionSuggestions::WOOPAYMENTS === $suggestion['id'] ) { return $suggestion; } } return null; } } OnboardingTasks/Tasks/WooCommercePayments.php 0000777 00000020264 15253027022 0015403 0 ustar 00 <?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\Admin\Features\PaymentGatewaySuggestions\DefaultPaymentGateways; use Automattic\WooCommerce\Internal\Admin\WcPayWelcomePage; use WC_Gateway_BACS; use WC_Gateway_Cheque; use WC_Gateway_COD; /** * WooCommercePayments Task. * * @deprecated 9.9.0 The WooPayments onboarding task is deprecated and will be removed in a future version of WooCommerce. */ 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() { /* translators: %s: Payment provider name. */ return sprintf( __( 'Get paid with %s', 'woocommerce' ), 'WooPayments' ); } /** * 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' ); } /** * Additional data. * * @return mixed */ public function get_additional_data() { /** * Filter WooPayments onboarding task additional data. * * @since 9.4.0 * * @param ?array $additional_data The task additional data. */ return apply_filters( 'woocommerce_admin_woopayments_onboarding_task_additional_data', null ); } /** * 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 task is complete if there are other ecommerce gateways enabled (offline payment methods are excluded), // or if WooPayments is active and has a connected, fully onboarded account. $this->is_complete_result = self::has_other_ecommerce_gateways() || ( self::is_connected() && ! self::is_account_partially_onboarded() ); } return $this->is_complete_result; } /** * Task visibility. * * @return bool */ public function can_view() { return 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 ( ! self::is_wcpay_active() ) { return false; } $wc_payments_gateway = self::get_gateway(); if ( $wc_payments_gateway && method_exists( $wc_payments_gateway, 'is_connected' ) ) { return $wc_payments_gateway->is_connected(); } return false; } /** * Check if WooPayments needs setup. * Errored data or payments not enabled. * * @return bool */ public static function is_account_partially_onboarded() { if ( ! self::is_wcpay_active() ) { return false; } $wc_payments_gateway = self::get_gateway(); if ( $wc_payments_gateway && method_exists( $wc_payments_gateway, 'is_account_partially_onboarded' ) ) { return $wc_payments_gateway->is_account_partially_onboarded(); } return false; } /** * Get the WooPayments payment gateway suggestion. * * @return object|null The WooPayments suggestion, or null if none found. */ public static function get_suggestion() { $suggestions = Suggestions::get_suggestions( DefaultPaymentGateways::get_all() ); $wcpay_suggestions = array_filter( $suggestions, function ( $suggestion ) { if ( empty( $suggestion->plugins ) || ! is_array( $suggestion->plugins ) ) { return false; } return in_array( 'woocommerce-payments', $suggestion->plugins, true ); } ); if ( empty( $wcpay_suggestions ) ) { return null; } return reset( $wcpay_suggestions ); } /** * Check if the store location is in a WooPayments supported country. * * We infer this from the availability of a WooPayments payment gateways suggestion. * * @return bool True if the store location is in a WooPayments supported country, false otherwise. */ public static function is_supported() { return ! empty( self::get_suggestion() ); } /** * Get the WooPayments gateway. * * @return \WC_Payments|null */ private static function get_gateway() { $payment_gateways = WC()->payment_gateways()->payment_gateways(); if ( isset( $payment_gateways['woocommerce_payments'] ) ) { return $payment_gateways['woocommerce_payments']; } return null; } /** * Check if the store has any enabled ecommerce gateways, other than WooPayments. * * We exclude offline payment methods from this check. * * @return bool */ public static function has_other_ecommerce_gateways(): bool { $gateways = WC()->payment_gateways()->payment_gateways; $enabled_gateways = array_filter( $gateways, function ( $gateway ) { // Filter out any WooPayments-related or offline gateways. return 'yes' === $gateway->enabled && 0 !== strpos( $gateway->id, 'woocommerce_payments' ) && ! in_array( $gateway->id, array( WC_Gateway_BACS::ID, WC_Gateway_Cheque::ID, WC_Gateway_COD::ID ), true ); } ); return ! empty( $enabled_gateways ); } /** * The task action URL. * * @return string */ public function get_action_url() { if ( self::is_supported() ) { // If WooPayments is active, point to the WooPayments client surfaces/flows. if ( self::is_wcpay_active() ) { // Point to a WooPayments connect link to let the WooPayments client figure out the proper // place to redirect the user to. return add_query_arg( array( 'wcpay-connect' => '1', 'from' => 'WCADMIN_PAYMENT_TASK', '_wpnonce' => wp_create_nonce( 'wcpay-connect' ), ), admin_url( 'admin.php' ) ); } // Check if there is an active WooPayments incentive via the welcome page. if ( WcPayWelcomePage::instance()->has_incentive() ) { // 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' ) ); } // WooPayments is not active. // Trigger the WooPayments plugin installation and/or activation by pointing to the task suggestion URL. return add_query_arg( array( 'task' => $this->get_id(), 'id' => self::get_suggestion()->id, ), admin_url( 'admin.php?page=wc-admin' ) ); } // Fall back to the WooPayments task page URL. return add_query_arg( 'task', $this->get_id(), admin_url( 'admin.php?page=wc-admin' ) ); } } OnboardingTasks/Tasks/GetMobileApp.php 0000777 00000005014 15253027022 0013747 0 ustar 00 <?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' ); } } OnboardingTasks/Tasks/Products.php 0000777 00000016137 15253027022 0013252 0 ustar 00 <?php namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks; use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task; use Automattic\WooCommerce\Enums\ProductStatus; use Automattic\WooCommerce\Internal\Admin\WCAdminAssets; use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile; /** * Products Task */ class Products extends Task { const HAS_PRODUCT_TRANSIENT = 'woocommerce_product_task_has_product_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_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, 'maybe_set_has_product_transient' ), 10, 2 ); add_action( 'woocommerce_new_product', array( $this, 'maybe_set_has_product_transient' ), 10, 2 ); add_action( 'untrashed_post', array( $this, 'maybe_set_has_product_transient_on_untrashed_post' ) ); add_action( 'current_screen', array( $this, 'maybe_redirect_to_add_product_tasklist' ), 30, 0 ); } /** * 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() { if ( $this->has_previously_completed() ) { return true; } return self::has_products(); } /** * Additional data. * * @return array */ public function get_additional_data() { return array( 'has_products' => self::has_products(), ); } /** * If a task is always accessible, relevant for when a task list is hidden but a task can still be viewed. * * @return bool */ public function is_always_accessible() { return true; } /** * 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 ); } /** * Set the has products transient if the post qualifies as a user created product. * * @param int $post_id Post ID. */ public function maybe_set_has_product_transient_on_untrashed_post( $post_id ) { if ( get_post_type( $post_id ) !== 'product' ) { return; } $this->maybe_set_has_product_transient( $post_id, wc_get_product( $post_id ) ); } /** * Set the has products transient if the product qualifies as a user created product. * * @param int $product_id Product ID. * @param WC_Product $product Product object. */ public function maybe_set_has_product_transient( $product_id, $product ) { if ( ! $this->has_previously_completed() && $this->is_valid_product( $product ) ) { set_transient( self::HAS_PRODUCT_TRANSIENT, 'yes' ); $this->possibly_track_completion(); } } /** * Check if the product qualifies as a user created product. * * @param WC_Product $product Product object. * @return bool */ private function is_valid_product( $product ) { return ProductStatus::PUBLISH === $product->get_status() && ( ! $product->get_meta( '_headstart_post' ) || get_post_meta( $product->get_id(), '_edit_last', true ) ); } /** * Check if the store has any user created published products. * * @return bool */ public static function has_products() { $product_exists = get_transient( self::HAS_PRODUCT_TRANSIENT ); if ( $product_exists ) { return 'yes' === $product_exists; } global $wpdb; /* * Check if any valid products exist and return 'yes' or 'no' * A valid product must: * 1. Be a published product post type * 2. Meet one of these conditions: * - Have been edited by a user (_edit_last meta exists), OR * - Not have _headstart_post meta, OR * - Have _headstart_post meta but it's NULL */ $value = $wpdb->get_var( $wpdb->prepare( "SELECT IF( EXISTS ( SELECT 1 FROM {$wpdb->posts} p WHERE p.post_type = %s AND p.post_status = %s AND ( EXISTS ( SELECT 1 FROM {$wpdb->postmeta} pm WHERE pm.post_id = p.ID AND pm.meta_key = %s ) OR NOT EXISTS ( SELECT 1 FROM {$wpdb->postmeta} pm WHERE pm.post_id = p.ID AND pm.meta_key = %s ) OR EXISTS ( SELECT 1 FROM {$wpdb->postmeta} pm WHERE pm.post_id = p.ID AND pm.meta_key = %s AND pm.meta_value = '' ) ) LIMIT 1 ), 'yes', 'no' )", 'product', ProductStatus::PUBLISH, '_edit_last', '_headstart_post', '_headstart_post' ) ); set_transient( self::HAS_PRODUCT_TRANSIENT, $value ); return 'yes' === $value; } /** * Redirect to the add product tasklist if there are no products. * * @return void */ public function maybe_redirect_to_add_product_tasklist() { $screen = get_current_screen(); if ( 'edit' === $screen->base && 'product' === $screen->post_type ) { // wp_count_posts is cached. $counts = (array) wp_count_posts( $screen->post_type ); unset( $counts['auto-draft'] ); $count = array_sum( $counts ); if ( $count > 0 ) { return; } wp_safe_redirect( admin_url( 'admin.php?page=wc-admin&task=products' ) ); exit; } } } OnboardingTasks/Tasks/ExperimentalShippingRecommendation.php 0000777 00000003350 15253027022 0020464 0 ustar 00 <?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; use Automattic\WooCommerce\Internal\Jetpack\JetpackConnection; /** * 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-shipping' ); } /** * Check if the Jetpack is connected. * * @return bool */ public static function has_jetpack_connected() { $jetpack_connection_manager = JetpackConnection::get_manager(); return $jetpack_connection_manager->is_connected() && $jetpack_connection_manager->has_connected_owner(); } } OnboardingTasks/Tasks/LaunchYourStore.php 0000777 00000004650 15253027022 0014552 0 ustar 00 <?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; } } OnboardingTasks/Tasks/CustomizeStore.php 0000777 00000006645 15253027022 0014451 0 ustar 00 <?php namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks; use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task; use WP_Post; /** * Customize Your Store Task * * @internal */ class CustomizeStore extends Task { /** * Constructor * * @param TaskList $task_list Parent task list. */ public function __construct( $task_list ) { parent::__construct( $task_list ); add_action( 'save_post_wp_global_styles', array( $this, 'mark_task_as_complete_block_theme' ), 10, 3 ); add_action( 'save_post_wp_template', array( $this, 'mark_task_as_complete_block_theme' ), 10, 3 ); add_action( 'save_post_wp_template_part', array( $this, 'mark_task_as_complete_block_theme' ), 10, 3 ); add_action( 'customize_save_after', array( $this, 'mark_task_as_complete_classic_theme' ) ); } /** * 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_block_theme( $post_id, $post, $update ) { if ( $post instanceof WP_Post ) { $is_cys_complete = $this->has_custom_global_styles( $post ) || $this->has_custom_template( $post ); if ( $is_cys_complete ) { update_option( 'woocommerce_admin_customize_store_completed', 'yes' ); } } } /** * Mark the CYS task as complete whenever the user saves the customizer changes. * * @return void */ public function mark_task_as_complete_classic_theme() { 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' ); } /** * Checks if the post has custom global styles stored (if it is different from the default global styles). * * @param WP_Post $post The post object. * @return bool */ private function has_custom_global_styles( WP_Post $post ) { $required_keys = array( 'version', 'isGlobalStylesUserThemeJSON' ); $json_post_content = json_decode( $post->post_content, true ); if ( is_null( $json_post_content ) ) { return false; } $post_content_keys = array_keys( $json_post_content ); return ! empty( array_diff( $post_content_keys, $required_keys ) ) || ! empty( array_diff( $required_keys, $post_content_keys ) ); } /** * Checks if the post is a template or a template part. * * @param WP_Post $post The post object. * @return bool Whether the post is a template or a template part. */ private function has_custom_template( WP_Post $post ) { return in_array( $post->post_type, array( 'wp_template', 'wp_template_part' ), true ); } } OnboardingTasks/Tasks/StoreDetails.php 0000777 00000004207 15253027022 0014044 0 ustar 00 <?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', '' ) !== ''; } } OnboardingTasks/Tasks/ReviewShippingOptions.php 0000777 00000002177 15253027022 0015765 0 ustar 00 <?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' ); } } OnboardingTasks/Tasks/AdditionalPayments.php 0000777 00000006737 15253027022 0015245 0 ustar 00 <?php namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks; use Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders; use Automattic\WooCommerce\Internal\Admin\Settings\Payments as SettingsPaymentsService; /** * 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 = $this->has_enabled_non_psp_payment_suggestion(); } return $this->is_complete_result; } /** * Task visibility. * * @return bool */ public function can_view() { if ( null !== $this->can_view_result ) { return $this->can_view_result; } // Always show task if there are any gateways enabled (i.e. the Payments task is complete). if ( self::has_gateways() ) { $this->can_view_result = true; } else { $this->can_view_result = false; } return $this->can_view_result; } /** * Action URL. * * @return string */ public function get_action_url(): string { // We auto-expand the "Other" section to show the additional payment methods. return admin_url( 'admin.php?page=wc-settings&tab=checkout&other_pes_section=expanded&from=' . SettingsPaymentsService::FROM_ADDITIONAL_PAYMENTS_TASK ); } /** * Check if there are any enabled non-PSP payment suggestions. * * @return bool True if there are enabled non-PSP payment suggestions, false otherwise. */ private function has_enabled_non_psp_payment_suggestion(): bool { $providers = $this->get_payment_providers(); foreach ( $providers as $provider ) { // Check if the provider is enabled and has a suggestion category ID that matches the ones we are interested in. if ( ! empty( $provider['state']['enabled'] ) && ! empty( $provider['_suggestion_category_id'] ) && in_array( $provider['_suggestion_category_id'], array( PaymentsProviders::CATEGORY_BNPL, PaymentsProviders::CATEGORY_EXPRESS_CHECKOUT, PaymentsProviders::CATEGORY_CRYPTO ), true ) ) { return true; } } return false; } /** * Get the list of payments providers as it is used on the Payments Settings page. * * @return array The list of payment providers. */ private function get_payment_providers(): array { try { /** * The Payments Settings [page] service. * * @var SettingsPaymentsService $settings_payments_service */ $settings_payments_service = wc_get_container()->get( SettingsPaymentsService::class ); $providers = $settings_payments_service->get_payment_providers( $settings_payments_service->get_country(), false ); } catch ( \Throwable $e ) { // In case of any error, return an empty array. $providers = array(); } return $providers; } } OnboardingTasks/Tasks/Shipping.php 0000777 00000011726 15253027022 0013227 0 ustar 00 <?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; } } OnboardingTasks/Tasks/Marketing.php 0000777 00000004506 15253027022 0013365 0 ustar 00 <?php namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks; use Automattic\WooCommerce\Admin\Features\Features; use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task; /** * Marketing Task */ class Marketing extends Task { /** * Constructor * * @param TaskList $task_list Parent task list. */ public function __construct( $task_list ) { parent::__construct( $task_list ); add_action( 'activated_plugin', array( $this, 'on_activated_plugin' ), 10, 1 ); } /** * Mark the task as complete when related plugins are activated. */ public function on_activated_plugin( $plugin ) { $plugin_basename = basename( plugin_basename( $plugin ), '.php' ); // Example: How to mark the marketing task as complete when a specific plugin is activated. /** * Example: * if ( * $plugin_basename === 'multichannel-by-cedcommerce' && * $this->task_list->visible && * ! $this->task_list->is_hidden() && * ! $this->is_complete() * ) { * $this->mark_actioned(); * } */ } /** * 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 visibility. * * @return bool */ public function can_view() { return Features::is_enabled( 'remote-free-extensions' ); } /** * Get the marketing plugins. * * @deprecated 9.3.0 Removed to improve performance. * @return array */ public static function get_plugins() { wc_deprecated_function( __METHOD__, '9.3.0' ); return array(); } /** * Check if the store has installed marketing extensions. * * @deprecated 9.3.0 Removed to improve performance. * @return bool */ public static function has_installed_extensions() { wc_deprecated_function( __METHOD__, '9.3.0' ); return false; } } OnboardingTasks/Tasks/Tax.php 0000777 00000015562 15253027022 0012204 0 ustar 00 <?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 { private const TAX_RATE_EXISTS_CACHE_KEY = 'woocommerce_onboarding_task_tax_rates_exist'; /** * 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' ) ); add_action( 'woocommerce_tax_rate_added', array( $this, 'on_tax_rate_added' ) ); add_action( 'woocommerce_tax_rate_deleted', array( $this, 'on_tax_rate_deleted' ) ); } /** * 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 // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment -- We will replace this with a formal system by WC 9.6 so lets not advertise it yet. $third_party_complete = apply_filters( 'woocommerce_admin_third_party_tax_setup_complete', false ); /** * Ideally we would check against `wc_tax_enabled()` instead of `false !== get_option( 'woocommerce_no_sales_tax' )`, * however, tax is disabled by default making this task complete by default if we use it. If we change taxes * to be enabled by default in the future, this can be updated to check against `wc_tax_enabled()` which is * more accurate for this evaluation. */ $this->is_complete_result = $is_wc_connect_taxes_enabled || $third_party_complete || false !== get_option( 'woocommerce_no_sales_tax' ) || $this->has_existing_tax_rates(); } return $this->is_complete_result; } /** * Determines if a tax rate exists in the database. Result is indefinitely cached. * * @return bool */ private function has_existing_tax_rates() { global $wpdb; $has_existing_tax_rates = wp_cache_get( self::TAX_RATE_EXISTS_CACHE_KEY ); if ( false === $has_existing_tax_rates ) { $rate_exists = (bool) $wpdb->get_var( "SELECT 1 FROM {$wpdb->prefix}woocommerce_tax_rates limit 1" ); $has_existing_tax_rates = $rate_exists ? 'yes' : 'no'; wp_cache_set( self::TAX_RATE_EXISTS_CACHE_KEY, $has_existing_tax_rates ); } return 'yes' === $has_existing_tax_rates; } /** * Marks the task as actioned any time a tax rate has been added. Called from the `woocommerce_tax_rate_added` hook. * * @return void */ public function on_tax_rate_added() { $this->mark_actioned(); wp_cache_set( self::TAX_RATE_EXISTS_CACHE_KEY, 'yes' ); } /** * Clears the tax rate exists cache when a tax rate is deleted. Called from the `woocommerce_tax_rate_added` hook. * * @return void */ public function on_tax_rate_deleted() { wp_cache_delete( self::TAX_RATE_EXISTS_CACHE_KEY ); } /** * Additional 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' ), 'stripe_tax_activated' => PluginsHelper::is_plugin_active( 'stripe-tax-for-woocommerce' ), 'woocommerce_tax_activated' => PluginsHelper::is_plugin_active( 'woocommerce-tax' ), 'woocommerce_shipping_activated' => PluginsHelper::is_plugin_active( 'woocommerce-shipping' ), 'woocommerce_tax_countries' => self::get_automated_support_countries(), 'stripe_tax_countries' => self::get_stripe_tax_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; } /** * Get an array of countries that support Stripe tax. * * @return array */ private static function get_stripe_tax_support_countries() { // https://docs.stripe.com/tax/supported-countries#supported-countries accurate as of 2024-08-26. // countries with remote sales not included. return array( 'AU', 'AT', 'BE', 'BG', 'CA', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HK', 'HU', 'IE', 'IT', 'JP', 'LV', 'LT', 'LU', 'MT', 'NL', 'NZ', 'NO', 'PL', 'PT', 'RO', 'SG', 'SK', 'SI', 'ES', 'SE', 'CH', 'AE', 'GB', 'US', ); } } OnboardingTasks/TaskListSection.php 0000777 00000004466 15253027022 0013447 0 ustar 00 <?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(), ); } } OnboardingTasks/Init.php 0000777 00000002116 15253027022 0011255 0 ustar 00 <?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; } } OnboardingTasks/DeprecatedOptions.php 0000777 00000005000 15253027022 0013761 0 ustar 00 <?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; } } } OnboardingTasks/TaskList.php 0000777 00000024005 15253027022 0012111 0 ustar 00 <?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 the task list is explicitly set to not be visible, return false. if ( ! $this->visible ) { return false; } // If the task list is hidden, return false. if ( $this->is_hidden() ) { return false; } // If the task list has no viewable tasks, return false. $no_viewable_tasks = count( $this->get_viewable_tasks() ) === 0; if ( $no_viewable_tasks ) { return false; } return true; } /** * 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, true ); $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(); foreach ( $this->tasks as $task ) { // We have no use for hidden lists, it's expensive to compute individual tasks completion. // Exception: Secret tasklist is always hidden, or a task is always accessible. $list_is_visible = $this->is_visible() || 'secret_tasklist' === $this->id; if ( $list_is_visible || ( method_exists( $task, 'is_always_accessible' ) && $task->is_always_accessible() ) ) { $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(), ); } } LaunchYourStore.php 0000777 00000026720 15253027022 0010377 0 ustar 00 <?php declare( strict_types = 1 ); namespace Automattic\WooCommerce\Admin\Features; use Automattic\WooCommerce\Admin\PluginsHelper; use Automattic\WooCommerce\Admin\WCAdminHelper; use Automattic\WooCommerce\Internal\Admin\WCAdminUser; /** * Takes care of Launch Your Store related actions. */ class LaunchYourStore { const BANNER_DISMISS_USER_META_KEY = '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 ); add_filter( 'woocommerce_admin_get_user_data_fields', array( $this, 'add_user_data_fields' ) ); if ( Features::is_enabled( 'coming-soon-newsletter-template' ) ) { add_action( 'admin_enqueue_scripts', array( $this, 'load_newsletter_scripts' ) ); add_action( 'save_post_wp_template', array( $this, 'maybe_track_template_change' ), 10, 3 ); } } /** * 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'] ) ) : ''; // New Settings API uses wp_rest nonce. $nonce_string = Features::is_enabled( 'settings' ) ? 'wp_rest' : 'woocommerce-settings'; if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, $nonce_string ) ) { 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; } $has_dismissed_banner = WCAdminUser::get_user_data_field( $current_user_id, self::BANNER_DISMISS_USER_META_KEY ) // Remove this check in WC 9.4. || get_user_meta( $current_user_id, 'woocommerce_' . self::BANNER_DISMISS_USER_META_KEY, true ) === 'yes'; if ( $has_dismissed_banner ) { 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_current_page_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'><div class='coming-soon-footer-banner__content'>$text</div><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. * * This should be removed in WC 9.4. */ 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', 'woocommerce_coming_soon_banner_dismissed', array( 'type' => 'string', 'description' => 'Indicate whether the user has dismissed the coming soon notice or not.', 'single' => true, 'show_in_rest' => true, ) ); } /** * Register user meta fields for Launch Your Store. * * @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( 'launch_your_store_tour_hidden', self::BANNER_DISMISS_USER_META_KEY, ) ); } /** * 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 = WCAdminUser::get_user_data_field( $user->ID, self::BANNER_DISMISS_USER_META_KEY ); if ( 'yes' === $existing_meta ) { WCAdminUser::update_user_data_field( $user->ID, self::BANNER_DISMISS_USER_META_KEY, 'no' ); } } /** * Check if the Mailpoet is connected. * * @return bool true if Mailpoet is fully connected, meaning the API key is valid and approved. */ private function is_mailpoet_connected() { if ( ! class_exists( '\MailPoet\DI\ContainerWrapper' ) || ! class_exists( '\MailPoet\Settings\SettingsController' ) ) { return false; } $container = \MailPoet\DI\ContainerWrapper::getInstance( WP_DEBUG ); // SettingController retrieves data from wp_mailpoet_settings table. $settings = $container->get( \MailPoet\Settings\SettingsController::class ); if ( false === $settings instanceof \MailPoet\Settings\SettingsController ) { return false; } $mta = $settings->get( 'mta' ); $api_state = $mta['mailpoet_api_key_state'] ?? null; if ( ! $api_state || ! isset( $api_state['state'], $api_state['code'] ) ) { return false; } return 'valid' === $api_state['state'] && 200 === $api_state['code']; } /** * Track when coming soon template is changed. * * @param int $post_id The post ID. * @param WP_Post $post The post object. * @param bool $update Whether the post is being updated. */ public function maybe_track_template_change( $post_id, $post, $update ) { if ( ! $post instanceof \WP_Post || ! isset( $post->post_name, $post->post_title ) ) { return; } // Check multiple fields to avoid false matches with non-WooCommerce templates. if ( 'coming-soon' === $post->post_name && 'Page: Coming soon' === $post->post_title ) { $matches = array(); $content = $post->post_content; preg_match( '/"comingSoonPatternId":"([^"]+)"/', $content, $matches ); if ( isset( $matches[1] ) ) { wc_admin_record_tracks_event( 'coming_soon_template_saved', array( 'pattern_id' => $matches[1], 'is_update' => $update, ) ); } } } /** * Load slotfill script and JS variables for the newsletter. * The comingSoonNewsletter is used in client/wp-admin-scripts/coming-soon-newsletter-panel * * @return void */ public function load_newsletter_scripts() { $screen = get_current_screen(); if ( ! $screen instanceof \WP_Screen ) { return; } if ( 'site-editor' !== $screen->id ) { return; } $mailpoet = array( 'mailpoet_installed' => PluginsHelper::is_plugin_installed( 'mailpoet' ), 'mailpoet_connected' => $this->is_mailpoet_connected(), ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion, WordPress.WP.EnqueuedResourceParameters.NotInFooter wp_register_script( 'coming-soon-newsletter-mailpoet', '' ); wp_enqueue_script( 'coming-soon-newsletter-mailpoet' ); wp_add_inline_script( 'coming-soon-newsletter-mailpoet', 'var comingSoonNewsletter = ' . wp_json_encode( $mailpoet, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) . ';' ); } } ProductBlockEditor/Init.php 0000777 00000036101 15253027022 0011730 0 ustar 00 <?php /** * WooCommerce Product Block Editor */ declare(strict_types = 1); 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\Enums\ProductType; use Automattic\WooCommerce\LayoutTemplates\LayoutTemplateRegistry; use Automattic\WooCommerce\Internal\Features\ProductBlockEditor\ProductTemplates\SimpleProductTemplate; use Automattic\WooCommerce\Internal\Features\ProductBlockEditor\ProductTemplates\ProductVariationTemplate; use WC_Meta_Data; 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( ProductType::SIMPLE ); /** * Registered product templates. * * @var array */ private $product_templates = array(); /** * Redirection controller. * * @var RedirectionController */ private $redirection_controller; /** * Constructor */ public function __construct() { if ( ! is_admin() && ! WC()->is_rest_api_request() ) { return; } array_push( $this->supported_product_types, ProductType::VARIABLE ); array_push( $this->supported_product_types, ProductType::EXTERNAL ); array_push( $this->supported_product_types, ProductType::GROUPED ); $this->redirection_controller = new RedirectionController(); if ( \Automattic\WooCommerce\Utilities\FeaturesUtil::feature_is_enabled( 'product_block_editor' ) ) { 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 ); add_filter( 'woocommerce_rest_prepare_product_object', array( $this, 'possibly_add_template_id' ), 10, 2 ); add_filter( 'woocommerce_rest_prepare_product_variation_object', array( $this, 'possibly_add_template_id' ), 10, 2 ); // 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(); } } /** * Adds the product template ID to the product if it doesn't exist. * * @param WP_REST_Response $response The response object. * @param WC_Product $product The product. */ public function possibly_add_template_id( $response, $product ) { if ( ! $product ) { return $response; } if ( ! $product->meta_exists( '_product_template_id' ) ) { /** * Experimental: Allows to determine a product template id based on the product data. * * @ignore * @since 9.1.0 */ $product_template_id = apply_filters( 'experimental_woocommerce_product_editor_product_template_id_for_product', '', $product ); if ( $product_template_id ) { $response->data['meta_data'][] = new WC_Meta_Data( array( 'key' => '_product_template_id', 'value' => $product_template_id, ) ); } } return $response; } /** * 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( 'wp-blocks' ), '0.1.0', true ); wp_enqueue_script( $script_handle ); wp_add_inline_script( $script_handle, 'var productBlockEditorSettings = productBlockEditorSettings || ' . wp_json_encode( $editor_settings, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) . ';', 'before' ); wp_add_inline_script( $script_handle, sprintf( 'wp.blocks.setCategories( %s );', wp_json_encode( $editor_settings['blockCategories'], JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) ), '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_page() ) { return; } wp_enqueue_style( 'wc-product-editor' ); wp_enqueue_style( 'wp-editor' ); 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_page() ) { return; } // Dequeuing 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() === ProductType::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(), JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) . ');' ); } } /** * 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' => ProductType::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' => ProductType::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' => ProductType::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 ); // PFT: Initialize the product form controller. if ( Features::is_enabled( 'product-editor-template-system' ) ) { $product_form_controller = new ProductFormsController(); $product_form_controller->init(); } } /** * 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; } } ProductBlockEditor/BlockTemplateUtils.php 0000777 00000004751 15253027022 0014602 0 ustar 00 <?php namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor; use Automattic\WooCommerce\LayoutTemplates\LayoutTemplateRegistry; /** * Utils for block templates. */ class BlockTemplateUtils { /** * Directory which contains all templates * * @var string */ const TEMPLATES_ROOT_DIR = 'templates'; /** * Directory names. * * @var array */ const DIRECTORY_NAMES = array( 'TEMPLATES' => 'product-form', 'TEMPLATE_PARTS' => 'product-form/parts', ); /** * Gets the directory where templates of a specific template type can be found. * * @param string $template_type wp_template or wp_template_part. * @return string */ private static function get_templates_directory( $template_type = 'wp_template' ) { $root_path = dirname( __DIR__, 4 ) . '/' . self::TEMPLATES_ROOT_DIR . DIRECTORY_SEPARATOR; $templates_directory = $root_path . self::DIRECTORY_NAMES['TEMPLATES']; $template_parts_directory = $root_path . self::DIRECTORY_NAMES['TEMPLATE_PARTS']; if ( 'wp_template_part' === $template_type ) { return $template_parts_directory; } return $templates_directory; } /** * Return the path to a block template file. * Otherwise, False. * * @param string $slug - Template slug. * @return string|bool Path to the template file or false. */ public static function get_block_template_path( $slug ) { $directory = self::get_templates_directory(); $path = trailingslashit( $directory ) . $slug . '.php'; if ( ! file_exists( $path ) ) { return false; } return $path; } /** * Get the template data from the headers. * * @param string $file_path - File path. * @return array Template data. */ public static function get_template_file_data( $file_path ) { if ( ! file_exists( $file_path ) ) { return array(); } $file_data = get_file_data( $file_path, array( 'title' => 'Title', 'slug' => 'Slug', 'description' => 'Description', 'product_types' => 'Product Types', ), ); $file_data['product_types'] = explode( ',', trim( $file_data['product_types'] ) ); return $file_data; } /** * Get the template content from the file. * * @param string $file_path - File path. * @return string Content. */ public static function get_template_content( $file_path ) { if ( ! file_exists( $file_path ) ) { return ''; } ob_start(); include $file_path; $content = ob_get_contents(); ob_end_clean(); return $content; } } ProductBlockEditor/ProductFormsController.php 0000777 00000007120 15253027022 0015517 0 ustar 00 <?php /** * WooCommerce Product Forms Controller */ namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor; /** * Handle retrieval of product forms. */ class ProductFormsController { /** * Product form templates. * * @var array */ private $product_form_templates = array( 'simple', ); /** * Set up the product forms controller. */ public function init() { // phpcs:ignore WooCommerce.Functions.InternalInjectionMethod.MissingFinal, WooCommerce.Functions.InternalInjectionMethod.MissingInternalTag -- Not an injection. add_action( 'upgrader_process_complete', array( $this, 'migrate_templates_when_plugin_updated' ), 10, 2 ); } /** * Migrate form templates after WooCommerce plugin update. * * @param \WP_Upgrader $upgrader The WP_Upgrader instance. * @param array $hook_extra Extra arguments passed to hooked filters. * @return void */ public function migrate_templates_when_plugin_updated( \WP_Upgrader $upgrader, array $hook_extra ) { // If it is not a plugin hook type, bail early. $type = isset( $hook_extra['type'] ) ? $hook_extra['type'] : ''; if ( 'plugin' !== $type ) { return; } // If it is not the WooCommerce plugin, bail early. $plugins = isset( $hook_extra['plugins'] ) ? $hook_extra['plugins'] : array(); if ( ! in_array( 'woocommerce/woocommerce.php', $plugins, true ) ) { return; } // If the action is not install or update, bail early. $action = isset( $hook_extra['action'] ) ? $hook_extra['action'] : ''; if ( 'install' !== $action && 'update' !== $action ) { return; } // Trigger the migration process. $this->migrate_product_form_posts( $action ); } /** * Create or update a product_form post for each product form template. * If the post already exists, it will be updated. * If the post does not exist, it will be created even if the action is `update`. * * @param string $action - The action to perform. `insert` | `update`. * @return void */ public function migrate_product_form_posts( $action ) { /** * Allow extend the list of templates that should be auto-generated. * * @since 9.1.0 * @param array $templates List of templates to auto-generate. */ $templates = apply_filters( 'woocommerce_product_form_templates', $this->product_form_templates ); foreach ( $templates as $slug ) { $file_path = BlockTemplateUtils::get_block_template_path( $slug ); if ( ! $file_path ) { continue; } $file_data = BlockTemplateUtils::get_template_file_data( $file_path ); $posts = get_posts( array( 'name' => $slug, 'post_type' => 'product_form', 'post_status' => 'any', 'posts_per_page' => 1, ) ); /* * Update the the CPT post if it already exists, * and the action is `update`. */ if ( 'update' === $action ) { $post = $posts[0] ?? null; if ( ! empty( $post ) ) { wp_update_post( array( 'ID' => $post->ID, 'post_title' => $file_data['title'], 'post_content' => BlockTemplateUtils::get_template_content( $file_path ), 'post_excerpt' => $file_data['description'], ) ); } } /* * Skip the post creation if the post already exists. */ if ( ! empty( $posts ) ) { continue; } $post = wp_insert_post( array( 'post_title' => $file_data['title'], 'post_name' => $slug, 'post_status' => 'publish', 'post_type' => 'product_form', 'post_content' => BlockTemplateUtils::get_template_content( $file_path ), 'post_excerpt' => $file_data['description'], ) ); } } } ProductBlockEditor/BlockRegistry.php 0000777 00000022075 15253027022 0013615 0 ustar 00 <?php /** * WooCommerce Product Editor Block Registration */ namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor; use Automattic\WooCommerce\Internal\Admin\WCAdminAssets; use Automattic\WooCommerce\Blocks\Utils\Utils; /** * 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', 'woocommerce/product-notice-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 ) { global $wp_version; // Note: If you modify this function, also update the client-side // registerWooBlockType function in @woocommerce/block-templates. $augmented_attributes = array_merge( $attributes, array( '_templateBlockId' => array( 'type' => 'string', 'role' => 'content', ), '_templateBlockOrder' => array( 'type' => 'integer', 'role' => 'content', ), '_templateBlockHideConditions' => array( 'type' => 'array', 'role' => 'content', ), '_templateBlockDisableConditions' => array( 'type' => 'array', 'role' => 'content', ), 'disabled' => isset( $attributes['disabled'] ) ? $attributes['disabled'] : array( 'type' => 'boolean', 'role' => 'content', ), ) ); if ( ! $this->has_role_support() ) { foreach ( $augmented_attributes as $key => $attribute ) { if ( isset( $attribute['role'] ) ) { $augmented_attributes[ $key ]['__experimentalRole'] = $attribute['role']; } } } return $augmented_attributes; } /** * Checks for block attribute role support. */ private function has_role_support() { if ( Utils::wp_version_compare( '6.7', '>=' ) ) { return true; } if ( is_plugin_active( 'gutenberg/gutenberg.php' ) ) { $gutenberg_version = ''; if ( defined( 'GUTENBERG_VERSION' ) ) { $gutenberg_version = GUTENBERG_VERSION; } if ( ! $gutenberg_version ) { $gutenberg_data = get_file_data( WP_PLUGIN_DIR . '/gutenberg/gutenberg.php', array( 'Version' => 'Version' ) ); $gutenberg_version = $gutenberg_data['Version']; } return version_compare( $gutenberg_version, '19.4', '>=' ); } return false; } /** * 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() ), ) ); } } ProductBlockEditor/Tracks.php 0000777 00000002263 15253027022 0012256 0 ustar 00 <?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; } } ProductBlockEditor/RedirectionController.php 0000777 00000012200 15253027022 0015332 0 ustar 00 <?php /** * WooCommerce Product Editor Redirection Controller */ namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor; use Automattic\WooCommerce\Admin\Features\Features; use Automattic\WooCommerce\Enums\ProductType; 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() === ProductType::VARIABLE ? ProductType::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 templates. */ 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] ?? 0 ) : 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(); } } } ProductBlockEditor/ProductTemplate.php 0000777 00000010353 15253027022 0014142 0 ustar 00 <?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(), ); } } ProductBlockEditor/ProductTemplates/ProductFormTemplateInterface.php 0000777 00000002455 15253027022 0022112 0 ustar 00 <?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; } ProductBlockEditor/ProductTemplates/GroupInterface.php 0000777 00000001353 15253027022 0017242 0 ustar 00 <?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; } ProductBlockEditor/ProductTemplates/SectionInterface.php 0000777 00000001727 15253027022 0017557 0 ustar 00 <?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; } ProductBlockEditor/ProductTemplates/SubsectionInterface.php 0000777 00000001047 15253027022 0020264 0 ustar 00 <?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; } PaymentGatewaySuggestions/DefaultPaymentGateways.php 0000777 00000131230 15253027022 0017103 0 ustar 00 <?php /** * Gets a list of fallback methods if remote fetching is disabled. */ namespace Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions; defined( 'ABSPATH' ) || exit; use WC_Gateway_BACS; use WC_Gateway_COD; /** * Default Payment Gateways */ class DefaultPaymentGateways { /** * This is the default priority for countries that are not in the $recommendation_priority_map. * Priority is used to determine which payment gateway to recommend first. * The lower the number, the higher the priority. * * @var array */ private static $recommendation_priority = array( 'woocommerce_payments' => 1, 'woocommerce_payments:with-in-person-payments' => 1, 'woocommerce_payments:without-in-person-payments' => 1, 'stripe' => 2, 'woo-mercado-pago-custom' => 3, // PayPal Payments. 'ppcp-gateway' => 4, 'mollie_wc_gateway_banktransfer' => 5, 'razorpay' => 5, 'payfast' => 5, 'payubiz' => 6, 'square_credit_card' => 6, 'klarna_payments' => 6, // Klarna Checkout. 'kco' => 6, 'paystack' => 6, 'eway' => 7, 'amazon_payments_advanced' => 7, 'affirm' => 8, 'afterpay' => 9, 'zipmoney' => 10, 'payoneer-checkout' => 11, ); /** * Get default specs. * * @return array Default specs. */ public static function get_all() { $payment_gateways = array( array( 'id' => 'affirm', 'title' => __( 'Affirm', 'woocommerce' ), 'content' => __( 'Affirm’s tailored Buy Now Pay Later programs remove price as a barrier, turning browsers into buyers, increasing average order value, and expanding your customer base.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/affirm.png', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/affirm.png', 'plugins' => array(), 'external_link' => 'https://woocommerce.com/products/woocommerce-gateway-affirm', 'is_visible' => array( self::get_rules_for_countries( array( 'US', 'CA', ) ), (object) array( 'type' => 'or', 'operands' => array( self::get_rules_for_wcpay_activated( false ), self::get_rules_for_wcpay_connected( false ), ), ), ), 'category_other' => array(), 'category_additional' => array( 'US', 'CA', ), ), array( 'id' => 'afterpay', 'title' => __( 'Afterpay', 'woocommerce' ), 'content' => __( 'Afterpay allows customers to receive products immediately and pay for purchases over four installments, always interest-free.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/afterpay.png', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/afterpay.png', 'plugins' => array( 'afterpay-gateway-for-woocommerce' ), 'is_visible' => array( self::get_rules_for_countries( array( 'US', 'CA', 'AU', ) ), (object) array( 'type' => 'or', 'operands' => array( self::get_rules_for_wcpay_activated( false ), self::get_rules_for_wcpay_connected( false ), ), ), ), 'category_other' => array(), 'category_additional' => array( 'US', 'CA', 'AU', ), ), array( 'id' => 'airwallex_main', 'title' => __( 'Airwallex Payments', 'woocommerce' ), 'content' => __( 'Boost international sales and save on FX fees. Accept 60+ local payment methods including Apple Pay and Google Pay.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/airwallex.png', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/airwallex.png', 'plugins' => array( 'airwallex-online-payments-gateway' ), 'is_visible' => array( self::get_rules_for_countries( array( 'GB', 'AT', 'BE', 'EE', 'FR', 'DE', 'GR', 'IE', 'IT', 'NL', 'PL', 'PT', 'AU', 'NZ', 'HK', 'SG', 'CN' ) ), ), 'category_other' => array( 'GB', 'AT', 'BE', 'EE', 'FR', 'DE', 'GR', 'IE', 'IT', 'NL', 'PL', 'PT', 'AU', 'NZ', 'HK', 'SG', 'CN' ), 'category_additional' => array(), ), array( 'id' => 'amazon_payments_advanced', 'title' => __( 'Amazon Pay', 'woocommerce' ), 'content' => __( 'Enable a familiar, fast checkout for hundreds of millions of active Amazon customers globally.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/amazonpay.png', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/amazonpay.png', 'plugins' => array( 'woocommerce-gateway-amazon-payments-advanced' ), 'is_visible' => array( self::get_rules_for_countries( array( 'US', 'AT', 'BE', 'CY', 'DK', 'ES', 'FR', 'DE', 'GB', 'HU', 'IE', 'IT', 'LU', 'NL', 'PT', 'SL', 'SE', 'JP', ) ), ), 'category_other' => array(), 'category_additional' => array( 'US', 'AT', 'BE', 'CY', 'DK', 'ES', 'FR', 'DE', 'GB', 'HU', 'IE', 'IT', 'LU', 'NL', 'PT', 'SL', 'SE', 'JP', ), ), array( 'id' => WC_Gateway_BACS::ID, 'title' => __( 'Direct bank transfer', 'woocommerce' ), 'content' => __( 'Take payments via bank transfer.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/bacs.svg', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/bacs.png', 'is_visible' => array( self::get_rules_for_cbd( false ), ), 'is_offline' => true, ), array( 'id' => WC_Gateway_COD::ID, 'title' => __( 'Cash on delivery', 'woocommerce' ), 'content' => __( 'Take payments in cash upon delivery.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/cod.svg', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/cod.png', 'is_visible' => array( self::get_rules_for_cbd( false ), ), 'is_offline' => true, ), array( 'id' => 'eway', 'title' => __( 'Eway', 'woocommerce' ), 'content' => __( 'The Eway extension for WooCommerce allows you to take credit card payments directly on your store without redirecting your customers to a third party site to make payment.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/eway.png', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/eway.png', 'plugins' => array( 'woocommerce-gateway-eway' ), 'is_visible' => false, 'category_other' => array(), 'category_additional' => array(), ), array( 'id' => 'kco', 'title' => __( 'Klarna Checkout', 'woocommerce' ), 'content' => __( 'Choose the payment that you want, pay now, pay later or slice it. No credit card numbers, no passwords, no worries.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/klarna-black.png', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/klarna.png', 'plugins' => array( 'klarna-checkout-for-woocommerce' ), 'is_visible' => array( self::get_rules_for_countries( array( 'NO', 'SE', 'FI', ) ), self::get_rules_for_cbd( false ), ), 'category_other' => array( 'NO', 'SE', 'FI', ), 'category_additional' => array(), ), array( 'id' => 'klarna_payments', 'title' => __( 'Klarna Payments', 'woocommerce' ), 'content' => __( 'Choose the payment that you want, pay now, pay later or slice it. No credit card numbers, no passwords, no worries.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/klarna-black.png', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/klarna.png', 'plugins' => array( 'klarna-payments-for-woocommerce' ), 'is_visible' => array( self::get_rules_for_countries( array( 'MX', 'US', 'CA', 'AT', 'BE', 'CH', 'DK', 'ES', 'FI', 'FR', 'DE', 'GB', 'IT', 'NL', 'NO', 'PL', 'SE', 'NZ', 'AU', ) ), self::get_rules_for_cbd( false ), (object) array( 'type' => 'or', 'operands' => array( (object) array( 'type' => 'not', 'operand' => array( self::get_rules_for_countries( self::get_wcpay_countries() ), ), ), self::get_rules_for_wcpay_activated( false ), self::get_rules_for_wcpay_connected( false ), ), ), ), 'category_other' => array(), 'category_additional' => array( 'MX', 'US', 'CA', 'AT', 'BE', 'CH', 'DK', 'ES', 'FI', 'FR', 'DE', 'GB', 'IT', 'NL', 'NO', 'PL', 'SE', 'NZ', 'AU', ), ), array( 'id' => 'mollie_wc_gateway_banktransfer', 'title' => __( 'Mollie', 'woocommerce' ), 'content' => __( 'Effortless payments by Mollie: Offer global and local payment methods, get onboarded in minutes, and supported in your language.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/mollie.svg', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/mollie.png', 'plugins' => array( 'mollie-payments-for-woocommerce' ), 'is_visible' => array( self::get_rules_for_countries( array( 'AT', 'BE', 'CH', 'ES', 'FI', 'FR', 'DE', 'GB', 'IT', 'NL', 'PL', ) ), ), 'category_other' => array( 'AT', 'BE', 'CH', 'ES', 'FI', 'FR', 'DE', 'GB', 'IT', 'NL', 'PL', ), 'category_additional' => array(), ), array( 'id' => 'payfast', 'title' => __( 'Payfast', 'woocommerce' ), 'content' => __( 'The Payfast extension for WooCommerce enables you to accept payments by Credit Card and EFT via one of South Africa’s most popular payment gateways. No setup fees or monthly subscription costs. Selecting this extension will configure your store to use South African rands as the selected currency.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/payfast.png', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/payfast.png', 'plugins' => array( 'woocommerce-payfast-gateway' ), 'is_visible' => array( self::get_rules_for_countries( array( 'ZA' ) ), self::get_rules_for_cbd( false ), ), 'category_other' => array( 'ZA' ), 'category_additional' => array(), ), array( 'id' => 'payoneer-checkout', 'title' => __( 'Payoneer Checkout', 'woocommerce' ), 'content' => __( 'Payoneer Checkout is the next generation of payment processing platforms, giving merchants around the world the solutions and direction they need to succeed in today’s hyper-competitive global market.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/payoneer.png', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/payoneer.png', 'plugins' => array( 'payoneer-checkout' ), 'is_visible' => array( self::get_rules_for_countries( array( 'HK', 'CN', ) ), ), 'category_other' => array(), 'category_additional' => array( 'HK', 'CN', ), ), array( 'id' => 'paystack', 'title' => __( 'Paystack', 'woocommerce' ), 'content' => __( 'Paystack helps African merchants accept one-time and recurring payments online with a modern, safe, and secure payment gateway.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/paystack.png', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/paystack.png', 'plugins' => array( 'woo-paystack' ), 'is_visible' => array( self::get_rules_for_countries( array( 'ZA', 'GH', 'NG' ) ), self::get_rules_for_cbd( false ), ), 'category_other' => array( 'ZA', 'GH', 'NG' ), 'category_additional' => array(), ), array( 'id' => 'payubiz', 'title' => __( 'PayU for WooCommerce', 'woocommerce' ), 'content' => __( 'Enable PayU’s exclusive plugin for WooCommerce to start accepting payments in 100+ payment methods available in India including credit cards, debit cards, UPI, & more!', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/payu.svg', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/payu.png', 'plugins' => array( 'payu-india' ), 'is_visible' => array( (object) array( 'type' => 'base_location_country', 'value' => 'IN', 'operation' => '=', ), self::get_rules_for_cbd( false ), ), 'category_other' => array( 'IN' ), 'category_additional' => array(), ), array( 'id' => 'ppcp-gateway', 'title' => __( 'PayPal Payments', 'woocommerce' ), 'content' => __( "Safe and secure payments using credit cards or your customer's PayPal account.", 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/paypal.png', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/paypal.png', 'plugins' => array( 'woocommerce-paypal-payments' ), 'is_visible' => array( self::get_rules_for_countries( array( 'US', 'CA', 'MX', 'BR', 'AR', 'CL', 'CO', 'EC', 'PE', 'UY', 'VE', 'AT', 'BE', 'BG', 'HR', 'CH', 'CY', 'CZ', 'DK', 'EE', 'ES', 'FI', 'FR', 'DE', 'GB', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SK', 'SL', 'SE', 'AU', 'NZ', 'HK', 'JP', 'SG', 'CN', 'ID', 'IN', ) ), self::get_rules_for_cbd( false ), ), 'category_other' => array( 'US', 'CA', 'MX', 'BR', 'AR', 'CL', 'CO', 'EC', 'PE', 'UY', 'VE', 'AT', 'BE', 'BG', 'HR', 'CH', 'CY', 'CZ', 'DK', 'EE', 'ES', 'FI', 'FR', 'DE', 'GB', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SK', 'SL', 'SE', 'AU', 'NZ', 'HK', 'JP', 'SG', 'CN', 'ID', ), 'category_additional' => array( 'US', 'CA', 'ZA', 'NG', 'GH', 'EC', 'VE', 'AR', 'CL', 'CO', 'PE', 'UY', 'MX', 'BR', 'AT', 'BE', 'BG', 'HR', 'CH', 'CY', 'CZ', 'DK', 'EE', 'ES', 'FI', 'FR', 'DE', 'GB', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SK', 'SL', 'SE', 'AU', 'NZ', 'HK', 'JP', 'SG', 'CN', 'ID', 'IN', ), ), array( 'id' => 'razorpay', 'title' => __( 'Razorpay', 'woocommerce' ), 'content' => __( 'The official Razorpay extension for WooCommerce allows you to accept credit cards, debit cards, netbanking, wallet, and UPI payments.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/razorpay.svg', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/razorpay.png', 'plugins' => array( 'woo-razorpay' ), 'is_visible' => array( (object) array( 'type' => 'base_location_country', 'value' => 'IN', 'operation' => '=', ), self::get_rules_for_cbd( false ), ), 'category_other' => array( 'IN' ), 'category_additional' => array(), ), array( 'id' => 'square_credit_card', 'title' => __( 'Square', 'woocommerce' ), 'content' => __( 'Securely accept credit and debit cards with one low rate, no surprise fees (custom rates available). Sell online and in store and track sales and inventory in one place.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/square-black.png', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/square.png', 'plugins' => array( 'woocommerce-square' ), 'is_visible' => array( (object) array( 'type' => 'or', 'operands' => (object) array( array( self::get_rules_for_countries( array( 'US' ) ), self::get_rules_for_cbd( true ), ), array( self::get_rules_for_countries( array( 'US', 'CA', 'IE', 'ES', 'FR', 'GB', 'AU', 'JP', ) ), (object) array( 'type' => 'or', 'operands' => (object) array( self::get_rules_for_selling_venues( array( 'brick-mortar', 'brick-mortar-other' ) ), self::get_rules_selling_offline(), ), ), ), ), ), ), 'category_other' => array( 'US', 'CA', 'IE', 'ES', 'FR', 'GB', 'AU', 'JP', ), 'category_additional' => array(), ), array( 'id' => 'stripe', 'title' => __( ' Stripe', 'woocommerce' ), 'content' => __( 'Accept debit and credit cards in 135+ currencies, methods such as Alipay, and one-touch checkout with Apple Pay.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/stripe.png', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/stripe.png', 'plugins' => array( 'woocommerce-gateway-stripe' ), 'is_visible' => array( // https://stripe.com/global. self::get_rules_for_countries( array( 'US', 'CA', 'MX', 'BR', 'AT', 'BE', 'BG', 'CH', 'CY', 'CZ', 'DK', 'EE', 'ES', 'FI', 'FR', 'DE', 'GB', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SK', 'SL', 'SE', 'AU', 'NZ', 'HK', 'JP', 'SG', 'ID', 'IN', ) ), self::get_rules_for_cbd( false ), ), 'category_other' => array( 'US', 'CA', 'MX', 'BR', 'AT', 'BE', 'BG', 'CH', 'CY', 'CZ', 'DK', 'EE', 'ES', 'FI', 'FR', 'DE', 'GB', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SK', 'SL', 'SE', 'AU', 'NZ', 'HK', 'JP', 'SG', 'ID', 'IN', ), 'category_additional' => array(), ), array( 'id' => 'woo-mercado-pago-custom', 'title' => __( 'Mercado Pago', 'woocommerce' ), 'content' => __( 'Set up your payment methods and accept credit and debit cards, cash, bank transfers and money from your Mercado Pago account. Offer safe and secure payments with Latin America’s leading processor.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/mercadopago.png', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/mercadopago.png', 'plugins' => array( 'woocommerce-mercadopago' ), 'is_visible' => array( self::get_rules_for_countries( array( 'AR', 'CL', 'CO', 'EC', 'PE', 'UY', 'MX', 'BR', ) ), ), 'is_local_partner' => true, 'category_other' => array( 'AR', 'CL', 'CO', 'EC', 'PE', 'UY', 'MX', 'BR', ), 'category_additional' => array(), ), // This is for backwards compatibility only (WC < 5.10.0-dev or WCA < 2.9.0-dev). array( 'id' => 'woocommerce_payments', 'title' => __( 'WooPayments', 'woocommerce' ), 'content' => __( 'Manage transactions without leaving your WordPress Dashboard. Only with WooPayments.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg', 'plugins' => array( 'woocommerce-payments' ), 'description' => __( 'With WooPayments, you can securely accept major cards, Apple Pay, and payments in over 100 currencies. Track cash flow and manage recurring revenue directly from your store’s dashboard - with no setup costs or monthly fees.', 'woocommerce' ), 'is_visible' => array( self::get_rules_for_cbd( false ), self::get_rules_for_countries( self::get_wcpay_countries() ), (object) array( 'type' => 'plugin_version', 'plugin' => 'woocommerce', 'version' => '5.10.0-dev', 'operator' => '<', ), (object) array( 'type' => 'or', 'operands' => (object) array( (object) array( 'type' => 'not', 'operand' => array( (object) array( 'type' => 'plugins_activated', 'plugins' => array( 'woocommerce-admin' ), ), ), ), (object) array( 'type' => 'plugin_version', 'plugin' => 'woocommerce-admin', 'version' => '2.9.0-dev', 'operator' => '<', ), ), ), ), ), array( 'id' => 'woocommerce_payments:without-in-person-payments', 'title' => __( 'WooPayments', 'woocommerce' ), 'content' => __( 'Manage transactions without leaving your WordPress Dashboard. Only with WooPayments.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg', 'plugins' => array( 'woocommerce-payments' ), 'description' => __( 'With WooPayments, you can securely accept major cards, Apple Pay, and payments in over 100 currencies. Track cash flow and manage recurring revenue directly from your store’s dashboard - with no setup costs or monthly fees.', 'woocommerce' ), 'is_visible' => array( self::get_rules_for_cbd( false ), self::get_rules_for_countries( array_diff( self::get_wcpay_countries(), array( 'US', 'CA' ) ) ), (object) array( 'type' => 'or', // Older versions of WooCommerce Admin require the ID to be `woocommerce-payments` to show the suggestion card. 'operands' => (object) array( (object) array( 'type' => 'plugin_version', 'plugin' => 'woocommerce-admin', 'version' => '2.9.0-dev', 'operator' => '>=', ), (object) array( 'type' => 'plugin_version', 'plugin' => 'woocommerce', 'version' => '5.10.0-dev', 'operator' => '>=', ), ), ), ), ), // This is the same as the above, but with a different description for countries that support in-person payments such as US and CA. array( 'id' => 'woocommerce_payments:with-in-person-payments', 'title' => __( 'WooPayments', 'woocommerce' ), 'content' => __( 'Manage transactions without leaving your WordPress Dashboard. Only with WooPayments.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg', 'plugins' => array( 'woocommerce-payments' ), 'description' => __( 'With WooPayments, you can securely accept major cards, Apple Pay, and payments in over 100 currencies – with no setup costs or monthly fees – and you can now accept in-person payments with the Woo mobile app.', 'woocommerce' ), 'is_visible' => array( self::get_rules_for_cbd( false ), self::get_rules_for_countries( array( 'US', 'CA' ) ), (object) array( 'type' => 'or', // Older versions of WooCommerce Admin require the ID to be `woocommerce-payments` to show the suggestion card. 'operands' => (object) array( (object) array( 'type' => 'plugin_version', 'plugin' => 'woocommerce-admin', 'version' => '2.9.0-dev', 'operator' => '>=', ), (object) array( 'type' => 'plugin_version', 'plugin' => 'woocommerce', 'version' => '5.10.0-dev', 'operator' => '>=', ), ), ), ), ), array( 'id' => 'woocommerce_payments:bnpl', 'title' => __( 'Activate BNPL instantly on WooPayments', 'woocommerce' ), 'content' => __( 'The world’s favorite buy now, pay later options and many more are right at your fingertips with WooPayments — all from one dashboard, without needing multiple extensions and logins.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay-bnpl.svg', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay-bnpl.svg', 'plugins' => array( 'woocommerce-payments' ), 'is_visible' => array( self::get_rules_for_countries( array_intersect( array( 'US', 'CA', 'AU', 'AT', 'BE', 'CH', 'DK', 'ES', 'FI', 'FR', 'DE', 'GB', 'IT', 'NL', 'NO', 'PL', 'SE', 'NZ', ), self::get_wcpay_countries() ), ), self::get_rules_for_cbd( false ), self::get_rules_for_wcpay_activated( true ), self::get_rules_for_wcpay_connected( true ), ), ), array( 'id' => 'zipmoney', 'title' => __( 'Zip Co - Buy Now, Pay Later', 'woocommerce' ), 'content' => __( 'Give your customers the power to pay later, interest free and watch your sales grow.', 'woocommerce' ), 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/zipco.png', 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/zipco.png', 'plugins' => array( 'zipmoney-payments-woocommerce' ), 'is_visible' => false, 'category_other' => array(), 'category_additional' => array(), ), ); $base_location = wc_get_base_location(); $country = $base_location['country']; foreach ( $payment_gateways as $index => $payment_gateway ) { $payment_gateways[ $index ]['recommendation_priority'] = self::get_recommendation_priority( $payment_gateway['id'], $country ); } return $payment_gateways; } /** * Get array of countries supported by WCPay depending on feature flag. * * @return array Array of countries. */ public static function get_wcpay_countries() { return array( 'US', 'PR', 'AU', 'CA', 'CY', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'IE', 'IT', 'LU', 'LT', 'LV', 'NO', 'NZ', 'MT', 'AT', 'BE', 'NL', 'PL', 'PT', 'CH', 'HK', 'SI', 'SK', 'SG', 'BG', 'CZ', 'HR', 'HU', 'RO', 'SE', 'JP', 'AE' ); } /** * Get rules that match the store base location to one of the provided countries. * * @param array $countries Array of countries to match. * @return object Rules to match. */ public static function get_rules_for_countries( $countries ) { $rules = array(); foreach ( $countries as $country ) { $rules[] = (object) array( 'type' => 'base_location_country', 'value' => $country, 'operation' => '=', ); } return (object) array( 'type' => 'or', 'operands' => $rules, ); } /** * Get rules that match the store's selling venues. * * @param array $selling_venues Array of venues to match. * @return object Rules to match. */ public static function get_rules_for_selling_venues( $selling_venues ) { $rules = array(); foreach ( $selling_venues as $venue ) { $rules[] = (object) array( 'type' => 'option', 'transformers' => array( (object) array( 'use' => 'dot_notation', 'arguments' => (object) array( 'path' => 'selling_venues', ), ), ), 'option_name' => 'woocommerce_onboarding_profile', 'operation' => '=', 'value' => $venue, 'default' => array(), ); } return (object) array( 'type' => 'or', 'operands' => $rules, ); } /** * Get rules for when selling offline for core profiler. * * @return object Rules to match. */ public static function get_rules_selling_offline() { return (object) array( 'type' => 'option', 'transformers' => array( (object) array( 'use' => 'dot_notation', 'arguments' => (object) array( 'path' => 'selling_online_answer', ), ), ), 'option_name' => 'woocommerce_onboarding_profile', 'operation' => 'in', 'value' => array( 'no_im_selling_offline', 'im_selling_both_online_and_offline' ), 'default' => '', ); } /** * Get default rules for CBD based on given argument. * * @param bool $should_have Whether or not the store should have CBD as an industry (true) or not (false). * @return object Rules to match. */ public static function get_rules_for_cbd( $should_have ) { return (object) array( 'type' => 'option', 'transformers' => array( (object) array( 'use' => 'dot_notation', 'arguments' => (object) array( 'path' => 'industry', ), ), (object) array( 'use' => 'array_column', 'arguments' => (object) array( 'key' => 'slug', ), ), ), 'option_name' => 'woocommerce_onboarding_profile', 'operation' => $should_have ? 'contains' : '!contains', 'value' => 'cbd-other-hemp-derived-products', 'default' => array(), ); } /** * Get default rules for the WooPayments plugin being installed and activated. * * @param bool $should_be Whether WooPayments should be activated. * * @return object Rules to match. */ public static function get_rules_for_wcpay_activated( $should_be ) { $active_rule = (object) array( 'type' => 'plugins_activated', 'plugins' => array( 'woocommerce-payments' ), ); if ( $should_be ) { return $active_rule; } return (object) array( 'type' => 'not', 'operand' => array( $active_rule ), ); } /** * Get default rules for WooPayments being connected or not. * * This does not include the check for the WooPayments plugin to be active. * * @param bool $should_be Whether WooPayments should be connected. * * @return object Rules to match. */ public static function get_rules_for_wcpay_connected( $should_be ) { return (object) array( 'type' => 'option', 'transformers' => array( // Extract only the 'data' key from the option. (object) array( 'use' => 'dot_notation', 'arguments' => (object) array( 'path' => 'data', ), ), // Extract the keys from the data array. (object) array( 'use' => 'array_keys', ), ), 'option_name' => 'wcpay_account_data', // The rule will be look for the 'account_id' key in the account data array. 'operation' => $should_be ? 'contains' : '!contains', 'value' => 'account_id', 'default' => array(), ); } /** * Get recommendation priority for a given payment gateway by id and country. * If country is not supported, return null. * * @param string $gateway_id Payment gateway id. * @param string $country_code Store country code. * @return int|null Priority. Priority is 0-indexed, so 0 is the highest priority. */ private static function get_recommendation_priority( $gateway_id, $country_code ) { $recommendation_priority_map = array( 'US' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'square_credit_card', 'amazon_payments_advanced', 'affirm', 'afterpay', 'klarna_payments', ), 'CA' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'square_credit_card', 'affirm', 'afterpay', 'klarna_payments', ), 'AT' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'airwallex_main', 'mollie_wc_gateway_banktransfer', 'klarna_payments', 'amazon_payments_advanced', ), 'BE' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'airwallex_main', 'mollie_wc_gateway_banktransfer', 'klarna_payments', 'amazon_payments_advanced', ), 'BG' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', ), 'HR' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'ppcp-gateway', ), 'CH' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'mollie_wc_gateway_banktransfer', 'klarna_payments', ), 'CY' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'amazon_payments_advanced', ), 'CZ' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', ), 'DK' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'klarna_payments', 'amazon_payments_advanced', ), 'EE' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'airwallex_main', ), 'ES' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'mollie_wc_gateway_banktransfer', 'square_credit_card', 'klarna_payments', 'amazon_payments_advanced', ), 'FI' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'mollie_wc_gateway_banktransfer', 'kco', 'klarna_payments', ), 'FR' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'airwallex_main', 'mollie_wc_gateway_banktransfer', 'square_credit_card', 'klarna_payments', 'amazon_payments_advanced', ), 'DE' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'airwallex_main', 'mollie_wc_gateway_banktransfer', 'klarna_payments', 'amazon_payments_advanced', ), 'GB' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'airwallex_main', 'mollie_wc_gateway_banktransfer', 'square_credit_card', 'klarna_payments', 'amazon_payments_advanced', ), 'GR' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'airwallex_main', ), 'HU' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'amazon_payments_advanced', ), 'IE' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'airwallex_main', 'square_credit_card', 'amazon_payments_advanced', ), 'IT' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'airwallex_main', 'mollie_wc_gateway_banktransfer', 'klarna_payments', 'amazon_payments_advanced', ), 'LV' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', ), 'LT' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', ), 'LU' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'amazon_payments_advanced', ), 'MT' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', ), 'NL' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'airwallex_main', 'mollie_wc_gateway_banktransfer', 'klarna_payments', 'amazon_payments_advanced', ), 'NO' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'kco', 'klarna_payments', ), 'PL' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'airwallex_main', 'mollie_wc_gateway_banktransfer', 'klarna_payments', ), 'PT' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'airwallex_main', 'amazon_payments_advanced', ), 'RO' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', ), 'SK' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', ), 'SL' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'amazon_payments_advanced', ), 'SE' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'kco', 'klarna_payments', 'amazon_payments_advanced', ), 'MX' => array( 'stripe', 'woo-mercado-pago-custom', 'ppcp-gateway', 'klarna_payments', ), 'BR' => array( 'stripe', 'woo-mercado-pago-custom', 'ppcp-gateway' ), 'AR' => array( 'woo-mercado-pago-custom', 'ppcp-gateway' ), 'BO' => array(), 'CL' => array( 'woo-mercado-pago-custom', 'ppcp-gateway' ), 'CO' => array( 'woo-mercado-pago-custom', 'ppcp-gateway' ), 'EC' => array( 'woo-mercado-pago-custom', 'ppcp-gateway' ), 'FK' => array(), 'GF' => array(), 'GY' => array(), 'PY' => array(), 'PE' => array( 'woo-mercado-pago-custom', 'ppcp-gateway' ), 'SR' => array(), 'UY' => array( 'woo-mercado-pago-custom', 'ppcp-gateway' ), 'VE' => array( 'ppcp-gateway' ), 'AU' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'airwallex_main', 'ppcp-gateway', 'square_credit_card', 'afterpay', 'klarna_payments', ), 'NZ' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'airwallex_main', 'ppcp-gateway', 'klarna_payments', ), 'HK' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'airwallex_main', 'ppcp-gateway', 'payoneer-checkout', ), 'JP' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'square_credit_card', 'amazon_payments_advanced', ), 'SG' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', 'stripe', 'airwallex_main', 'ppcp-gateway', ), 'CN' => array( 'airwallex_main', 'ppcp-gateway', 'payoneer-checkout' ), 'FJ' => array(), 'GU' => array(), 'ID' => array( 'stripe', 'ppcp-gateway' ), 'IN' => array( 'stripe', 'razorpay', 'payubiz', 'ppcp-gateway' ), 'ZA' => array( 'payfast', 'paystack' ), 'NG' => array( 'paystack' ), 'GH' => array( 'paystack' ), 'AE' => array( 'woocommerce_payments:with-in-person-payments', 'woocommerce_payments:without-in-person-payments', 'woocommerce_payments', ), ); // If the country code is not in the list, return default priority. if ( ! isset( $recommendation_priority_map[ $country_code ] ) ) { return self::get_default_recommendation_priority( $gateway_id ); } $index = array_search( $gateway_id, $recommendation_priority_map[ $country_code ], true ); // If the gateway is not in the list, return the last index + 1. if ( false === $index ) { return count( $recommendation_priority_map[ $country_code ] ); } return $index; } /** * Get the default recommendation priority for a payment gateway. * This is used when a country is not in the $recommendation_priority_map array. * * @param string $id Payment gateway id. * @return int Priority. */ private static function get_default_recommendation_priority( $id ) { if ( ! $id || ! array_key_exists( $id, self::$recommendation_priority ) ) { return null; } return self::$recommendation_priority[ $id ]; } } PaymentGatewaySuggestions/PaymentGatewaysController.php 0000777 00000010671 15253027022 0017647 0 ustar 00 <?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() ); } } PaymentGatewaySuggestions/Init.php 0000777 00000010177 15253027022 0013365 0 ustar 00 <?php /** * Handles running payment gateway suggestion specs */ namespace Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions; defined( 'ABSPATH' ) || exit; 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; } /** * Gets either cached or default suggestions. * * @return array */ public static function get_cached_or_default_suggestions() { $specs = 'no' === get_option( 'woocommerce_show_marketplace_suggestions', 'yes' ) ? DefaultPaymentGateways::get_all() : PaymentGatewaySuggestionsDataSourcePoller::get_instance()->get_cached_specs(); if ( ! is_array( $specs ) || 0 === count( $specs ) ) { $specs = DefaultPaymentGateways::get_all(); } /** * Allows filtering of payment gateway suggestion specs * * @since 6.4.0 * * @param array Gateway specs. */ $specs = apply_filters( 'woocommerce_admin_payment_gateway_suggestion_specs', $specs ); $results = EvaluateSuggestion::evaluate_specs( $specs ); return $results['suggestions']; } /** * 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' ); } } PaymentGatewaySuggestions/PaymentGatewaySuggestionsDataSourcePoller.php 0000777 00000002716 15253027022 0023005 0 ustar 00 <?php namespace Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions; use Automattic\WooCommerce\Admin\RemoteSpecs\DataSourcePoller; use WC_Helper; /** * 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. * * @deprecated since 9.5.0. Use get_data_sources() instead. */ const DATA_SOURCES = array(); /** * Class instance. * * @var PaymentGatewaySuggestionsDataSourcePoller instance */ protected static $instance = null; /** * Get class instance. */ public static function get_instance() { if ( ! self::$instance ) { self::$instance = new self( self::ID, self::get_data_sources() ); } return self::$instance; } /** * Get data sources with dynamic base URL. * * @return array */ public static function get_data_sources() { $data_sources = array( WC_Helper::get_woocommerce_com_base_url() . 'wp-json/wccom/payment-gateway-suggestions/2.0/suggestions.json', ); // Add country query param to data sources. $base_location = wc_get_base_location(); $data_sources_with_country = array_map( function ( $url ) use ( $base_location ) { return add_query_arg( 'country', $base_location['country'], $url ); }, $data_sources ); return $data_sources_with_country; } } PaymentGatewaySuggestions/EvaluateSuggestion.php 0000777 00000006526 15253027022 0016303 0 ustar 00 <?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 { /** * Stores memoized results of evaluate_specs. * * @var array */ protected static $memo = array(); /** * Evaluates the spec and returns the suggestion. * * @param object|array $spec The suggestion to evaluate. * @param array $logger_args Optional. Arguments for the rule evaluator logger. * * @return object The evaluated suggestion. */ public static function evaluate( $spec, $logger_args = array() ) { $rule_evaluator = new RuleEvaluator(); $suggestion = is_array( $spec ) ? (object) $spec : clone $spec; if ( isset( $suggestion->is_visible ) ) { // Determine the suggestion's logger slug. $logger_slug = ! empty( $suggestion->id ) ? $suggestion->id : ''; // If the suggestion has no ID, use the title to generate a slug. if ( empty( $logger_slug ) ) { $logger_slug = ! empty( $suggestion->title ) ? sanitize_title_with_dashes( trim( $suggestion->title ) ) : 'anonymous-suggestion'; } // Evaluate the visibility of the suggestion. $is_visible = $rule_evaluator->evaluate( $suggestion->is_visible, null, array( 'slug' => $logger_slug, 'source' => $logger_args['source'] ?? 'wc-payment-gateway-suggestions', ) ); $suggestion->is_visible = $is_visible; } return $suggestion; } /** * Evaluates the specs and returns the visible suggestions. * * @param array $specs payment suggestion spec array. * @param array $logger_args Optional. Arguments for the rule evaluator logger. * * @return array The visible suggestions and errors. */ public static function evaluate_specs( $specs, $logger_args = array() ) { $specs_key = self::get_memo_key( $specs ); if ( isset( self::$memo[ $specs_key ] ) ) { return self::$memo[ $specs_key ]; } $suggestions = array(); $errors = array(); foreach ( $specs as $spec ) { try { $suggestion = self::evaluate( $spec, $logger_args ); if ( ! property_exists( $suggestion, 'is_visible' ) || $suggestion->is_visible ) { $suggestions[] = $suggestion; } } catch ( \Throwable $e ) { $errors[] = $e; } } $result = array( 'suggestions' => $suggestions, 'errors' => $errors, ); // Memoize results, with a fail safe to prevent unbounded memory growth. // This limit is unlikely to be reached under normal circumstances. if ( count( self::$memo ) > 50 ) { self::reset_memo(); } self::$memo[ $specs_key ] = $result; return $result; } /** * Resets the memoized results. Useful for testing. */ public static function reset_memo() { self::$memo = array(); } /** * Returns a memoization key for the given specs. * * @param array $specs The specs to generate a key for. * * @return string The memoization key. */ private static function get_memo_key( $specs ) { $data = wp_json_encode( $specs ); if ( function_exists( 'hash' ) && in_array( 'xxh3', hash_algos(), true ) ) { // Use xxHash (xxh3) if available. return hash( 'xxh3', $data ); } // Fall back to CRC32. return (string) crc32( $data ); } } TransientNotices.php 0000777 00000005450 15253027022 0010562 0 ustar 00 <?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; } }
| ver. 1.6 |
Github
|
.
| PHP 8.1.34 | Генерация страницы: 0 |
proxy
|
phpinfo
|
Настройка