Agentic
4 months ago
BlockTemplates
4 weeks ago
EmailImprovements
1 year ago
EmailPreview
4 weeks ago
Emails
1 year ago
ImportExport
1 year ago
Logging
1 year ago
Marketing
2 years ago
Notes
4 weeks ago
Onboarding
5 months ago
Orders
4 weeks ago
ProductForm
2 years ago
ProductReviews
4 weeks ago
RemoteFreeExtensions
4 weeks ago
Schedulers
4 weeks ago
Settings
5 days ago
Suggestions
4 weeks ago
WCPayPromotion
10 months ago
ActivityPanels.php
3 years ago
Analytics.php
4 weeks ago
CategoryLookup.php
4 years ago
Coupons.php
1 year ago
CouponsMovedTrait.php
5 months ago
CustomerEffortScoreTracks.php
7 months ago
Events.php
2 months ago
FeaturePlugin.php
4 months ago
Homescreen.php
1 month ago
Loader.php
4 weeks ago
Marketing.php
1 year ago
Marketplace.php
5 months ago
MobileAppBanner.php
4 years ago
OrderMilestoneEasterEgg.php
4 weeks ago
RemoteInboxNotifications.php
3 years ago
Settings.php
5 days ago
ShippingLabelBanner.php
1 year ago
ShippingLabelBannerDisplayRules.php
1 year ago
SiteHealth.php
3 years ago
Survey.php
4 years ago
SystemStatusReport.php
1 year ago
Translations.php
1 year ago
WCAdminAssets.php
5 days ago
WCAdminSharedSettings.php
1 year ago
WCAdminUser.php
9 months ago
WcPayWelcomePage.php
1 year ago
Settings.php
446 lines
| 1 | <?php |
| 2 | /** |
| 3 | * WooCommerce Settings. |
| 4 | */ |
| 5 | |
| 6 | namespace Automattic\WooCommerce\Internal\Admin; |
| 7 | |
| 8 | use Automattic\WooCommerce\Admin\API\Plugins; |
| 9 | use Automattic\WooCommerce\Admin\API\Reports\Orders\DataStore as OrdersDataStore; |
| 10 | use Automattic\WooCommerce\Admin\Features\Features; |
| 11 | use Automattic\WooCommerce\Admin\PageController; |
| 12 | use Automattic\WooCommerce\Admin\PluginsHelper; |
| 13 | use Automattic\WooCommerce\Internal\Admin\Settings\SettingsUIRequestContext; |
| 14 | use Automattic\WooCommerce\Utilities\FeaturesUtil; |
| 15 | use Automattic\WooCommerce\Utilities\OrderUtil; |
| 16 | use WC_Marketplace_Suggestions; |
| 17 | |
| 18 | /** |
| 19 | * Contains logic in regards to WooCommerce Admin Settings. |
| 20 | */ |
| 21 | class Settings { |
| 22 | |
| 23 | /** |
| 24 | * Class instance. |
| 25 | * |
| 26 | * @var Settings instance |
| 27 | */ |
| 28 | protected static $instance = null; |
| 29 | |
| 30 | /** |
| 31 | * Get class instance. |
| 32 | */ |
| 33 | public static function get_instance() { |
| 34 | if ( ! self::$instance ) { |
| 35 | self::$instance = new self(); |
| 36 | } |
| 37 | return self::$instance; |
| 38 | } |
| 39 | |
| 40 | /** |
| 41 | * Hook into WooCommerce. |
| 42 | */ |
| 43 | public function __construct() { |
| 44 | // Old settings injection. |
| 45 | add_filter( 'woocommerce_components_settings', array( $this, 'add_component_settings' ) ); |
| 46 | // New settings injection. |
| 47 | add_filter( 'woocommerce_admin_shared_settings', array( $this, 'add_component_settings' ) ); |
| 48 | add_filter( 'woocommerce_settings_groups', array( $this, 'add_settings_group' ) ); |
| 49 | add_filter( 'woocommerce_settings-wc_admin', array( $this, 'add_settings' ) ); |
| 50 | } |
| 51 | |
| 52 | /** |
| 53 | * Format order statuses by removing a leading 'wc-' if present. |
| 54 | * |
| 55 | * @param array $statuses Order statuses. |
| 56 | * @return array formatted statuses. |
| 57 | */ |
| 58 | public static function get_order_statuses( $statuses ) { |
| 59 | $formatted_statuses = array(); |
| 60 | foreach ( $statuses as $key => $value ) { |
| 61 | $formatted_key = preg_replace( '/^wc-/', '', $key ); |
| 62 | $formatted_statuses[ $formatted_key ] = $value; |
| 63 | } |
| 64 | return $formatted_statuses; |
| 65 | } |
| 66 | |
| 67 | /** |
| 68 | * Get all order statuses present in analytics tables that aren't registered. |
| 69 | * |
| 70 | * @return array Unregistered order statuses. |
| 71 | */ |
| 72 | private function get_unregistered_order_statuses() { |
| 73 | $registered_statuses = wc_get_order_statuses(); |
| 74 | $all_synced_statuses = OrdersDataStore::get_all_statuses(); |
| 75 | $unregistered_statuses = array_diff( $all_synced_statuses, array_keys( $registered_statuses ) ); |
| 76 | $formatted_status_keys = self::get_order_statuses( array_fill_keys( $unregistered_statuses, '' ) ); |
| 77 | $formatted_statuses = array_keys( $formatted_status_keys ); |
| 78 | |
| 79 | return array_combine( $formatted_statuses, $formatted_statuses ); |
| 80 | } |
| 81 | |
| 82 | /** |
| 83 | * Return an object defining the currency options for the site's current currency |
| 84 | * |
| 85 | * @return array Settings for the current currency { |
| 86 | * Array of settings. |
| 87 | * |
| 88 | * @type string $code Currency code. |
| 89 | * @type string $precision Number of decimals. |
| 90 | * @type string $symbol Symbol for currency. |
| 91 | * } |
| 92 | */ |
| 93 | public static function get_currency_settings() { |
| 94 | $code = get_woocommerce_currency(); |
| 95 | |
| 96 | /** |
| 97 | * The wc_currency_settings hook |
| 98 | * |
| 99 | * @since 6.5.0 |
| 100 | */ |
| 101 | return apply_filters( |
| 102 | 'wc_currency_settings', |
| 103 | array( |
| 104 | 'code' => $code, |
| 105 | 'precision' => wc_get_price_decimals(), |
| 106 | 'symbol' => html_entity_decode( get_woocommerce_currency_symbol( $code ) ), |
| 107 | 'symbolPosition' => get_option( 'woocommerce_currency_pos' ), |
| 108 | 'decimalSeparator' => wc_get_price_decimal_separator(), |
| 109 | 'thousandSeparator' => wc_get_price_thousand_separator(), |
| 110 | 'priceFormat' => html_entity_decode( get_woocommerce_price_format() ), |
| 111 | ) |
| 112 | ); |
| 113 | } |
| 114 | |
| 115 | /** |
| 116 | * Hooks extra necessary data into the component settings array already set in WooCommerce core. |
| 117 | * |
| 118 | * @param array $settings Array of component settings. |
| 119 | * @return array Array of component settings. |
| 120 | */ |
| 121 | public function add_component_settings( $settings ) { |
| 122 | if ( ! is_admin() ) { |
| 123 | return $settings; |
| 124 | } |
| 125 | |
| 126 | if ( ! function_exists( 'wc_blocks_container' ) ) { |
| 127 | global $wp_locale; |
| 128 | // inject data not available via older versions of wc_blocks/woo. |
| 129 | $settings['orderStatuses'] = self::get_order_statuses( wc_get_order_statuses() ); |
| 130 | $settings['stockStatuses'] = self::get_order_statuses( wc_get_product_stock_status_options() ); |
| 131 | $settings['currency'] = self::get_currency_settings(); |
| 132 | $settings['locale'] = array( |
| 133 | 'siteLocale' => isset( $settings['siteLocale'] ) |
| 134 | ? $settings['siteLocale'] |
| 135 | : get_locale(), |
| 136 | 'userLocale' => isset( $settings['l10n']['userLocale'] ) |
| 137 | ? $settings['l10n']['userLocale'] |
| 138 | : get_user_locale(), |
| 139 | 'weekdaysShort' => isset( $settings['l10n']['weekdaysShort'] ) |
| 140 | ? $settings['l10n']['weekdaysShort'] |
| 141 | : array_values( $wp_locale->weekday_abbrev ), |
| 142 | ); |
| 143 | } |
| 144 | |
| 145 | //phpcs:ignore |
| 146 | $preload_data_endpoints = apply_filters( 'woocommerce_component_settings_preload_endpoints', array() ); |
| 147 | $preload_data_endpoints['jetpackStatus'] = '/jetpack/v4/connection'; |
| 148 | if ( ! empty( $preload_data_endpoints ) ) { |
| 149 | $preload_data = array_reduce( |
| 150 | array_values( $preload_data_endpoints ), |
| 151 | 'rest_preload_api_request' |
| 152 | ); |
| 153 | } |
| 154 | |
| 155 | //phpcs:ignore |
| 156 | $preload_options = apply_filters( 'woocommerce_admin_preload_options', array() ); |
| 157 | if ( ! empty( $preload_options ) ) { |
| 158 | // Prime caches to reduce future queries. |
| 159 | wp_prime_option_caches( $preload_options ); |
| 160 | foreach ( $preload_options as $option ) { |
| 161 | $settings['preloadOptions'][ $option ] = get_option( $option ); |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | //phpcs:ignore |
| 166 | $preload_settings = apply_filters( 'woocommerce_admin_preload_settings', array() ); |
| 167 | if ( ! empty( $preload_settings ) ) { |
| 168 | $setting_options = new \WC_REST_Setting_Options_V2_Controller(); |
| 169 | foreach ( $preload_settings as $group ) { |
| 170 | $group_settings = $setting_options->get_group_settings( $group ); |
| 171 | $preload_settings = array(); |
| 172 | foreach ( $group_settings as $option ) { |
| 173 | if ( array_key_exists( 'id', $option ) && array_key_exists( 'value', $option ) ) { |
| 174 | $preload_settings[ $option['id'] ] = $option['value']; |
| 175 | } |
| 176 | } |
| 177 | $settings['preloadSettings'][ $group ] = $preload_settings; |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | $settings['currentUserData'] = WCAdminUser::get_user_data(); |
| 182 | $settings['reviewsEnabled'] = get_option( 'woocommerce_enable_reviews' ); |
| 183 | $settings['manageStock'] = get_option( 'woocommerce_manage_stock' ); |
| 184 | $settings['commentModeration'] = get_option( 'comment_moderation' ); |
| 185 | $settings['notifyLowStockAmount'] = get_option( 'woocommerce_notify_low_stock_amount' ); |
| 186 | |
| 187 | /** |
| 188 | * Deprecate wcAdminAssetUrl as we no longer need it after The Merge. |
| 189 | * Use wcAssetUrl instead. |
| 190 | * |
| 191 | * @deprecated 6.7.0 |
| 192 | * @var string |
| 193 | */ |
| 194 | $settings['wcAdminAssetUrl'] = WC_ADMIN_IMAGES_FOLDER_URL; |
| 195 | $settings['wcVersion'] = WC_VERSION; |
| 196 | $settings['siteUrl'] = site_url(); |
| 197 | $settings['shopUrl'] = get_permalink( wc_get_page_id( 'shop' ) ); |
| 198 | $settings['homeUrl'] = home_url(); |
| 199 | $settings['dateFormat'] = get_option( 'date_format' ); |
| 200 | $settings['timeZone'] = wc_timezone_string(); |
| 201 | $settings['plugins'] = array( |
| 202 | 'installedPlugins' => PluginsHelper::get_installed_plugin_slugs(), |
| 203 | 'activePlugins' => Plugins::get_active_plugins(), |
| 204 | ); |
| 205 | |
| 206 | // DO NOT use outside of core, these can be removed without deprecation. |
| 207 | $settings['__experimentalFlags'] = array(); |
| 208 | |
| 209 | // Plugins that depend on changing the translation work on the server but not the client - |
| 210 | // WooCommerce Branding is an example of this - so pass through the translation of |
| 211 | // 'WooCommerce' to wcSettings. |
| 212 | $settings['woocommerceTranslation'] = __( 'WooCommerce', 'woocommerce' ); |
| 213 | |
| 214 | if ( PageController::is_admin_page() && Features::is_enabled( 'analytics' ) ) { |
| 215 | // We may have synced orders with a now-unregistered status. |
| 216 | // E.g. an extension that added statuses is now inactive or removed. |
| 217 | $settings['unregisteredOrderStatuses'] = $this->get_unregistered_order_statuses(); |
| 218 | $settings['usesNewFullRefundData'] = OrderUtil::uses_new_full_refund_data(); |
| 219 | } |
| 220 | |
| 221 | // The separator used for attributes found in Variation titles. |
| 222 | //phpcs:ignore |
| 223 | $settings['variationTitleAttributesSeparator'] = apply_filters( 'woocommerce_product_variation_title_attributes_separator', ' - ', new \WC_Product() ); |
| 224 | |
| 225 | $settings = $this->add_settings_ui_schema( $settings ); |
| 226 | if ( ! empty( $preload_data_endpoints ) ) { |
| 227 | $settings['dataEndpoints'] = isset( $settings['dataEndpoints'] ) |
| 228 | ? $settings['dataEndpoints'] |
| 229 | : array(); |
| 230 | foreach ( $preload_data_endpoints as $key => $endpoint ) { |
| 231 | // Handle error case: rest_do_request() doesn't guarantee success. |
| 232 | if ( empty( $preload_data[ $endpoint ] ) ) { |
| 233 | $settings['dataEndpoints'][ $key ] = array(); |
| 234 | } else { |
| 235 | $settings['dataEndpoints'][ $key ] = $preload_data[ $endpoint ]['body']; |
| 236 | } |
| 237 | } |
| 238 | } |
| 239 | $settings = $this->get_custom_settings( $settings ); |
| 240 | if ( PageController::is_embed_page() ) { |
| 241 | $settings['embedBreadcrumbs'] = wc_admin_get_breadcrumbs(); |
| 242 | } |
| 243 | |
| 244 | $settings['allowMarketplaceSuggestions'] = WC_Marketplace_Suggestions::allow_suggestions(); |
| 245 | $settings['connectNonce'] = wp_create_nonce( 'connect' ); |
| 246 | $settings['wcpay_welcome_page_connect_nonce'] = wp_create_nonce( 'wcpay-connect' ); |
| 247 | $settings['email_preview_nonce'] = wp_create_nonce( 'email-preview-nonce' ); |
| 248 | $settings['email_listing_nonce'] = wp_create_nonce( 'email-listing-nonce' ); |
| 249 | $settings['wc_helper_nonces'] = array( |
| 250 | 'refresh' => wp_create_nonce( 'refresh' ), |
| 251 | ); |
| 252 | |
| 253 | $settings['features'] = $this->get_features(); |
| 254 | |
| 255 | $has_gutenberg = is_plugin_active( 'gutenberg/gutenberg.php' ); |
| 256 | $gutenberg_version = ''; |
| 257 | if ( $has_gutenberg ) { |
| 258 | if ( defined( 'GUTENBERG_VERSION' ) ) { |
| 259 | $gutenberg_version = GUTENBERG_VERSION; |
| 260 | } |
| 261 | |
| 262 | if ( ! $gutenberg_version ) { |
| 263 | $gutenberg_data = get_plugin_data( WP_PLUGIN_DIR . '/gutenberg/gutenberg.php' ); |
| 264 | $gutenberg_version = $gutenberg_data['Version']; |
| 265 | } |
| 266 | } |
| 267 | $settings['gutenberg_version'] = $has_gutenberg ? $gutenberg_version : 0; |
| 268 | |
| 269 | return $settings; |
| 270 | } |
| 271 | |
| 272 | /** |
| 273 | * Removes non-necessary feature properties for the client side. |
| 274 | * |
| 275 | * @return array |
| 276 | */ |
| 277 | public function get_features() { |
| 278 | $features = FeaturesUtil::get_features( true, true ); |
| 279 | $new_features = array(); |
| 280 | |
| 281 | foreach ( array_keys( $features ) as $feature_id ) { |
| 282 | $new_features[ $feature_id ] = array( |
| 283 | 'is_enabled' => $features[ $feature_id ]['is_enabled'], |
| 284 | 'is_experimental' => $features[ $feature_id ]['is_experimental'] ?? false, |
| 285 | ); |
| 286 | } |
| 287 | |
| 288 | return $new_features; |
| 289 | } |
| 290 | |
| 291 | /** |
| 292 | * Register the admin settings for use in the WC REST API |
| 293 | * |
| 294 | * @param array $groups Array of setting groups. |
| 295 | * @return array |
| 296 | */ |
| 297 | public function add_settings_group( $groups ) { |
| 298 | $groups[] = array( |
| 299 | 'id' => 'wc_admin', |
| 300 | 'label' => __( 'WooCommerce Admin', 'woocommerce' ), |
| 301 | 'description' => __( 'Settings for WooCommerce admin reporting.', 'woocommerce' ), |
| 302 | ); |
| 303 | return $groups; |
| 304 | } |
| 305 | |
| 306 | /** |
| 307 | * Add WC Admin specific settings |
| 308 | * |
| 309 | * @param array $settings Array of settings in wc admin group. |
| 310 | * @return array |
| 311 | */ |
| 312 | public function add_settings( $settings ) { |
| 313 | $unregistered_statuses = $this->get_unregistered_order_statuses(); |
| 314 | $registered_statuses = self::get_order_statuses( wc_get_order_statuses() ); |
| 315 | $all_statuses = array_merge( $unregistered_statuses, $registered_statuses ); |
| 316 | |
| 317 | $settings[] = array( |
| 318 | 'id' => 'woocommerce_excluded_report_order_statuses', |
| 319 | 'option_key' => 'woocommerce_excluded_report_order_statuses', |
| 320 | 'label' => __( 'Excluded report order statuses', 'woocommerce' ), |
| 321 | 'description' => __( 'Statuses that should not be included when calculating report totals.', 'woocommerce' ), |
| 322 | 'default' => array( 'pending', 'cancelled', 'failed' ), |
| 323 | 'type' => 'multiselect', |
| 324 | 'options' => $all_statuses, |
| 325 | ); |
| 326 | $settings[] = array( |
| 327 | 'id' => 'woocommerce_actionable_order_statuses', |
| 328 | 'option_key' => 'woocommerce_actionable_order_statuses', |
| 329 | 'label' => __( 'Actionable order statuses', 'woocommerce' ), |
| 330 | 'description' => __( 'Statuses that require extra action on behalf of the store admin.', 'woocommerce' ), |
| 331 | 'default' => array( 'processing', 'on-hold' ), |
| 332 | 'type' => 'multiselect', |
| 333 | 'options' => $all_statuses, |
| 334 | ); |
| 335 | $settings[] = array( |
| 336 | 'id' => 'woocommerce_default_date_range', |
| 337 | 'option_key' => 'woocommerce_default_date_range', |
| 338 | 'label' => __( 'Default Date Range', 'woocommerce' ), |
| 339 | 'description' => __( 'Default Date Range', 'woocommerce' ), |
| 340 | 'default' => 'period=month&compare=previous_year', |
| 341 | 'type' => 'text', |
| 342 | ); |
| 343 | $settings[] = array( |
| 344 | 'id' => 'woocommerce_date_type', |
| 345 | 'option_key' => 'woocommerce_date_type', |
| 346 | 'label' => __( 'Date Type', 'woocommerce' ), |
| 347 | 'description' => __( 'Database date field considered for Revenue and Orders reports', 'woocommerce' ), |
| 348 | 'type' => 'select', |
| 349 | 'options' => array( |
| 350 | 'date_created' => 'date_created', |
| 351 | 'date_paid' => 'date_paid', |
| 352 | 'date_completed' => 'date_completed', |
| 353 | ), |
| 354 | ); |
| 355 | |
| 356 | if ( Features::is_enabled( 'analytics-scheduled-import' ) ) { |
| 357 | $settings[] = array( |
| 358 | 'id' => 'woocommerce_analytics_scheduled_import', |
| 359 | 'option_key' => 'woocommerce_analytics_scheduled_import', |
| 360 | 'label' => __( 'Updates', 'woocommerce' ), |
| 361 | 'description' => __( 'Controls how analytics data is imported from orders.', 'woocommerce' ), |
| 362 | 'type' => 'radio', |
| 363 | 'default' => null, // Default to null so we can know if it's a new site or an existing site. New sites will have the option set. |
| 364 | 'options' => array( |
| 365 | 'yes' => __( 'Scheduled (recommended)', 'woocommerce' ), |
| 366 | 'no' => __( 'Immediately', 'woocommerce' ), |
| 367 | ), |
| 368 | ); |
| 369 | |
| 370 | // Add hidden setting for the import interval to display in the client side. |
| 371 | $import_interval = \Automattic\WooCommerce\Internal\Admin\Schedulers\OrdersScheduler::get_import_interval(); |
| 372 | $import_interval = absint( $import_interval ); |
| 373 | // Format the import interval to a human-readable string. |
| 374 | $import_interval_string = human_time_diff( 0, $import_interval ); |
| 375 | $settings[] = array( |
| 376 | 'id' => 'woocommerce_analytics_import_interval', |
| 377 | 'option_key' => 'woocommerce_analytics_import_interval', |
| 378 | 'type' => 'hidden', |
| 379 | 'default' => $import_interval_string, |
| 380 | ); |
| 381 | } |
| 382 | |
| 383 | return $settings; |
| 384 | } |
| 385 | |
| 386 | /** |
| 387 | * Gets custom settings used for WC Admin. |
| 388 | * |
| 389 | * @param array $settings Array of settings to merge into. |
| 390 | * @return array |
| 391 | */ |
| 392 | private function get_custom_settings( $settings ) { |
| 393 | $wc_rest_settings_options_controller = new \WC_REST_Setting_Options_Controller(); |
| 394 | $wc_admin_group_settings = $wc_rest_settings_options_controller->get_group_settings( 'wc_admin' ); |
| 395 | $settings['wcAdminSettings'] = array(); |
| 396 | |
| 397 | foreach ( $wc_admin_group_settings as $setting ) { |
| 398 | if ( ! empty( $setting['id'] ) ) { |
| 399 | $settings['wcAdminSettings'][ $setting['id'] ] = $setting['value']; |
| 400 | } |
| 401 | } |
| 402 | return $settings; |
| 403 | } |
| 404 | |
| 405 | /** |
| 406 | * Add the settings UI schema for the current classic settings page. |
| 407 | * |
| 408 | * @param array $settings Array of component settings. |
| 409 | * @return array |
| 410 | */ |
| 411 | private function add_settings_ui_schema( array $settings ): array { |
| 412 | try { |
| 413 | if ( ! class_exists( SettingsUIRequestContext::class ) ) { |
| 414 | return $settings; |
| 415 | } |
| 416 | |
| 417 | $context = SettingsUIRequestContext::get_current(); |
| 418 | } catch ( \Throwable $e ) { |
| 419 | return $settings; |
| 420 | } |
| 421 | |
| 422 | if ( ! $context ) { |
| 423 | return $settings; |
| 424 | } |
| 425 | |
| 426 | $schema = $context->get_schema(); |
| 427 | if ( ! is_array( $schema ) ) { |
| 428 | return $settings; |
| 429 | } |
| 430 | |
| 431 | $page_id = $context->get_page_id(); |
| 432 | $section_key = $context->get_current_section_key(); |
| 433 | |
| 434 | if ( ! isset( $settings['settingsUI'] ) || ! is_array( $settings['settingsUI'] ) ) { |
| 435 | $settings['settingsUI'] = array(); |
| 436 | } |
| 437 | if ( ! isset( $settings['settingsUI'][ $page_id ] ) || ! is_array( $settings['settingsUI'][ $page_id ] ) ) { |
| 438 | $settings['settingsUI'][ $page_id ] = array(); |
| 439 | } |
| 440 | |
| 441 | $settings['settingsUI'][ $page_id ][ $section_key ] = $schema; |
| 442 | |
| 443 | return $settings; |
| 444 | } |
| 445 | } |
| 446 |