first commit

This commit is contained in:
User A0264400
2026-04-01 23:20:16 +03:00
commit a766acdc90
23071 changed files with 4933189 additions and 0 deletions

View File

@@ -0,0 +1,430 @@
<?php
/**
* WooCommerce Account Settings.
*
* @package WooCommerce\Admin
*/
defined( 'ABSPATH' ) || exit;
if ( class_exists( 'WC_Settings_Accounts', false ) ) {
return new WC_Settings_Accounts();
}
use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;
use Automattic\WooCommerce\Admin\Features\Features;
/**
* WC_Settings_Accounts.
*/
class WC_Settings_Accounts extends WC_Settings_Page {
/**
* Constructor.
*/
public function __construct() {
$this->id = 'account';
$this->label = __( 'Accounts &amp; Privacy', 'woocommerce' );
parent::__construct();
}
/**
* Setting page icon.
*
* @var string
*/
public $icon = 'people';
/**
* Get settings array.
*
* @return array
*/
protected function get_settings_for_default_section() {
$erasure_text = esc_html__( 'account erasure request', 'woocommerce' );
$privacy_text = esc_html__( 'privacy page', 'woocommerce' );
if ( current_user_can( 'manage_privacy_options' ) ) {
if ( version_compare( get_bloginfo( 'version' ), '5.3', '<' ) ) {
$erasure_text = sprintf( '<a href="%s">%s</a>', esc_url( admin_url( 'tools.php?page=remove_personal_data' ) ), $erasure_text );
} else {
$erasure_text = sprintf( '<a href="%s">%s</a>', esc_url( admin_url( 'erase-personal-data.php' ) ), $erasure_text );
}
$privacy_text = sprintf( '<a href="%s">%s</a>', esc_url( admin_url( 'options-privacy.php' ) ), $privacy_text );
}
$account_settings = array(
array(
'title' => '',
'type' => 'title',
'id' => 'account_registration_options',
),
array(
'title' => __( 'Checkout', 'woocommerce' ),
'desc' => __( 'Enable guest checkout (recommended)', 'woocommerce' ),
'desc_tip' => __( 'Allows customers to checkout without an account.', 'woocommerce' ),
'id' => 'woocommerce_enable_guest_checkout',
'default' => 'yes',
'type' => 'checkbox',
'checkboxgroup' => 'start',
'autoload' => false,
),
array(
'title' => __( 'Login', 'woocommerce' ),
'desc' => __( 'Enable log-in during checkout', 'woocommerce' ),
'id' => 'woocommerce_enable_checkout_login_reminder',
'default' => 'no',
'type' => 'checkbox',
'checkboxgroup' => 'end',
'autoload' => false,
),
array(
'title' => __( 'Account creation', 'woocommerce' ),
'desc' => __( 'After checkout (recommended)', 'woocommerce' ),
'desc_tip' => sprintf(
/* Translators: %1$s and %2$s are opening and closing <a> tags respectively. */
__( 'Customers can create an account after their order is placed. Customize messaging %1$shere%2$s.', 'woocommerce' ),
'<a target="_blank" class="delayed-account-creation-customize-link" href="' . esc_url( admin_url( 'site-editor.php?postId=woocommerce%2Fwoocommerce%2F%2Forder-confirmation&postType=wp_template&canvas=edit' ) ) . '">',
'</a>'
),
'id' => 'woocommerce_enable_delayed_account_creation',
'default' => 'no',
'type' => 'checkbox',
'checkboxgroup' => 'start',
'autoload' => false,
'custom_attributes' => array(
'disabled-tooltip' => __( 'Enable guest checkout to use this feature.', 'woocommerce' ),
),
'legend' => __( 'Allow customers to create an account', 'woocommerce' ),
),
array(
'title' => __( 'Account creation', 'woocommerce' ),
'desc' => __( 'During checkout', 'woocommerce' ),
'desc_tip' => __( 'Customers can create an account before placing their order.', 'woocommerce' ),
'id' => 'woocommerce_enable_signup_and_login_from_checkout',
'default' => 'no',
'type' => 'checkbox',
'checkboxgroup' => '',
'autoload' => false,
),
array(
'title' => __( 'Account creation', 'woocommerce' ),
'desc' => __( 'On "My account" page', 'woocommerce' ),
'id' => 'woocommerce_enable_myaccount_registration',
'default' => 'no',
'type' => 'checkbox',
'checkboxgroup' => 'end',
'autoload' => false,
),
array(
'title' => __( 'Account creation options', 'woocommerce' ),
'desc' => __( 'Send password setup link (recommended)', 'woocommerce' ),
'desc_tip' => __( 'New users receive an email to set up their password.', 'woocommerce' ),
'id' => 'woocommerce_registration_generate_password',
'default' => 'yes',
'type' => 'checkbox',
'checkboxgroup' => 'start',
'autoload' => false,
'custom_attributes' => array(
'disabled-tooltip' => __( 'Enable an account creation method to use this feature.', 'woocommerce' ),
),
),
array(
'title' => __( 'Account creation options', 'woocommerce' ),
'desc' => __( 'Generate account login (recommended)', 'woocommerce' ),
'desc_tip' => __( 'Generate a login for the account using first and/or last name. If neither is usable (e.g. invalid or missing) the email address will be used. If this option is unchecked, customers will need to set a username during account creation', 'woocommerce' ),
'id' => 'woocommerce_registration_generate_username',
'default' => 'yes',
'type' => 'checkbox',
'checkboxgroup' => 'end',
'autoload' => false,
'custom_attributes' => array(
'disabled-tooltip' => __( 'Enable an account creation method to use this feature.', 'woocommerce' ),
),
),
array(
'title' => __( 'Account erasure requests', 'woocommerce' ),
'desc' => __( 'Remove personal data from orders on request', 'woocommerce' ),
/* Translators: %s URL to erasure request screen. */
'desc_tip' => sprintf( esc_html__( 'When handling an %s, should personal data within orders be retained or removed?', 'woocommerce' ), $erasure_text ),
'id' => 'woocommerce_erasure_request_removes_order_data',
'type' => 'checkbox',
'default' => 'no',
'checkboxgroup' => 'start',
'autoload' => false,
),
array(
'desc' => __( 'Remove access to downloads on request', 'woocommerce' ),
/* Translators: %s URL to erasure request screen. */
'desc_tip' => sprintf( esc_html__( 'When handling an %s, should access to downloadable files be revoked and download logs cleared?', 'woocommerce' ), $erasure_text ),
'id' => 'woocommerce_erasure_request_removes_download_data',
'type' => 'checkbox',
'default' => 'no',
'checkboxgroup' => '',
'autoload' => false,
),
array(
'title' => __( 'Personal data removal', 'woocommerce' ),
'desc' => __( 'Allow personal data to be removed in bulk from orders', 'woocommerce' ),
'desc_tip' => __( 'Adds an option to the orders screen for removing personal data in bulk. Note that removing personal data cannot be undone.', 'woocommerce' ),
'id' => 'woocommerce_allow_bulk_remove_personal_data',
'type' => 'checkbox',
'checkboxgroup' => 'end',
'default' => 'no',
'autoload' => false,
),
array(
'type' => 'sectionend',
'id' => 'account_registration_options',
),
array(
'title' => __( 'Privacy policy', 'woocommerce' ),
'type' => 'title',
'id' => 'privacy_policy_options',
/* translators: %s: privacy page link. */
'desc' => sprintf( esc_html__( 'This section controls the display of your website privacy policy. The privacy notices below will not show up unless a %s is set.', 'woocommerce' ), $privacy_text ),
),
array(
'title' => __( 'Registration privacy policy', 'woocommerce' ),
'desc_tip' => __( 'Optionally add some text about your store privacy policy to show on account registration forms.', 'woocommerce' ),
'id' => 'woocommerce_registration_privacy_policy_text',
/* translators: %s privacy policy page name and link */
'default' => sprintf( __( 'Your personal data will be used to support your experience throughout this website, to manage access to your account, and for other purposes described in our %s.', 'woocommerce' ), '[privacy_policy]' ),
'type' => 'textarea',
'css' => 'min-width: 50%; height: 75px;',
),
array(
'title' => __( 'Checkout privacy policy', 'woocommerce' ),
'desc_tip' => __( 'Optionally add some text about your store privacy policy to show during checkout.', 'woocommerce' ),
'id' => 'woocommerce_checkout_privacy_policy_text',
/* translators: %s privacy policy page name and link */
'default' => sprintf( __( 'Your personal data will be used to process your order, support your experience throughout this website, and for other purposes described in our %s.', 'woocommerce' ), '[privacy_policy]' ),
'type' => 'textarea',
'css' => 'min-width: 50%; height: 75px;',
),
array(
'type' => 'sectionend',
'id' => 'privacy_policy_options',
),
array(
'title' => __( 'Personal data retention', 'woocommerce' ),
'desc' => __( 'Choose how long to retain personal data when it\'s no longer needed for processing. Leave the following options blank to retain this data indefinitely.', 'woocommerce' ),
'type' => 'title',
'id' => 'personal_data_retention',
),
array(
'title' => __( 'Retain inactive accounts ', 'woocommerce' ),
'desc_tip' => __( 'Inactive accounts are those which have not logged in, or placed an order, for the specified duration. They will be deleted. Any orders will be converted into guest orders.', 'woocommerce' ),
'id' => 'woocommerce_delete_inactive_accounts',
'type' => 'relative_date_selector',
'placeholder' => __( 'N/A', 'woocommerce' ),
'default' => array(
'number' => '',
'unit' => 'months',
),
'autoload' => false,
),
array(
'title' => __( 'Retain pending orders ', 'woocommerce' ),
'desc_tip' => __( 'Pending orders are unpaid and may have been abandoned by the customer. They will be trashed after the specified duration.', 'woocommerce' ),
'id' => 'woocommerce_trash_pending_orders',
'type' => 'relative_date_selector',
'placeholder' => __( 'N/A', 'woocommerce' ),
'default' => '',
'autoload' => false,
),
array(
'title' => __( 'Retain failed orders', 'woocommerce' ),
'desc_tip' => __( 'Failed orders are unpaid and may have been abandoned by the customer. They will be trashed after the specified duration.', 'woocommerce' ),
'id' => 'woocommerce_trash_failed_orders',
'type' => 'relative_date_selector',
'placeholder' => __( 'N/A', 'woocommerce' ),
'default' => '',
'autoload' => false,
),
array(
'title' => __( 'Retain cancelled orders', 'woocommerce' ),
'desc_tip' => __( 'Cancelled orders are unpaid and may have been cancelled by the store owner or customer. They will be trashed after the specified duration.', 'woocommerce' ),
'id' => 'woocommerce_trash_cancelled_orders',
'type' => 'relative_date_selector',
'placeholder' => __( 'N/A', 'woocommerce' ),
'default' => '',
'autoload' => false,
),
array(
'title' => __( 'Retain refunded orders', 'woocommerce' ),
'desc_tip' => __( 'Retain refunded orders for a specified duration before anonymizing the personal data within them.', 'woocommerce' ),
'id' => 'woocommerce_anonymize_refunded_orders',
'type' => 'relative_date_selector',
'placeholder' => __( 'N/A', 'woocommerce' ),
'default' => array(
'number' => '',
'unit' => 'months',
),
'autoload' => false,
),
array(
'title' => __( 'Retain completed orders', 'woocommerce' ),
'desc_tip' => __( 'Retain completed orders for a specified duration before anonymizing the personal data within them.', 'woocommerce' ),
'id' => 'woocommerce_anonymize_completed_orders',
'type' => 'relative_date_selector',
'placeholder' => __( 'N/A', 'woocommerce' ),
'default' => array(
'number' => '',
'unit' => 'months',
),
'autoload' => false,
),
array(
'type' => 'sectionend',
'id' => 'personal_data_retention',
),
);
// Feature requires a block theme. Re-order settings if not using a block theme.
if ( ! wp_is_block_theme() ) {
$account_settings = array_map(
function ( $setting ) {
if ( 'woocommerce_enable_signup_and_login_from_checkout' === $setting['id'] ) {
$setting['checkboxgroup'] = 'start';
$setting['legend'] = __( 'Allow customers to create an account', 'woocommerce' );
}
return $setting;
},
$account_settings
);
$account_settings = array_filter(
$account_settings,
function ( $setting ) {
return 'woocommerce_enable_delayed_account_creation' !== $setting['id'];
},
);
}
// Change settings when using the block based checkout.
if ( CartCheckoutUtils::is_checkout_block_default() ) {
$account_settings = array_filter(
$account_settings,
function ( $setting ) {
return 'woocommerce_registration_generate_username' !== $setting['id'];
},
);
$account_settings = array_map(
function ( $setting ) {
if ( 'woocommerce_registration_generate_password' === $setting['id'] ) {
unset( $setting['checkboxgroup'] );
}
return $setting;
},
$account_settings
);
} else {
$account_settings = array_map(
function ( $setting ) {
if ( 'woocommerce_enable_delayed_account_creation' === $setting['id'] ) {
$setting['desc_tip'] = sprintf(
/* Translators: %1$s and %2$s are opening and closing <a> tags respectively. */
__( 'This feature is only available with the Cart & Checkout blocks. %1$sLearn more%2$s.', 'woocommerce' ),
'<a href="https://woocommerce.com/document/woocommerce-store-editing/customizing-cart-and-checkout">',
'</a>'
);
$setting['disabled'] = true;
$setting['value'] = 0;
$setting['custom_attributes']['disabled-tooltip'] = __( 'Your store is using shortcode checkout. Use the Checkout blocks to activate this option.', 'woocommerce' );
}
return $setting;
},
$account_settings
);
}
/**
* Filter account settings.
*
* @hook woocommerce_account_settings
* @since 3.5.0
* @param array $account_settings Account settings.
*/
return apply_filters( 'woocommerce_' . $this->id . '_settings', $account_settings );
}
/**
* Output the HTML for the settings.
*/
public function output() {
parent::output();
// The following code toggles disabled state on the account options based on other values.
$script =
'
// Move tooltips to label element. This is not possible through the settings field API so this is a workaround
// until said API is refactored.
document.querySelectorAll("input[disabled-tooltip]").forEach(function(element) {
const label = element.closest("label");
label.setAttribute("disabled-tooltip", element.getAttribute("disabled-tooltip"));
});
// This handles settings that are enabled/disabled based on other settings.
const checkboxes = [
document.getElementById("woocommerce_enable_signup_and_login_from_checkout"),
document.getElementById("woocommerce_enable_myaccount_registration"),
document.getElementById("woocommerce_enable_delayed_account_creation"),
document.getElementById("woocommerce_enable_signup_from_checkout_for_subscriptions")
];
const inputs = [
document.getElementById("woocommerce_registration_generate_username"),
document.getElementById("woocommerce_registration_generate_password")
];
checkboxes.forEach(cb => cb && cb.addEventListener("change", function() {
const isChecked = checkboxes.some(cb => cb && cb.checked);
inputs.forEach(input => {
if ( ! input ) {
return;
}
input.disabled = !isChecked;
});
}));
checkboxes[0].dispatchEvent(new Event("change")); // Initial state
// Tracks for customize link.
if ( typeof window?.wcTracks?.recordEvent === "function" ) {
const customizeLink = document.querySelector("a.delayed-account-creation-customize-link");
if ( customizeLink ) {
customizeLink.addEventListener("click", function() {
window.wcTracks.recordEvent("delayed_account_creation_customize_link_clicked");
});
}
}
';
// If the checkout block is not default, delayed account creation is always disabled. Otherwise its based on other settings.
if ( CartCheckoutUtils::is_checkout_block_default() ) {
$script .=
'
// Guest checkout should toggle off some options.
const guestCheckout = document.getElementById("woocommerce_enable_guest_checkout");
if ( guestCheckout ) {
guestCheckout.addEventListener("change", function() {
const isChecked = this.checked;
const input = document.getElementById("woocommerce_enable_delayed_account_creation");
if ( ! input ) {
return;
}
input.disabled = !isChecked;
});
guestCheckout.dispatchEvent(new Event("change")); // Initial state
}
';
}
$handle = 'wc-admin-settings-accounts';
wp_register_script( $handle, '', array(), WC_VERSION, array( 'in_footer' => true ) );
wp_enqueue_script( $handle );
wp_add_inline_script( $handle, $script );
}
}
return new WC_Settings_Accounts();

View File

@@ -0,0 +1,633 @@
<?php
/**
* WooCommerce advanced settings
*
* @package WooCommerce\Admin
*/
use Automattic\WooCommerce\Internal\Features\FeaturesController;
use Automattic\WooCommerce\Utilities\FeaturesUtil;
defined( 'ABSPATH' ) || exit;
/**
* Settings for API.
*/
if ( class_exists( 'WC_Settings_Advanced', false ) ) {
return new WC_Settings_Advanced();
}
/**
* WC_Settings_Advanced.
*/
class WC_Settings_Advanced extends WC_Settings_Page {
/**
* Constructor.
*/
public function __construct() {
$this->id = 'advanced';
$this->label = __( 'Advanced', 'woocommerce' );
parent::__construct();
$this->notices();
}
/**
* Setting page icon.
*
* @var string
*/
public $icon = 'more';
/**
* Get own sections.
*
* @return array
*/
protected function get_own_sections() {
$sections = array(
'' => __( 'Page setup', 'woocommerce' ),
'keys' => __( 'REST API keys', 'woocommerce' ),
);
$features_controller = wc_get_container()->get( FeaturesController::class );
if ( $features_controller->feature_is_enabled( 'rest_api_caching' ) ) {
$sections['rest_api_caching'] = __( 'REST API caching', 'woocommerce' );
}
$sections['webhooks'] = __( 'Webhooks', 'woocommerce' );
$sections['legacy_api'] = __( 'Legacy API', 'woocommerce' );
$sections['woocommerce_com'] = __( 'WooCommerce.com', 'woocommerce' );
if ( FeaturesUtil::feature_is_enabled( 'blueprint' ) ) {
$sections['blueprint'] = __( 'Blueprint (beta)', 'woocommerce' );
}
return $sections;
}
/**
* Get settings for the default section.
*
* @return array
*/
protected function get_settings_for_default_section() {
$settings =
array(
array(
'title' => __( 'Page setup', 'woocommerce' ),
'desc' => __( 'These pages need to be set so that WooCommerce knows where to send users to checkout.', 'woocommerce' ),
'type' => 'title',
'id' => 'advanced_page_options',
),
array(
'title' => __( 'Cart page', 'woocommerce' ),
/* Translators: %s Page contents. */
'desc' => __( 'Page where shoppers review their shopping cart', 'woocommerce' ),
'id' => 'woocommerce_cart_page_id',
'type' => 'single_select_page_with_search',
'default' => '',
'class' => 'wc-page-search',
'css' => 'min-width:300px;',
'args' => array(
'exclude' =>
array(
wc_get_page_id( 'checkout' ),
wc_get_page_id( 'myaccount' ),
),
),
'desc_tip' => true,
'autoload' => false,
),
array(
'title' => __( 'Checkout page', 'woocommerce' ),
/* Translators: %s Page contents. */
'desc' => __( 'Page where shoppers go to finalize their purchase', 'woocommerce' ),
'id' => 'woocommerce_checkout_page_id',
'type' => 'single_select_page_with_search',
'default' => wc_get_page_id( 'checkout' ),
'class' => 'wc-page-search',
'css' => 'min-width:300px;',
'args' => array(
'exclude' =>
array(
wc_get_page_id( 'cart' ),
wc_get_page_id( 'myaccount' ),
),
),
'desc_tip' => true,
'autoload' => false,
),
array(
'title' => __( 'My account page', 'woocommerce' ),
/* Translators: %s Page contents. */
'desc' => sprintf( __( 'Page contents: [%s]', 'woocommerce' ), apply_filters( 'woocommerce_my_account_shortcode_tag', 'woocommerce_my_account' ) ),
'id' => 'woocommerce_myaccount_page_id',
'type' => 'single_select_page_with_search',
'default' => '',
'class' => 'wc-page-search',
'css' => 'min-width:300px;',
'args' => array(
'exclude' =>
array(
wc_get_page_id( 'cart' ),
wc_get_page_id( 'checkout' ),
),
),
'desc_tip' => true,
'autoload' => false,
),
array(
'title' => __( 'Terms and conditions', 'woocommerce' ),
'desc' => __( 'If you define a "Terms" page the customer will be asked if they accept them when checking out.', 'woocommerce' ),
'id' => 'woocommerce_terms_page_id',
'default' => '',
'class' => 'wc-page-search',
'css' => 'min-width:300px;',
'type' => 'single_select_page_with_search',
'args' => array( 'exclude' => wc_get_page_id( 'checkout' ) ),
'desc_tip' => true,
'autoload' => false,
),
array(
'type' => 'sectionend',
'id' => 'advanced_page_options',
),
array(
'title' => '',
'type' => 'title',
'id' => 'checkout_process_options',
),
'force_ssl_checkout' => array(
'title' => __( 'Secure checkout', 'woocommerce' ),
'desc' => __( 'Force secure checkout', 'woocommerce' ),
'id' => 'woocommerce_force_ssl_checkout',
'default' => 'no',
'type' => 'checkbox',
'checkboxgroup' => 'start',
'show_if_checked' => 'option',
/* Translators: %s Docs URL. */
'desc_tip' => sprintf( __( 'Force SSL (HTTPS) on the checkout pages (<a href="%s" target="_blank">an SSL Certificate is required</a>).', 'woocommerce' ), 'https://woocommerce.com/document/ssl-and-https/#section-3' ),
),
'unforce_ssl_checkout' => array(
'desc' => __( 'Force HTTP when leaving the checkout', 'woocommerce' ),
'id' => 'woocommerce_unforce_ssl_checkout',
'default' => 'no',
'type' => 'checkbox',
'checkboxgroup' => 'end',
'show_if_checked' => 'yes',
),
array(
'type' => 'sectionend',
'id' => 'checkout_process_options',
),
array(
'title' => __( 'Checkout endpoints', 'woocommerce' ),
'type' => 'title',
'desc' => __( 'Endpoints are appended to your page URLs to handle specific actions during the checkout process. They should be unique.', 'woocommerce' ),
'id' => 'checkout_endpoint_options',
),
array(
'title' => __( 'Pay', 'woocommerce' ),
'desc' => __( 'Endpoint for the "Checkout &rarr; Pay" page.', 'woocommerce' ),
'id' => 'woocommerce_checkout_pay_endpoint',
'type' => 'text',
'default' => 'order-pay',
'desc_tip' => true,
),
array(
'title' => __( 'Order received', 'woocommerce' ),
'desc' => __( 'Endpoint for the "Checkout &rarr; Order received" page.', 'woocommerce' ),
'id' => 'woocommerce_checkout_order_received_endpoint',
'type' => 'text',
'default' => 'order-received',
'desc_tip' => true,
),
array(
'title' => __( 'Add payment method', 'woocommerce' ),
'desc' => __( 'Endpoint for the "Checkout &rarr; Add payment method" page.', 'woocommerce' ),
'id' => 'woocommerce_myaccount_add_payment_method_endpoint',
'type' => 'text',
'default' => 'add-payment-method',
'desc_tip' => true,
),
array(
'title' => __( 'Delete payment method', 'woocommerce' ),
'desc' => __( 'Endpoint for the delete payment method page.', 'woocommerce' ),
'id' => 'woocommerce_myaccount_delete_payment_method_endpoint',
'type' => 'text',
'default' => 'delete-payment-method',
'desc_tip' => true,
),
array(
'title' => __( 'Set default payment method', 'woocommerce' ),
'desc' => __( 'Endpoint for the setting a default payment method page.', 'woocommerce' ),
'id' => 'woocommerce_myaccount_set_default_payment_method_endpoint',
'type' => 'text',
'default' => 'set-default-payment-method',
'desc_tip' => true,
),
array(
'type' => 'sectionend',
'id' => 'checkout_endpoint_options',
),
array(
'title' => __( 'Account endpoints', 'woocommerce' ),
'type' => 'title',
'desc' => __( 'Endpoints are appended to your page URLs to handle specific actions on the accounts pages. They should be unique and can be left blank to disable the endpoint.', 'woocommerce' ),
'id' => 'account_endpoint_options',
),
array(
'title' => __( 'Orders', 'woocommerce' ),
'desc' => __( 'Endpoint for the "My account &rarr; Orders" page.', 'woocommerce' ),
'id' => 'woocommerce_myaccount_orders_endpoint',
'type' => 'text',
'default' => 'orders',
'desc_tip' => true,
),
array(
'title' => __( 'View order', 'woocommerce' ),
'desc' => __( 'Endpoint for the "My account &rarr; View order" page.', 'woocommerce' ),
'id' => 'woocommerce_myaccount_view_order_endpoint',
'type' => 'text',
'default' => 'view-order',
'desc_tip' => true,
),
array(
'title' => __( 'Downloads', 'woocommerce' ),
'desc' => __( 'Endpoint for the "My account &rarr; Downloads" page.', 'woocommerce' ),
'id' => 'woocommerce_myaccount_downloads_endpoint',
'type' => 'text',
'default' => 'downloads',
'desc_tip' => true,
),
array(
'title' => __( 'Edit account', 'woocommerce' ),
'desc' => __( 'Endpoint for the "My account &rarr; Edit account" page.', 'woocommerce' ),
'id' => 'woocommerce_myaccount_edit_account_endpoint',
'type' => 'text',
'default' => 'edit-account',
'desc_tip' => true,
),
array(
'title' => __( 'Addresses', 'woocommerce' ),
'desc' => __( 'Endpoint for the "My account &rarr; Addresses" page.', 'woocommerce' ),
'id' => 'woocommerce_myaccount_edit_address_endpoint',
'type' => 'text',
'default' => 'edit-address',
'desc_tip' => true,
),
array(
'title' => __( 'Payment methods', 'woocommerce' ),
'desc' => __( 'Endpoint for the "My account &rarr; Payment methods" page.', 'woocommerce' ),
'id' => 'woocommerce_myaccount_payment_methods_endpoint',
'type' => 'text',
'default' => 'payment-methods',
'desc_tip' => true,
),
array(
'title' => __( 'Lost password', 'woocommerce' ),
'desc' => __( 'Endpoint for the "My account &rarr; Lost password" page.', 'woocommerce' ),
'id' => 'woocommerce_myaccount_lost_password_endpoint',
'type' => 'text',
'default' => 'lost-password',
'desc_tip' => true,
),
array(
'title' => __( 'Logout', 'woocommerce' ),
'desc' => __( 'Endpoint for the triggering logout. You can add this to your menus via a custom link: yoursite.com/?customer-logout=true', 'woocommerce' ),
'id' => 'woocommerce_logout_endpoint',
'type' => 'text',
'default' => 'customer-logout',
'desc_tip' => true,
),
array(
'type' => 'sectionend',
'id' => 'account_endpoint_options',
),
);
$settings = apply_filters( 'woocommerce_settings_pages', $settings );
if ( wc_site_is_https() ) {
unset( $settings['unforce_ssl_checkout'], $settings['force_ssl_checkout'] );
}
return $settings;
}
/**
* Get settings for the WooCommerce.com section.
*
* @return array
*/
protected function get_settings_for_woocommerce_com_section() {
$tracking_info_text = sprintf( '<a href="%s" target="_blank">%s</a>', 'https://woocommerce.com/usage-tracking', esc_html__( 'WooCommerce.com Usage Tracking Documentation', 'woocommerce' ) );
$settings =
array(
array(
'title' => esc_html__( 'Usage Tracking', 'woocommerce' ),
'type' => 'title',
'id' => 'tracking_options',
'desc' => __( 'Gathering usage data allows us to tailor your store setup experience, offer more relevant content, and help make WooCommerce better for everyone.', 'woocommerce' ),
),
array(
'title' => __( 'Enable tracking', 'woocommerce' ),
'desc' => __( 'Allow usage of WooCommerce to be tracked', 'woocommerce' ),
/* Translators: %s URL to tracking info screen. */
'desc_tip' => sprintf( esc_html__( 'To opt out, leave this box unticked. Your store remains untracked, and no data will be collected. Read about what usage data is tracked at: %s.', 'woocommerce' ), $tracking_info_text ),
'id' => 'woocommerce_allow_tracking',
'type' => 'checkbox',
'checkboxgroup' => 'start',
'default' => 'no',
'autoload' => true,
),
array(
'type' => 'sectionend',
'id' => 'tracking_options',
),
array(
'title' => esc_html__( 'Marketplace suggestions', 'woocommerce' ),
'type' => 'title',
'id' => 'marketplace_suggestions',
'desc' => __( 'We show contextual suggestions for official extensions that may be helpful to your store.', 'woocommerce' ),
),
array(
'title' => __( 'Show Suggestions', 'woocommerce' ),
'desc' => __( 'Display suggestions within WooCommerce', 'woocommerce' ),
'desc_tip' => esc_html__( 'Leave this box unchecked if you do not want to pull suggested extensions from WooCommerce.com.', 'woocommerce' ),
'id' => 'woocommerce_show_marketplace_suggestions',
'type' => 'checkbox',
'checkboxgroup' => 'start',
'default' => 'yes',
'autoload' => false,
),
array(
'type' => 'sectionend',
'id' => 'marketplace_suggestions',
),
);
return apply_filters( 'woocommerce_com_integration_settings', $settings );
}
/**
* Get settings for the legacy API section.
*
* @return array
*/
protected function get_settings_for_legacy_api_section() {
$legacy_api_setting_desc =
'yes' === get_option( 'woocommerce_api_enabled' ) ?
__( 'The legacy REST API is enabled', 'woocommerce' ) :
__( 'The legacy REST API is NOT enabled', 'woocommerce' );
$legacy_api_setting_tip =
WC()->legacy_rest_api_is_available() ?
__( ' The WooCommerce Legacy REST API extension is installed and active.', 'woocommerce' ) :
sprintf(
/* translators: placeholders are URLs */
__( '⚠️ The WooCommerce Legacy REST API has been moved to <a target=”_blank” href="%1$s">a dedicated extension</a>. <b><a target=”_blank” href="%2$s">Learn more about this change</a></b>', 'woocommerce' ),
'https://wordpress.org/plugins/woocommerce-legacy-rest-api/',
'https://developer.woocommerce.com/2023/10/03/the-legacy-rest-api-will-move-to-a-dedicated-extension-in-woocommerce-9-0/'
);
$settings =
array(
array(
'title' => '',
'type' => 'title',
'desc' => '',
'id' => 'legacy_api_options',
),
array(
'title' => __( 'Legacy API', 'woocommerce' ),
'desc' => $legacy_api_setting_desc,
'id' => 'woocommerce_api_enabled',
'type' => 'checkbox',
'default' => 'no',
'disabled' => true,
'desc_tip' => $legacy_api_setting_tip,
),
array(
'type' => 'sectionend',
'id' => 'legacy_api_options',
),
);
return apply_filters( 'woocommerce_settings_rest_api', $settings );
}
/**
* Get settings for the REST API caching section.
*
* @return array
*/
protected function get_settings_for_rest_api_caching_section() {
$has_object_cache = wp_using_ext_object_cache();
$settings = array(
array(
'title' => __( 'REST API response cache', 'woocommerce' ),
'type' => 'title',
'desc' => __( 'These settings control backend caching and cache control headers for REST API responses.', 'woocommerce' ),
'id' => 'rest_api_cache_options',
),
);
if ( ! $has_object_cache ) {
$settings[] = array(
'type' => 'notice',
'id' => 'rest_api_cache_warning',
'notice_type' => 'warning',
'text' => sprintf(
/* translators: %1$s and %2$s are opening and closing <a> tags */
__( 'Backend caching requires a WordPress object cache plugin (Redis, Memcached, etc.) to be installed and active. %1$sLearn more about object caching%2$s.', 'woocommerce' ),
'<a href="https://developer.wordpress.org/reference/classes/wp_object_cache/" target="_blank">',
'</a>'
),
);
}
$backend_caching_setting = array(
'title' => __( 'Enable backend caching', 'woocommerce' ),
'desc' => __( 'Cache REST API responses on the server', 'woocommerce' ),
'id' => 'woocommerce_rest_api_enable_backend_caching',
'type' => 'checkbox',
'default' => 'no',
'disabled' => ! $has_object_cache,
'fixed_value' => $has_object_cache ? null : 'no',
'desc_tip' => __( 'Enables responses for REST API endpoints configured as cacheable. Requires an external object cache.<br/>This setting should be enabled only if no other plugins that handle caching are active.', 'woocommerce' ),
);
$settings[] = $backend_caching_setting;
$settings[] = array(
'title' => __( 'Enable cache control headers', 'woocommerce' ),
'desc' => __( 'Send cache control headers and support 304 Not Modified responses', 'woocommerce' ),
'id' => 'woocommerce_rest_api_enable_cache_headers',
'type' => 'checkbox',
'default' => 'yes',
'desc_tip' => __( 'Enables including ETag and Cache-Control headers, and returning 304 Not Modified responses, for REST API endpoints configured as cacheable.', 'woocommerce' ),
);
$settings[] = array(
'type' => 'sectionend',
'id' => 'rest_api_cache_options',
);
/**
* Filter REST API cache settings.
*
* @since 10.5.0
* @param array $settings REST API cache settings.
*/
return apply_filters( 'woocommerce_rest_api_cache_settings', $settings );
}
/**
* Get settings for the Blueprint section.
*
* @return array
*/
protected function get_settings_for_blueprint_section() {
$settings =
array(
array(
'id' => 'wc_settings_blueprint_slotfill',
'type' => 'slotfill_placeholder',
),
);
return $settings;
}
/**
* Form method.
*
* @deprecated 3.4.4
*
* @param string $method Method name.
*
* @return string
*/
public function form_method( $method ) {
return 'post';
}
/**
* Notices.
*/
private function notices() {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['section'] ) && 'webhooks' === $_GET['section'] ) {
WC_Admin_Webhooks::notices();
}
if ( isset( $_GET['section'] ) && 'keys' === $_GET['section'] ) {
WC_Admin_API_Keys::notices();
}
// phpcs:enable
}
/**
* Output the settings.
*/
public function output() {
global $current_section, $hide_save_button;
if ( 'blueprint' === $current_section ) {
$hide_save_button = true;
}
if ( 'webhooks' === $current_section ) {
WC_Admin_Webhooks::page_output();
} elseif ( 'keys' === $current_section ) {
WC_Admin_API_Keys::page_output();
} else {
parent::output();
}
}
/**
* Save settings.
*/
public function save() {
// phpcs:disable WordPress.Security.NonceVerification.Missing
global $current_section;
$prev_value = 'yes' === get_option( 'woocommerce_allow_tracking', 'no' ) ? 'yes' : 'no';
$new_value = isset( $_POST['woocommerce_allow_tracking'] ) && ( 'yes' === $_POST['woocommerce_allow_tracking'] || '1' === $_POST['woocommerce_allow_tracking'] ) ? 'yes' : 'no';
if ( apply_filters( 'woocommerce_rest_api_valid_to_save', ! in_array( $current_section, array( 'keys', 'webhooks' ), true ) ) ) {
// Prevent the T&Cs and checkout page from being set to the same page.
if ( isset( $_POST['woocommerce_terms_page_id'], $_POST['woocommerce_checkout_page_id'] ) && $_POST['woocommerce_terms_page_id'] === $_POST['woocommerce_checkout_page_id'] ) {
$_POST['woocommerce_terms_page_id'] = '';
}
// Prevent the Cart, checkout and my account page from being set to the same page.
if ( isset( $_POST['woocommerce_cart_page_id'], $_POST['woocommerce_checkout_page_id'], $_POST['woocommerce_myaccount_page_id'] ) ) {
if ( $_POST['woocommerce_cart_page_id'] === $_POST['woocommerce_checkout_page_id'] ) {
$_POST['woocommerce_checkout_page_id'] = '';
}
if ( $_POST['woocommerce_cart_page_id'] === $_POST['woocommerce_myaccount_page_id'] ) {
$_POST['woocommerce_myaccount_page_id'] = '';
}
if ( $_POST['woocommerce_checkout_page_id'] === $_POST['woocommerce_myaccount_page_id'] ) {
$_POST['woocommerce_myaccount_page_id'] = '';
}
}
if ( class_exists( 'WC_Tracks' ) && 'no' === $new_value && 'yes' === $prev_value ) {
WC_Tracks::track_woocommerce_allow_tracking_toggled( $prev_value, $new_value, 'settings' );
}
$this->save_settings_for_current_section();
$this->do_update_options_action();
if ( class_exists( 'WC_Tracks' ) && 'yes' === $new_value && 'no' === $prev_value ) {
WC_Tracks::track_woocommerce_allow_tracking_toggled( $prev_value, $new_value, 'settings' );
}
}
// phpcs:enable
}
}
// phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound, Generic.Commenting.Todo.CommentFound
/**
* WC_Settings_Rest_API class.
*
* @deprecated 3.4 in favour of WC_Settings_Advanced.
*/
class WC_Settings_Rest_API extends WC_Settings_Advanced {
}
return new WC_Settings_Advanced();
// phpcs:enable

View File

@@ -0,0 +1,11 @@
<?php // @codingStandardsIgnoreFile.
/**
* Settings class file.
*
* @deprecated 3.4.0 Replaced with class-wc-settings-payment-gateways.php.
* @todo remove in 4.0.
*/
defined( 'ABSPATH' ) || exit;
return include __DIR__ . '/class-wc-settings-payment-gateways.php';

View File

@@ -0,0 +1,437 @@
<?php
/**
* WooCommerce General Settings
*
* @package WooCommerce\Admin
*/
use Automattic\WooCommerce\Admin\Features\Features;
use Automattic\WooCommerce\Internal\AddressProvider\AddressProviderController;
defined( 'ABSPATH' ) || exit;
if ( class_exists( 'WC_Settings_General', false ) ) {
return new WC_Settings_General();
}
/**
* WC_Admin_Settings_General.
*/
class WC_Settings_General extends WC_Settings_Page {
/**
* Constructor.
*/
public function __construct() {
$this->id = 'general';
$this->label = __( 'General', 'woocommerce' );
parent::__construct();
}
/**
* Setting page icon.
*
* @var string
*/
public $icon = 'cog';
/**
* Get settings or the default section.
*
* @return array
*/
protected function get_settings_for_default_section() {
$currency_code_options = get_woocommerce_currencies();
foreach ( $currency_code_options as $code => $name ) {
$currency_code_options[ $code ] = $name . ' (' . get_woocommerce_currency_symbol( $code ) . ') — ' . esc_html( $code );
}
$address_autocomplete_preferred_provider_setting = array();
$address_autocomplete_setting_desc_tip = __( 'Suggest full addresses to customers as they type.', 'woocommerce' );
// This is in a try because getting the class from the container may fail if the class is not available.
// If it fails, these settings should not be shown as the feature is not available.
try {
$address_provider_class = wc_get_container()->get( AddressProviderController::class );
$address_autocomplete_providers = $address_provider_class->get_providers();
$address_autocomplete_available = ! empty( $address_autocomplete_providers );
if ( ! $address_autocomplete_available ) {
// translators: %s: WooPayments URL.
$address_autocomplete_setting_desc_tip .= ' ' . sprintf( __( 'Requires a plugin with predictive address search support (e.g. <a href="%s" target="_blank">WooPayments</a>).', 'woocommerce' ), 'https://woocommerce.com/products/woocommerce-payments/' );
}
$enable_address_autocomplete_setting = array(
'id' => 'woocommerce_address_autocomplete_enabled',
'desc' => __( 'Enable predictive address search', 'woocommerce' ),
'name' => __( 'Address autocomplete', 'woocommerce' ),
'type' => 'checkbox',
'disabled' => ! $address_autocomplete_available,
'desc_tip' => $address_autocomplete_setting_desc_tip,
'default' => 'no',
);
// If no providers are available, make sure the checkbox is unchecked.
if ( ! $address_autocomplete_available ) {
$enable_address_autocomplete_setting['value'] = false;
}
if ( count( $address_autocomplete_providers ) > 1 ) {
$address_provider_options = array();
foreach ( $address_autocomplete_providers as $address_provider ) {
$address_provider_options[ $address_provider->id ] = sanitize_text_field( $address_provider->name );
}
$address_autocomplete_preferred_provider_setting = array(
'id' => 'woocommerce_address_autocomplete_provider',
'name' => __( 'Preferred address autocomplete provider', 'woocommerce' ),
'type' => 'select',
'class' => 'wc-enhanced-select',
'default' => $address_autocomplete_providers[0]->id ?? '',
'options' => $address_provider_options,
);
}
} catch ( \Exception $e ) {
// If the class is not available, we don't want to show the setting.
wc_get_logger()->log( 'error', 'Error getting address provider class: ' . $e->getMessage() );
$enable_address_autocomplete_setting = array();
$address_autocomplete_preferred_provider_setting = array();
}
$settings =
array(
array(
'title' => __( 'Store Address', 'woocommerce' ),
'type' => 'title',
'desc' => __( 'This is where your business is located. Tax rates and shipping rates will use this address.', 'woocommerce' ),
'id' => 'store_address',
'order' => 10,
),
array(
'title' => __( 'Address line 1', 'woocommerce' ),
'desc' => __( 'The street address for your business location.', 'woocommerce' ),
'id' => 'woocommerce_store_address',
'default' => '',
'type' => 'text',
'desc_tip' => true,
),
array(
'title' => __( 'Address line 2', 'woocommerce' ),
'desc' => __( 'An additional, optional address line for your business location.', 'woocommerce' ),
'id' => 'woocommerce_store_address_2',
'default' => '',
'type' => 'text',
'desc_tip' => true,
),
array(
'title' => __( 'City', 'woocommerce' ),
'desc' => __( 'The city in which your business is located.', 'woocommerce' ),
'id' => 'woocommerce_store_city',
'default' => '',
'type' => 'text',
'desc_tip' => true,
),
array(
'title' => __( 'Country / State', 'woocommerce' ),
'desc' => __( 'The country and state or province, if any, in which your business is located.', 'woocommerce' ),
'id' => 'woocommerce_default_country',
'default' => 'US:CA',
'type' => 'single_select_country',
'desc_tip' => true,
),
array(
'title' => __( 'Postcode / ZIP', 'woocommerce' ),
'desc' => __( 'The postal code, if any, in which your business is located.', 'woocommerce' ),
'id' => 'woocommerce_store_postcode',
'css' => 'min-width:50px;',
'default' => '',
'type' => 'text',
'desc_tip' => true,
),
array(
'type' => 'sectionend',
'id' => 'store_address',
),
array(
'title' => __( 'General options', 'woocommerce' ),
'type' => 'title',
'desc' => '',
'id' => 'general_options',
'order' => 20,
),
array(
'title' => __( 'Selling location(s)', 'woocommerce' ),
'desc' => __( 'This option lets you limit which countries you are willing to sell to.', 'woocommerce' ),
'id' => 'woocommerce_allowed_countries',
'default' => 'all',
'type' => 'select',
'class' => 'wc-enhanced-select',
'css' => 'min-width: 350px;',
'desc_tip' => true,
'options' => array(
'all' => __( 'Sell to all countries', 'woocommerce' ),
'all_except' => __( 'Sell to all countries, except for&hellip;', 'woocommerce' ),
'specific' => __( 'Sell to specific countries', 'woocommerce' ),
),
),
array(
'title' => __( 'Sell to all countries, except for&hellip;', 'woocommerce' ),
'desc' => '',
'id' => 'woocommerce_all_except_countries',
'css' => 'min-width: 350px;',
'default' => '',
'type' => 'multi_select_countries',
),
array(
'title' => __( 'Sell to specific countries', 'woocommerce' ),
'desc' => '',
'id' => 'woocommerce_specific_allowed_countries',
'css' => 'min-width: 350px;',
'default' => '',
'type' => 'multi_select_countries',
),
array(
'title' => __( 'Shipping location(s)', 'woocommerce' ),
'desc' => __( 'Choose which countries you want to ship to, or choose to ship to all locations you sell to.', 'woocommerce' ),
'id' => 'woocommerce_ship_to_countries',
'default' => '',
'type' => 'select',
'class' => 'wc-enhanced-select',
'desc_tip' => true,
'options' => array(
'' => __( 'Ship to all countries you sell to', 'woocommerce' ),
'all' => __( 'Ship to all countries', 'woocommerce' ),
'specific' => __( 'Ship to specific countries only', 'woocommerce' ),
'disabled' => __( 'Disable shipping &amp; shipping calculations', 'woocommerce' ),
),
),
array(
'title' => __( 'Ship to specific countries', 'woocommerce' ),
'desc' => '',
'id' => 'woocommerce_specific_ship_to_countries',
'css' => '',
'default' => '',
'type' => 'multi_select_countries',
),
array(
'title' => __( 'Default customer location', 'woocommerce' ),
'id' => 'woocommerce_default_customer_address',
'desc_tip' => __( 'This option determines a customers default location. The MaxMind GeoLite Database will be periodically downloaded to your wp-content directory if using geolocation.', 'woocommerce' ),
'default' => 'base',
'type' => 'select',
'class' => 'wc-enhanced-select',
'options' => array(
'' => __( 'No location by default', 'woocommerce' ),
'base' => __( 'Shop country/region', 'woocommerce' ),
'geolocation' => __( 'Geolocate', 'woocommerce' ),
'geolocation_ajax' => __( 'Geolocate (with page caching support)', 'woocommerce' ),
),
),
$enable_address_autocomplete_setting,
$address_autocomplete_preferred_provider_setting,
array(
'type' => 'sectionend',
'id' => 'general_options',
),
array(
'title' => __( 'Taxes and coupons', 'woocommerce' ),
'type' => 'title',
'desc' => __( 'Enable taxes and coupons and configure how they are calculated.', 'woocommerce' ),
'id' => 'taxes_and_coupons_options',
'order' => 30,
),
array(
'title' => __( 'Enable taxes', 'woocommerce' ),
'desc' => __( 'Enable tax rates and calculations', 'woocommerce' ),
'id' => 'woocommerce_calc_taxes',
'default' => 'no',
'type' => 'checkbox',
'desc_tip' => __( 'Rates will be configurable and taxes will be calculated during checkout.', 'woocommerce' ),
),
array(
'title' => __( 'Enable coupons', 'woocommerce' ),
'desc' => __( 'Enable the use of coupon codes', 'woocommerce' ),
'id' => 'woocommerce_enable_coupons',
'default' => 'yes',
'type' => 'checkbox',
'checkboxgroup' => 'start',
'show_if_checked' => 'option',
'desc_tip' => __( 'Coupons can be applied from the cart and checkout pages.', 'woocommerce' ),
),
array(
'desc' => __( 'Calculate coupon discounts sequentially', 'woocommerce' ),
'id' => 'woocommerce_calc_discounts_sequentially',
'default' => 'no',
'type' => 'checkbox',
'desc_tip' => __( 'When applying multiple coupons, apply the first coupon to the full price and the second coupon to the discounted price and so on.', 'woocommerce' ),
'show_if_checked' => 'yes',
'checkboxgroup' => 'end',
'autoload' => false,
),
array(
'type' => 'sectionend',
'id' => 'taxes_and_coupons_options',
),
array(
'title' => __( 'Currency options', 'woocommerce' ),
'type' => 'title',
'desc' => __( 'The following options affect how prices are displayed on the frontend.', 'woocommerce' ),
'id' => 'pricing_options',
'order' => 40,
),
array(
'title' => __( 'Currency', 'woocommerce' ),
'desc' => __( 'This controls what currency prices are listed at in the catalog and which currency gateways will take payments in.', 'woocommerce' ),
'id' => 'woocommerce_currency',
'default' => 'USD',
'type' => 'select',
'class' => 'wc-enhanced-select',
'desc_tip' => true,
'options' => $currency_code_options,
),
array(
'title' => __( 'Currency position', 'woocommerce' ),
'desc' => __( 'This controls the position of the currency symbol.', 'woocommerce' ),
'id' => 'woocommerce_currency_pos',
'class' => 'wc-enhanced-select',
'default' => 'left',
'type' => 'select',
'options' => array(
'left' => __( 'Left', 'woocommerce' ),
'right' => __( 'Right', 'woocommerce' ),
'left_space' => __( 'Left with space', 'woocommerce' ),
'right_space' => __( 'Right with space', 'woocommerce' ),
),
'desc_tip' => true,
),
array(
'title' => __( 'Thousand separator', 'woocommerce' ),
'desc' => __( 'This sets the thousand separator of displayed prices.', 'woocommerce' ),
'id' => 'woocommerce_price_thousand_sep',
'css' => 'width:50px;',
'default' => ',',
'type' => 'text',
'desc_tip' => true,
),
array(
'title' => __( 'Decimal separator', 'woocommerce' ),
'desc' => __( 'This sets the decimal separator of displayed prices.', 'woocommerce' ),
'id' => 'woocommerce_price_decimal_sep',
'css' => 'width:50px;',
'default' => '.',
'type' => 'text',
'desc_tip' => true,
),
array(
'title' => __( 'Number of decimals', 'woocommerce' ),
'desc' => __( 'This sets the number of decimal points shown in displayed prices.', 'woocommerce' ),
'id' => 'woocommerce_price_num_decimals',
'css' => 'width:50px;',
'default' => '2',
'desc_tip' => true,
'type' => 'number',
'custom_attributes' => array(
'min' => 0,
'step' => 1,
),
),
array(
'type' => 'sectionend',
'id' => 'pricing_options',
),
);
// Remove any empty items from settings array.
// e.g. The preferred autocomplete provider setting would be empty if <=1 providers are registered.
$settings = array_filter(
$settings,
function ( $setting ) {
return ! empty( $setting );
}
);
return apply_filters( 'woocommerce_general_settings', $settings );
}
/**
* Output a color picker input box.
*
* @param mixed $name Name of input.
* @param string $id ID of input.
* @param mixed $value Value of input.
* @param string $desc (default: '') Description for input.
*/
public function color_picker( $name, $id, $value, $desc = '' ) {
echo '<div class="color_box">' . wc_help_tip( $desc ) . '
<input name="' . esc_attr( $id ) . '" id="' . esc_attr( $id ) . '" type="text" value="' . esc_attr( $value ) . '" class="colorpick" /> <div id="colorPickerDiv_' . esc_attr( $id ) . '" class="colorpickdiv"></div>
</div>';
}
/**
* Output settings with additional JS to hide preferred provider if autocomplete is disabled.
*
* @return void
*/
public function output() {
parent::output();
$handle = 'wc-admin-settings-general';
wp_register_script( $handle, '', array(), WC_VERSION, array( 'in_footer' => true ) );
wp_enqueue_script( $handle );
wp_add_inline_script(
$handle,
"
const preferredProviderInput = document.querySelector( '#woocommerce_address_autocomplete_provider' );
const autocompleteEnabledInput = document.querySelector( '#woocommerce_address_autocomplete_enabled' );
let preferredProviderRow = null;
if ( preferredProviderInput ) {
preferredProviderRow = preferredProviderInput.closest( 'tr' );
}
if ( autocompleteEnabledInput && preferredProviderRow ) {
if ( ! autocompleteEnabledInput.checked ) {
preferredProviderRow.style.display = 'none';
}
autocompleteEnabledInput.addEventListener( 'change', function( e ) {
if ( e.target.checked ) {
preferredProviderRow.style.display = 'table-row';
} else {
preferredProviderRow.style.display = 'none';
}
} );
}
"
);
}
}
return new WC_Settings_General();

View File

@@ -0,0 +1,103 @@
<?php
/**
* WooCommerce Integration Settings
*
* @package WooCommerce\Admin
* @version 2.1.0
*/
use Automattic\Jetpack\Constants;
defined( 'ABSPATH' ) || exit;
if ( ! class_exists( 'WC_Settings_Integrations', false ) ) :
/**
* WC_Settings_Integrations.
*/
class WC_Settings_Integrations extends WC_Settings_Page {
/**
* Constructor.
*/
public function __construct() {
$this->id = 'integration';
$this->label = __( 'Integration', 'woocommerce' );
if ( isset( WC()->integrations ) && WC()->integrations->get_integrations() ) {
parent::__construct();
}
}
/**
* Setting page icon.
*
* @var string
*/
public $icon = 'plugins';
/**
* Get own sections.
*
* @return array
*/
protected function get_own_sections() {
global $current_section;
$sections = array();
if ( ! $this->wc_is_installing() ) {
$integrations = $this->get_integrations();
if ( ! $current_section && ! empty( $integrations ) ) {
$current_section = current( $integrations )->id;
}
if ( count( $integrations ) > 1 ) {
foreach ( $integrations as $integration ) {
$title = empty( $integration->method_title ) ? ucfirst( $integration->id ) : $integration->method_title;
$sections[ strtolower( $integration->id ) ] = esc_html( $title );
}
}
}
return $sections;
}
/**
* Is WC_INSTALLING constant defined?
* This method exists to ease unit testing.
*
* @return bool True is the WC_INSTALLING constant is defined.
*/
protected function wc_is_installing() {
return Constants::is_defined( 'WC_INSTALLING' );
}
/**
* Get the currently available integrations.
* This method exists to ease unit testing.
*
* @return array Currently available integrations.
*/
protected function get_integrations() {
return WC()->integrations->get_integrations();
}
/**
* Output the settings.
*/
public function output() {
global $current_section;
$integrations = $this->get_integrations();
if ( isset( $integrations[ $current_section ] ) ) {
$integrations[ $current_section ]->admin_options();
}
}
}
endif;
return new WC_Settings_Integrations();

View File

@@ -0,0 +1,553 @@
<?php
/**
* WooCommerce Settings Page/Tab
*
* @package WooCommerce\Admin
* @version 2.1.0
*/
declare( strict_types = 1);
use Automattic\WooCommerce\Admin\Features\Features;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
if ( ! class_exists( 'WC_Settings_Page', false ) ) :
/**
* WC_Settings_Page.
*/
abstract class WC_Settings_Page {
/**
* Setting page id.
*
* @var string
*/
protected $id = '';
/**
* Setting page icon.
*
* @var string
*/
public $icon = 'settings';
/**
* Setting field types.
*
* @var string
*/
const TYPE_TITLE = 'title';
const TYPE_INFO = 'info';
const TYPE_SECTIONEND = 'sectionend';
const TYPE_TEXT = 'text';
const TYPE_PASSWORD = 'password';
const TYPE_DATETIME = 'datetime';
const TYPE_DATETIME_LOCAL = 'datetime-local';
const TYPE_DATE = 'date';
const TYPE_MONTH = 'month';
const TYPE_TIME = 'time';
const TYPE_WEEK = 'week';
const TYPE_NUMBER = 'number';
const TYPE_EMAIL = 'email';
const TYPE_URL = 'url';
const TYPE_TEL = 'tel';
const TYPE_COLOR = 'color';
const TYPE_TEXTAREA = 'textarea';
const TYPE_SELECT = 'select';
const TYPE_MULTISELECT = 'multiselect';
const TYPE_RADIO = 'radio';
const TYPE_CHECKBOX = 'checkbox';
const TYPE_IMAGE_WIDTH = 'image_width';
const TYPE_SINGLE_SELECT_PAGE = 'single_select_page';
const TYPE_SINGLE_SELECT_PAGE_WITH_SEARCH = 'single_select_page_with_search';
const TYPE_SINGLE_SELECT_COUNTRY = 'single_select_country';
const TYPE_MULTI_SELECT_COUNTRIES = 'multi_select_countries';
const TYPE_RELATIVE_DATE_SELECTOR = 'relative_date_selector';
const TYPE_SLOTFILL_PLACEHOLDER = 'slotfill_placeholder';
/**
* Settings field types which are known.
*
* @var string[]
*/
protected $types = array(
self::TYPE_TITLE,
self::TYPE_INFO,
self::TYPE_SECTIONEND,
self::TYPE_TEXT,
self::TYPE_PASSWORD,
self::TYPE_DATETIME,
self::TYPE_DATETIME_LOCAL,
self::TYPE_DATE,
self::TYPE_MONTH,
self::TYPE_TIME,
self::TYPE_WEEK,
self::TYPE_NUMBER,
self::TYPE_EMAIL,
self::TYPE_URL,
self::TYPE_TEL,
self::TYPE_COLOR,
self::TYPE_TEXTAREA,
self::TYPE_SELECT,
self::TYPE_MULTISELECT,
self::TYPE_RADIO,
self::TYPE_CHECKBOX,
self::TYPE_IMAGE_WIDTH,
self::TYPE_SINGLE_SELECT_PAGE,
self::TYPE_SINGLE_SELECT_PAGE_WITH_SEARCH,
self::TYPE_SINGLE_SELECT_COUNTRY,
self::TYPE_MULTI_SELECT_COUNTRIES,
self::TYPE_RELATIVE_DATE_SELECTOR,
self::TYPE_SLOTFILL_PLACEHOLDER,
);
/**
* Setting page label.
*
* @var string
*/
protected $label = '';
/**
* Setting page is modern.
*
* @var bool
*/
protected $is_modern = false;
/**
* Whether the output method has been called.
*
* @var bool
*/
private $output_called = false;
/**
* Constructor.
*/
public function __construct() {
add_filter( 'woocommerce_settings_tabs_array', array( $this, 'add_settings_page' ), 20 );
add_action( 'woocommerce_sections_' . $this->id, array( $this, 'output_sections' ) );
add_action( 'woocommerce_settings_' . $this->id, array( $this, 'output' ) );
add_action( 'woocommerce_settings_save_' . $this->id, array( $this, 'save' ) );
add_action( 'woocommerce_admin_field_add_settings_slot', array( $this, 'add_settings_slot' ) );
}
/**
* Get settings page ID.
*
* @since 3.0.0
* @return string
*/
public function get_id() {
return $this->id;
}
/**
* Get settings page label.
*
* @since 3.0.0
* @return string
*/
public function get_label() {
return $this->label;
}
/**
* Creates the React mount point for settings slot.
*/
public function add_settings_slot() {
?>
<div id="wc_settings_slotfill"> </div>
<?php
}
/**
* Add this page to settings.
*
* @param array $pages The settings array where we'll add ourselves.
*
* @return mixed
*/
public function add_settings_page( $pages ) {
$pages[ $this->id ] = $this->label;
return $pages;
}
/**
* Get page settings data to populate the settings editor.
*
* @param array $pages The settings array where we'll add data.
*
* @return array
*/
public function add_settings_page_data( $pages ) {
global $current_section;
$saved_current_section = $current_section;
$sections = $this->get_sections();
$sections_data = array();
// Loop through each section and get the settings for that section.
foreach ( $sections as $section_id => $section_label ) {
$current_section = $section_id;
$section_settings_data = $this->get_section_settings_data( $section_id, $sections );
// Replace empty string section ids with 'default'.
$normalized_section_id = '' === $section_id ? 'default' : $section_id;
$sections_data[ $normalized_section_id ] = array(
'label' => html_entity_decode( esc_html( $section_label ) ),
'settings' => $section_settings_data,
);
}
// Reset the current section to the saved current section.
$current_section = $saved_current_section;
$pages[ $this->id ] = array(
'label' => html_entity_decode( $this->label ),
'slug' => $this->id,
'icon' => $this->icon,
'sections' => $sections_data,
'is_modern' => $this->is_modern,
);
$pages[ $this->id ]['start'] = $this->get_custom_view( 'woocommerce_before_settings_' . $this->id );
$pages[ $this->id ]['end'] = $this->get_custom_view( 'woocommerce_after_settings_' . $this->id );
return $pages;
}
/**
* Get settings data for a specific section.
*
* @param string $section_id The ID of the section.
* @param array $sections All sections available.
* @return array Settings data for the section.
*/
protected function get_section_settings_data( $section_id, $sections ) {
$section_settings_data = array();
$custom_view = $this->get_custom_view( 'woocommerce_settings_' . $this->id, $section_id );
// We only want to loop through the settings object if the parent class's output method is being rendered during the get_custom_view call.
if ( $this->output_called ) {
$section_settings = count( $sections ) > 1
? $this->get_settings_for_section( $section_id )
: $this->get_settings();
// Loop through each setting in the section and add the value to the settings data.
foreach ( $section_settings as $section_setting ) {
// Add custom views for sectionend.
if ( 'sectionend' === $section_setting['type'] && ! empty( $section_setting['id'] ) ) {
$section_settings_data[] = $this->get_custom_view( 'woocommerce_settings_' . $section_setting['id'] . '_end' );
$section_settings_data[] = $this->get_custom_view( 'woocommerce_settings_' . $section_setting['id'] . '_after' );
}
$section_settings_data[] = $this->populate_setting_value( $section_setting );
// Add custom views for title.
if ( 'title' === $section_setting['type'] && ! empty( $section_setting['id'] ) ) {
$section_settings_data[] = $this->get_custom_view( 'woocommerce_settings_' . $section_setting['id'] );
}
}
}
// If the custom view has output, add it to the settings data.
if ( ! empty( $custom_view ) ) {
$section_settings_data[] = $custom_view;
}
// Reset the output_called property.
$this->output_called = false;
return $section_settings_data;
}
/**
* Populate the value for a given section setting.
*
* @param array $section_setting The setting array to populate.
* @return array The setting array with populated value.
*/
protected function populate_setting_value( $section_setting ) {
if ( isset( $section_setting['id'] ) ) {
$section_setting['value'] = isset( $section_setting['default'] )
// Fallback to the default value if it exists.
? get_option( $section_setting['id'], $section_setting['default'] )
// Otherwise, fallback to false.
: get_option( $section_setting['id'] );
}
$type = $section_setting['type'];
if ( ! in_array( $type, $this->types, true ) ) {
$section_setting = $this->get_custom_type_field( 'woocommerce_admin_field_' . $type, $section_setting );
}
return $section_setting;
}
/**
* Get the custom view given the current tab and section.
*
* @param string $action The action to call.
* @param string $section_id The section id.
* @return string The custom view. HTML output.
*/
public function get_custom_view( $action, $section_id = false ) {
global $current_section;
if ( $section_id ) {
// Make sure the current section is set to the sectionid here. Reset it at the end of the function.
$saved_current_section = $current_section;
// set global current_section to the section_id.
$current_section = $section_id;
}
ob_start();
/**
* Output the custom view given the current tab and section by calling the action.
*
* @since 2.1.0
*/
do_action( $action );
$html = ob_get_contents();
ob_end_clean();
// Reset the global variable.
if ( $section_id ) {
$current_section = $saved_current_section;
}
$content = trim( $html );
if ( empty( $content ) ) {
return null;
}
return array(
'id' => wp_unique_prefixed_id( 'settings_custom_view' ),
'type' => 'custom',
'content' => $content,
);
}
/**
* Get the custom type field by calling the action and returning the setting with the content, id, and type.
*
* @param string $action The action to call.
* @param array $setting The setting to pass to the action.
* @return array The setting with the content, id, and type.
*/
public function get_custom_type_field( $action, $setting ) {
ob_start();
/**
* Output the custom type field by calling the action.
*
* @since 3.3.0
*/
do_action( $action, $setting );
$html = ob_get_contents();
ob_end_clean();
$setting['content'] = trim( $html );
$setting['id'] = isset( $setting['id'] ) ? $setting['id'] : wp_unique_prefixed_id( 'settings_custom_view' );
$setting['type'] = 'custom';
return $setting;
}
/**
* Get settings array for the default section.
*
* External settings classes (registered via 'woocommerce_get_settings_pages' filter)
* might have redefined this method as "get_settings($section_id='')", thus we need
* to use this method internally instead of 'get_settings_for_section' to register settings
* and render settings pages.
*
* *But* we can't just redefine the method as "get_settings($section_id='')" here, since this
* will break on PHP 8 if any external setting class have it as 'get_settings()'.
*
* Thus we leave the method signature as is and use 'func_get_arg' to get the setting id
* if it's supplied, and we use this method internally; but it's deprecated and should
* otherwise never be used.
*
* @deprecated 5.4.0 Use 'get_settings_for_section' (passing an empty string for default section)
*
* @return array Settings array, each item being an associative array representing a setting.
*/
public function get_settings() {
$section_id = 0 === func_num_args() ? '' : func_get_arg( 0 );
return $this->get_settings_for_section( $section_id );
}
/**
* Get settings array.
*
* The strategy for getting the settings is as follows:
*
* - If a method named 'get_settings_for_{section_id}_section' exists in the class
* it will be invoked (for the default '' section, the method name is 'get_settings_for_default_section').
* Derived classes can implement these methods as required.
*
* - Otherwise, 'get_settings_for_section_core' will be invoked. Derived classes can override it
* as an alternative to implementing 'get_settings_for_{section_id}_section' methods.
*
* @param string $section_id The id of the section to return settings for, an empty string for the default section.
*
* @return array Settings array, each item being an associative array representing a setting.
*/
final public function get_settings_for_section( $section_id ) {
if ( '' === $section_id ) {
$method_name = 'get_settings_for_default_section';
} else {
$method_name = "get_settings_for_{$section_id}_section";
}
if ( method_exists( $this, $method_name ) ) {
$settings = $this->$method_name();
} else {
$settings = $this->get_settings_for_section_core( $section_id );
}
return apply_filters( 'woocommerce_get_settings_' . $this->id, $settings, $section_id );
}
/**
* Get the settings for a given section.
* This method is invoked from 'get_settings_for_section' when no 'get_settings_for_{current_section}_section'
* method exists in the class.
*
* When overriding, note that the 'woocommerce_get_settings_' filter must NOT be triggered,
* as this is already done by 'get_settings_for_section'.
*
* @param string $section_id The section name to get the settings for.
*
* @return array Settings array, each item being an associative array representing a setting.
*/
protected function get_settings_for_section_core( $section_id ) {
return array();
}
/**
* Get all sections for this page, both the own ones and the ones defined via filters.
*
* @return array
*/
public function get_sections() {
$sections = $this->get_own_sections();
/**
* Filters the sections for this settings page.
*
* @since 2.2.0
* @param array $sections The sections for this settings page.
*/
return (array) apply_filters( 'woocommerce_get_sections_' . $this->id, $sections );
}
/**
* Get own sections for this page.
* Derived classes should override this method if they define sections.
* There should always be one default section with an empty string as identifier.
*
* Example:
* return array(
* '' => __( 'General', 'woocommerce' ),
* 'foobars' => __( 'Foos & Bars', 'woocommerce' ),
* );
*
* @return array An associative array where keys are section identifiers and the values are translated section names.
*/
protected function get_own_sections() {
return array( '' => __( 'General', 'woocommerce' ) );
}
/**
* Output sections.
*/
public function output_sections() {
global $current_section;
$sections = $this->get_sections();
if ( empty( $sections ) || 1 === count( $sections ) ) {
return;
}
echo '<ul class="subsubsub">';
$array_keys = array_keys( $sections );
foreach ( $sections as $id => $label ) {
$url = admin_url( 'admin.php?page=wc-settings&tab=' . $this->id . '&section=' . sanitize_title( $id ) );
$class = ( $current_section === $id ? 'current' : '' );
$separator = ( end( $array_keys ) === $id ? '' : '|' );
$text = esc_html( $label );
echo "<li><a href='$url' class='$class'>$text</a> $separator </li>"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '</ul><br class="clear" />';
}
/**
* Output the HTML for the settings.
*/
public function output() {
$this->output_called = true;
if ( Features::is_enabled( 'settings' ) ) {
return;
}
global $current_section;
// We can't use "get_settings_for_section" here
// for compatibility with derived classes overriding "get_settings".
$settings = $this->get_settings( $current_section );
WC_Admin_Settings::output_fields( $settings );
}
/**
* Save settings and trigger the 'woocommerce_update_options_'.id action.
*/
public function save() {
$this->save_settings_for_current_section();
$this->do_update_options_action();
}
/**
* Save settings for current section.
*/
protected function save_settings_for_current_section() {
global $current_section;
// We can't use "get_settings_for_section" here
// for compatibility with derived classes overriding "get_settings".
$settings = $this->get_settings( $current_section );
WC_Admin_Settings::save_fields( $settings );
}
/**
* Trigger the 'woocommerce_update_options_'.id action.
*
* @param string $section_id Section to trigger the action for, or null for current section.
*/
protected function do_update_options_action( $section_id = null ) {
global $current_section;
if ( is_null( $section_id ) ) {
$section_id = $current_section;
}
if ( $section_id ) {
do_action( 'woocommerce_update_options_' . $this->id . '_' . $section_id );
}
}
}
endif;

View File

@@ -0,0 +1,457 @@
<?php
/**
* WooCommerce Checkout Settings
*
* @package WooCommerce\Admin
*/
declare( strict_types = 1 );
use Automattic\WooCommerce\Internal\Admin\Loader;
defined( 'ABSPATH' ) || exit;
if ( class_exists( 'WC_Settings_Payment_Gateways', false ) ) {
return new WC_Settings_Payment_Gateways();
}
/**
* WC_Settings_Payment_Gateways.
*/
class WC_Settings_Payment_Gateways extends WC_Settings_Page {
const TAB_NAME = 'checkout';
const MAIN_SECTION_NAME = 'main';
const OFFLINE_SECTION_NAME = 'offline';
const COD_SECTION_NAME = 'cod'; // Cash on delivery.
const BACS_SECTION_NAME = 'bacs'; // Direct bank transfer.
const CHEQUE_SECTION_NAME = 'cheque'; // Cheque payments.
/**
* Setting page icon.
*
* @var string
*/
public $icon = 'payment';
/**
* Memoized list of sections to render using React.
*
* @var array|null
*/
private ?array $reactified_sections_memo = null;
/**
* Constructor.
*/
public function __construct() {
$this->id = self::TAB_NAME;
$this->label = esc_html_x( 'Payments', 'Settings tab label', 'woocommerce' );
// Add filters and actions.
add_filter( 'admin_body_class', array( $this, 'add_body_classes' ), 30 );
add_action( 'admin_head', array( $this, 'hide_help_tabs' ) );
// Hook in as late as possible - `in_admin_header` is the last action before the `admin_notices` action is fired.
// It is too risky to hook into `admin_notices` with a low priority because the callbacks might be cached.
add_action( 'in_admin_header', array( $this, 'suppress_admin_notices' ), PHP_INT_MAX );
// Do not show any store alerts (WC admin notes with type: 'error,update' and status: 'unactioned')
// on the WooCommerce Payments settings page and Reactified sections.
add_filter( 'woocommerce_admin_features', array( $this, 'suppress_store_alerts' ), PHP_INT_MAX );
parent::__construct();
}
/**
* Check if the given section should be rendered using React.
*
* @param mixed $section The section name to check.
* Since this value originates from the global `$current_section` variable,
* it is best to accept anything and standardize it to a string.
*
* @return bool Whether the section should be rendered using React.
*/
public function should_render_react_section( $section ): bool {
return in_array( $this->standardize_section_name( $section ), $this->get_reactified_sections(), true );
}
/**
* Add body classes.
*
* @param string $classes The existing body classes.
*
* @return string The modified body classes.
*/
public function add_body_classes( $classes ) {
global $current_tab, $current_section;
// Bail if the $classes variable is not a string.
if ( ! is_string( $classes ) ) {
return $classes;
}
// If we are not on the WooCommerce Payments settings page, return the classes as they are.
if ( self::TAB_NAME !== $current_tab ) {
return $classes;
}
if ( ! $this->should_render_react_section( $current_section ) ) {
// Add a class to indicate that the payments settings section page is rendered in legacy mode.
$classes .= ' woocommerce-settings-payments-section_legacy';
// Add a class to indicate that the current section is rendered in legacy mode.
$classes .= ' woocommerce_page_wc-settings-checkout-section-' . esc_attr( $this->standardize_section_name( $current_section ) ) . '_legacy';
}
return $classes;
}
/**
* Output the settings.
*/
public function output() {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
global $current_section;
// We don't want to output anything from the action for now. So we buffer it and discard it.
ob_start();
/**
* Fires before the payment gateways settings fields are rendered.
*
* @since 1.5.7
*/
do_action( 'woocommerce_admin_field_payment_gateways' );
ob_end_clean();
if ( is_string( $current_section ) && $this->should_render_react_section( $current_section ) ) {
$this->render_react_section( $this->standardize_section_name( $current_section ) );
} elseif ( is_string( $current_section ) && ! empty( $current_section ) ) {
// Load gateways so we can show any global options they may have.
$payment_gateways = WC()->payment_gateways()->payment_gateways;
$this->render_classic_gateway_settings_page( $payment_gateways, $current_section );
} else {
$this->render_react_section( self::MAIN_SECTION_NAME );
}
parent::output();
//phpcs:enable
}
/**
* Get settings array.
*
* This is just for backward compatibility with the rest of the codebase (primarily API responses).
*
* @return array
*/
protected function get_settings_for_default_section() {
return array(
array(
'type' => 'title',
// this is needed as <table> tag is generated by this element, even if it has no other content.
),
array(
'type' => 'sectionend',
'id' => 'payment_gateways_options',
),
);
}
/**
* Get the whitelist of sections to render using React.
*
* @return array List of section identifiers.
*/
private function get_reactified_sections(): array {
if ( ! is_null( $this->reactified_sections_memo ) ) {
return $this->reactified_sections_memo;
}
// These sections are always rendered using React.
$reactified_sections = array(
self::MAIN_SECTION_NAME,
self::OFFLINE_SECTION_NAME,
);
// These sections are optional and can be modified by plugins or themes.
$optional_reactified_sections = array(
self::COD_SECTION_NAME,
self::BACS_SECTION_NAME,
self::CHEQUE_SECTION_NAME,
);
/**
* Modify the optional set of payments settings sections to be rendered using React.
*
* This filter allows plugins to add or remove optional sections (typically offline gateways)
* that should be rendered using React. Sections should be identified by their gateway IDs.
* Note: The main Payments page ("main") and the Offline overview ("offline") are always React-only
* and cannot be disabled via this filter.
*
* @since 9.3.0
*
* @param array $sections List of section identifiers to be rendered using React.
*/
$optional_reactified_sections = apply_filters( 'experimental_woocommerce_admin_payment_reactify_render_sections', $optional_reactified_sections );
if ( empty( $optional_reactified_sections ) || ! is_array( $optional_reactified_sections ) ) {
// Sanity check: use empty array if the filter returns something unexpected.
$optional_reactified_sections = array();
} else {
// Enforce a list format and string-only values for section identifiers.
$optional_reactified_sections = array_values( array_filter( $optional_reactified_sections, 'is_string' ) );
}
$this->reactified_sections_memo = array_unique( array_merge( $reactified_sections, $optional_reactified_sections ) );
return $this->reactified_sections_memo;
}
/**
* Standardize the current section name.
*
* @param mixed $section The section name to standardize.
*
* @return string The standardized section name.
*/
private function standardize_section_name( $section ): string {
$section = (string) $section;
// If the section is empty, we are on the main settings page/section. Use a standardized name.
if ( '' === $section ) {
return self::MAIN_SECTION_NAME;
}
return $section;
}
/**
* Render the React section.
*
* @param string $section The section to render.
*/
private function render_react_section( string $section ) {
global $hide_save_button;
$hide_save_button = true;
echo '<div id="experimental_wc_settings_payments_' . esc_attr( $section ) . '"></div>';
}
/**
* Render the classic gateway settings page.
*
* @param array $payment_gateways The payment gateways.
* @param string $current_section The current section.
*/
private function render_classic_gateway_settings_page( array $payment_gateways, string $current_section ) {
foreach ( $payment_gateways as $gateway ) {
if ( in_array( $current_section, array( $gateway->id, sanitize_title( get_class( $gateway ) ) ), true ) ) {
if ( isset( $_GET['toggle_enabled'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$enabled = $gateway->get_option( 'enabled' );
if ( $enabled ) {
$gateway->settings['enabled'] = wc_string_to_bool( $enabled ) ? 'no' : 'yes';
}
}
$this->run_gateway_admin_options( $gateway );
break;
}
}
}
/**
* Run the 'admin_options' method on a given gateway.
*
* This method exists to help with unit testing.
*
* @param object $gateway The gateway object to run the method on.
*/
protected function run_gateway_admin_options( $gateway ) {
$gateway->admin_options();
}
/**
* Get all sections for the current page.
*
* Reactified section pages won't have any sections.
* The rest of the settings pages will get the default/own section and those added via
* the `woocommerce_get_sections_checkout` filter.
*
* @return array The sections for this settings page.
*/
public function get_sections() {
global $current_tab, $current_section;
// We only want to prevent sections on the main WooCommerce Payments settings page and Reactified sections.
if ( self::TAB_NAME === $current_tab && $this->should_render_react_section( $current_section ) ) {
return array();
}
return parent::get_sections();
}
/**
* Save settings.
*/
public function save() {
global $current_section;
$standardized_section = $this->standardize_section_name( $current_section );
$wc_payment_gateways = WC_Payment_Gateways::instance();
$this->save_settings_for_current_section();
if ( self::MAIN_SECTION_NAME === $standardized_section ) {
// This makes sure 'gateway ordering' is saved.
$wc_payment_gateways->process_admin_options();
$wc_payment_gateways->init();
} else {
// This may be a gateway or some custom section.
foreach ( $wc_payment_gateways->payment_gateways() as $gateway ) {
// If the section is that of a gateway, we need to run the gateway actions and init.
if ( in_array( $standardized_section, array( $gateway->id, sanitize_title( get_class( $gateway ) ) ), true ) ) {
/**
* Fires update actions for payment gateways.
*
* @since 3.4.0
*
* @param int $gateway->id Gateway ID.
*/
do_action( 'woocommerce_update_options_payment_gateways_' . $gateway->id );
$wc_payment_gateways->init();
// There is no need to run the action and gateways init again
// since we can't be on the section page of multiple gateways at once.
break;
}
}
$this->do_update_options_action();
}
}
/**
* Hide the help tabs.
*/
public function hide_help_tabs() {
global $current_tab, $current_section;
$screen = get_current_screen();
if ( ! $screen instanceof WP_Screen || 'woocommerce_page_wc-settings' !== $screen->id ) {
return;
}
// We only want to hide the help tabs on the main WooCommerce Payments settings page and Reactified sections.
if ( self::TAB_NAME !== $current_tab ) {
return;
}
if ( ! $this->should_render_react_section( $current_section ) ) {
return;
}
$screen->remove_help_tabs();
}
/**
* Suppress WP admin notices on the WooCommerce Payments settings page.
*/
public function suppress_admin_notices() {
global $wp_filter, $current_tab, $current_section;
$screen = get_current_screen();
if ( ! $screen instanceof WP_Screen || 'woocommerce_page_wc-settings' !== $screen->id ) {
return;
}
// We only want to suppress notices on the main WooCommerce Payments settings page and Reactified sections.
if ( self::TAB_NAME !== $current_tab ) {
return;
}
if ( ! $this->should_render_react_section( $current_section ) ) {
return;
}
// Generic admin notices are definitely not needed.
remove_all_actions( 'all_admin_notices' );
// WooCommerce uses the 'admin_notices' hook for its own notices.
// We will only allow WooCommerce core notices to be displayed.
$wp_admin_notices_hook = $wp_filter['admin_notices'] ?? null;
if ( ! $wp_admin_notices_hook || ! $wp_admin_notices_hook->has_filters() ) {
// Nothing to do if there are no actions hooked into `admin_notices`.
return;
}
$wc_admin_notices = WC_Admin_Notices::get_notices();
if ( empty( $wc_admin_notices ) ) {
// If there are no WooCommerce core notices, we can remove all actions hooked into `admin_notices`.
remove_all_actions( 'admin_notices' );
return;
}
// Go through the callbacks hooked into `admin_notices` and
// remove any that are NOT from the WooCommerce core (i.e. from the `WC_Admin_Notices` class).
foreach ( $wp_admin_notices_hook->callbacks as $priority => $callbacks ) {
if ( ! is_array( $callbacks ) ) {
continue;
}
foreach ( $callbacks as $callback ) {
// Ignore malformed callbacks.
if ( ! is_array( $callback ) ) {
continue;
}
// WooCommerce doesn't use closures to handle notices.
// WooCommerce core notices are handled by `WC_Admin_Notices` class methods.
// Remove plain functions or closures.
if ( ! is_array( $callback['function'] ) ) {
remove_action( 'admin_notices', $callback['function'], $priority );
continue;
}
$class_or_object = $callback['function'][0] ?? null;
// We need to allow Automattic\WooCommerce\Internal\Admin\Loader methods callbacks
// because they are used to wrap notices.
// @see Automattic\WooCommerce\Internal\Admin\Loader::inject_before_notices().
// @see Automattic\WooCommerce\Internal\Admin\Loader::inject_after_notices().
if (
(
// We have a class name.
is_string( $class_or_object ) &&
! ( WC_Admin_Notices::class === $class_or_object || Loader::class === $class_or_object )
) ||
(
// We have a class instance.
is_object( $class_or_object ) &&
! ( $class_or_object instanceof WC_Admin_Notices || $class_or_object instanceof Loader )
)
) {
remove_action( 'admin_notices', $callback['function'], $priority );
}
}
}
}
/**
* Suppress the store-alerts WCAdmin feature on the WooCommerce Payments settings page and Reactified sections.
*
* @param mixed $features The WCAdmin features list.
*
* @return mixed The modified features list.
*/
public function suppress_store_alerts( $features ) {
global $current_tab, $current_section;
$feature_name = 'store-alerts';
if ( is_array( $features ) &&
in_array( $feature_name, $features, true ) &&
self::TAB_NAME === $current_tab &&
$this->should_render_react_section( $current_section ) ) {
unset( $features[ array_search( $feature_name, $features, true ) ] );
}
return $features;
}
}
return new WC_Settings_Payment_Gateways();

View File

@@ -0,0 +1,127 @@
<?php
/**
* WooCommerce Point of Sale Settings
*
* @package WooCommerce\Admin
*/
declare(strict_types=1);
use Automattic\WooCommerce\Admin\Features\Features;
use Automattic\WooCommerce\Internal\Settings\PointOfSaleDefaultSettings;
use Automattic\WooCommerce\Utilities\FeaturesUtil;
defined( 'ABSPATH' ) || exit;
if ( class_exists( 'WC_Settings_Point_Of_Sale', false ) ) {
return new WC_Settings_Point_Of_Sale();
}
/**
* WC_Settings_Point_Of_Sale.
*/
class WC_Settings_Point_Of_Sale extends WC_Settings_Page {
/**
* Constructor.
*/
public function __construct() {
$this->id = 'point-of-sale';
$this->label = __( 'Point of Sale', 'woocommerce' );
parent::__construct();
add_filter( 'woocommerce_settings_tabs_array', array( $this, 'add_settings_page' ), 20 );
}
/**
* Setting page icon.
*
* @var string
*/
public $icon = 'store';
/**
* Add Point of Sale page to settings if the feature is enabled.
*
* @param array $pages Existing pages.
* @return array|mixed
*
* @internal For exclusive usage within this class, backwards compatibility not guaranteed.
*/
public function add_settings_page( $pages ) {
if ( FeaturesUtil::feature_is_enabled( 'point_of_sale' ) ) {
return parent::add_settings_page( $pages );
} else {
return $pages;
}
}
/**
* Get settings for the default section.
*
* @return array
*/
protected function get_settings_for_default_section() {
return array(
array(
'title' => __( 'Store details', 'woocommerce' ),
'type' => 'title',
'desc' => __( 'Details about the store that are shown in email receipts.', 'woocommerce' ),
'id' => 'store_details',
),
array(
'title' => __( 'Store name', 'woocommerce' ),
'desc' => __( 'The name of your physical store.', 'woocommerce' ),
'id' => 'woocommerce_pos_store_name',
'default' => PointOfSaleDefaultSettings::get_default_store_name(),
'type' => 'text',
'css' => 'min-width:300px;',
),
array(
'title' => __( 'Physical address', 'woocommerce' ),
'id' => 'woocommerce_pos_store_address',
'default' => PointOfSaleDefaultSettings::get_default_store_address(),
'type' => 'textarea',
'css' => 'min-width:300px; height: 100px;',
'desc_tip' => true,
),
array(
'title' => __( 'Phone number', 'woocommerce' ),
'id' => 'woocommerce_pos_store_phone',
'default' => '',
'type' => 'text',
'css' => 'min-width:300px;',
),
array(
'title' => __( 'Email', 'woocommerce' ),
'desc' => __( 'Your store contact email.', 'woocommerce' ),
'id' => 'woocommerce_pos_store_email',
'default' => PointOfSaleDefaultSettings::get_default_store_email(),
'type' => 'email',
'css' => 'min-width:300px;',
),
array(
'title' => __( 'Refund & Returns Policy', 'woocommerce' ),
'desc' => __( 'Brief statement that will appear on the receipts.', 'woocommerce' ),
'id' => 'woocommerce_pos_refund_returns_policy',
'default' => '',
'type' => 'textarea',
'css' => 'min-width:300px; height: 100px;',
'desc_tip' => true,
),
array(
'type' => 'sectionend',
'id' => 'store_details',
),
);
}
}
return new WC_Settings_Point_Of_Sale();

View File

@@ -0,0 +1,499 @@
<?php
/**
* WooCommerce Product Settings
*
* @package WooCommerce\Admin
* @version 2.4.0
*/
use Automattic\WooCommerce\Utilities\I18nUtil;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( class_exists( 'WC_Settings_Products', false ) ) {
return new WC_Settings_Products();
}
/**
* WC_Settings_Products.
*/
class WC_Settings_Products extends WC_Settings_Page {
/**
* Constructor.
*/
public function __construct() {
$this->id = 'products';
$this->label = __( 'Products', 'woocommerce' );
parent::__construct();
}
/**
* Setting page icon.
*
* @var string
*/
public $icon = 'box';
/**
* Get own sections.
*
* @return array
*/
protected function get_own_sections() {
return array(
'' => __( 'General', 'woocommerce' ),
'inventory' => __( 'Inventory', 'woocommerce' ),
'downloadable' => __( 'Downloadable products', 'woocommerce' ),
);
}
/**
* Get settings for the default section.
*
* @return array
*/
protected function get_settings_for_default_section() {
$locale_info = include WC()->plugin_path() . '/i18n/locale-info.php';
$default_weight_unit = $locale_info['US']['weight_unit'];
$default_dimension_unit = $locale_info['US']['dimension_unit'];
$settings =
array(
array(
'title' => __( 'Shop pages', 'woocommerce' ),
'type' => 'title',
'desc' => '',
'id' => 'catalog_options',
),
array(
'title' => __( 'Shop page', 'woocommerce' ),
/* translators: %s: URL to settings. */
'desc' => sprintf( __( 'The base page can also be used in your <a href="%s">product permalinks</a>.', 'woocommerce' ), admin_url( 'options-permalink.php' ) ),
'id' => 'woocommerce_shop_page_id',
'type' => 'single_select_page',
'default' => '',
'class' => 'wc-enhanced-select-nostd',
'css' => 'min-width:300px;',
'desc_tip' => __( 'This sets the base page of your shop - this is where your product archive will be.', 'woocommerce' ),
),
array(
'title' => __( 'Add to cart behaviour', 'woocommerce' ),
'desc' => __( 'Redirect to the cart page after successful addition', 'woocommerce' ),
'id' => 'woocommerce_cart_redirect_after_add',
'default' => 'no',
'type' => 'checkbox',
'checkboxgroup' => 'start',
),
array(
'desc' => __( 'Enable AJAX add to cart buttons on archives', 'woocommerce' ),
'id' => 'woocommerce_enable_ajax_add_to_cart',
'default' => 'yes',
'type' => 'checkbox',
'checkboxgroup' => 'end',
),
array(
'title' => __( 'Placeholder image', 'woocommerce' ),
'id' => 'woocommerce_placeholder_image',
'type' => 'text',
'default' => '',
'class' => '',
'css' => '',
'placeholder' => __( 'Enter attachment ID or URL to an image', 'woocommerce' ),
'desc_tip' => __( 'This is the attachment ID, or image URL, used for placeholder images in the product catalog. Products with no image will use this.', 'woocommerce' ),
),
array(
'type' => 'sectionend',
'id' => 'catalog_options',
),
array(
'title' => __( 'Measurements', 'woocommerce' ),
'type' => 'title',
'id' => 'product_measurement_options',
),
array(
'title' => __( 'Weight unit', 'woocommerce' ),
'desc' => __( 'This controls what unit you will define weights in.', 'woocommerce' ),
'id' => 'woocommerce_weight_unit',
'class' => 'wc-enhanced-select',
'css' => 'min-width:300px;',
'default' => $default_weight_unit,
'type' => 'select',
'options' => array(
'kg' => I18nUtil::get_weight_unit_label( 'kg' ),
'g' => I18nUtil::get_weight_unit_label( 'g' ),
'lbs' => I18nUtil::get_weight_unit_label( 'lbs' ),
'oz' => I18nUtil::get_weight_unit_label( 'oz' ),
),
'desc_tip' => true,
),
array(
'title' => __( 'Dimensions unit', 'woocommerce' ),
'desc' => __( 'This controls what unit you will define lengths in.', 'woocommerce' ),
'id' => 'woocommerce_dimension_unit',
'class' => 'wc-enhanced-select',
'css' => 'min-width:300px;',
'default' => $default_dimension_unit,
'type' => 'select',
'options' => array(
'm' => I18nUtil::get_dimensions_unit_label( 'm' ),
'cm' => I18nUtil::get_dimensions_unit_label( 'cm' ),
'mm' => I18nUtil::get_dimensions_unit_label( 'mm' ),
'in' => I18nUtil::get_dimensions_unit_label( 'in' ),
'yd' => I18nUtil::get_dimensions_unit_label( 'yd' ),
),
'desc_tip' => true,
),
array(
'type' => 'sectionend',
'id' => 'product_measurement_options',
),
array(
'title' => __( 'Reviews', 'woocommerce' ),
'type' => 'title',
'desc' => '',
'id' => 'product_rating_options',
),
array(
'title' => __( 'Enable reviews', 'woocommerce' ),
'desc' => __( 'Enable product reviews', 'woocommerce' ),
'id' => 'woocommerce_enable_reviews',
'default' => 'yes',
'type' => 'checkbox',
'checkboxgroup' => 'start',
'show_if_checked' => 'option',
),
array(
'desc' => __( 'Show "verified owner" label on customer reviews', 'woocommerce' ),
'id' => 'woocommerce_review_rating_verification_label',
'default' => 'yes',
'type' => 'checkbox',
'checkboxgroup' => '',
'show_if_checked' => 'yes',
'autoload' => false,
),
array(
'desc' => __( 'Reviews can only be left by "verified owners"', 'woocommerce' ),
'id' => 'woocommerce_review_rating_verification_required',
'default' => 'no',
'type' => 'checkbox',
'checkboxgroup' => 'end',
'show_if_checked' => 'yes',
'autoload' => false,
),
array(
'title' => __( 'Product ratings', 'woocommerce' ),
'desc' => __( 'Enable star rating on reviews', 'woocommerce' ),
'id' => 'woocommerce_enable_review_rating',
'default' => 'yes',
'type' => 'checkbox',
'checkboxgroup' => 'start',
'show_if_checked' => 'option',
),
array(
'desc' => __( 'Star ratings should be required, not optional', 'woocommerce' ),
'id' => 'woocommerce_review_rating_required',
'default' => 'yes',
'type' => 'checkbox',
'checkboxgroup' => 'end',
'show_if_checked' => 'yes',
'autoload' => false,
),
array(
'type' => 'sectionend',
'id' => 'product_rating_options',
),
);
$settings = apply_filters( 'woocommerce_products_general_settings', $settings );
return apply_filters( 'woocommerce_product_settings', $settings );
}
/**
* Get settings for the inventory section.
*
* @return array
*/
protected function get_settings_for_inventory_section() {
$settings =
array(
array(
'title' => __( 'Inventory', 'woocommerce' ),
'type' => 'title',
'desc' => '',
'id' => 'product_inventory_options',
),
array(
'title' => __( 'Manage stock', 'woocommerce' ),
'desc' => __( 'Enable stock management', 'woocommerce' ),
'id' => 'woocommerce_manage_stock',
'default' => 'yes',
'type' => 'checkbox',
),
array(
'title' => __( 'Hold stock (minutes)', 'woocommerce' ),
'desc' => __( 'Hold stock (for unpaid orders) for x minutes. When this limit is reached, the pending order will be cancelled. Leave blank to disable.', 'woocommerce' ),
'id' => 'woocommerce_hold_stock_minutes',
'type' => 'number',
'custom_attributes' => array(
'min' => 0,
'step' => 1,
),
'css' => 'width: 80px;',
'default' => '60',
'autoload' => false,
'class' => 'manage_stock_field',
),
array(
'title' => __( 'Notifications', 'woocommerce' ),
'desc' => __( 'Enable low stock notifications', 'woocommerce' ),
'id' => 'woocommerce_notify_low_stock',
'default' => 'yes',
'type' => 'checkbox',
'checkboxgroup' => 'start',
'autoload' => false,
'class' => 'manage_stock_field',
),
array(
'desc' => __( 'Enable out of stock notifications', 'woocommerce' ),
'id' => 'woocommerce_notify_no_stock',
'default' => 'yes',
'type' => 'checkbox',
'checkboxgroup' => 'end',
'autoload' => false,
'class' => 'manage_stock_field',
),
array(
'title' => __( 'Notification recipient(s)', 'woocommerce' ),
'desc' => __( 'Enter recipients (comma separated) that will receive this notification.', 'woocommerce' ),
'id' => 'woocommerce_stock_email_recipient',
'type' => 'text',
'default' => get_option( 'admin_email' ),
'css' => 'width: 250px;',
'autoload' => false,
'desc_tip' => true,
'class' => 'manage_stock_field',
),
array(
'title' => __( 'Low stock threshold', 'woocommerce' ),
'desc' => __( 'When product stock reaches this amount you will be notified via email.', 'woocommerce' ),
'id' => 'woocommerce_notify_low_stock_amount',
'css' => 'width:50px;',
'type' => 'number',
'custom_attributes' => array(
'min' => 0,
'step' => 1,
),
'default' => '2',
'autoload' => false,
'desc_tip' => true,
'class' => 'manage_stock_field',
),
array(
'title' => __( 'Out of stock threshold', 'woocommerce' ),
'desc' => __( 'When product stock reaches this amount the stock status will change to "out of stock" and you will be notified via email. This setting does not affect existing "in stock" products.', 'woocommerce' ),
'id' => 'woocommerce_notify_no_stock_amount',
'css' => 'width:50px;',
'type' => 'number',
'custom_attributes' => array(
'min' => 0,
'step' => 1,
),
'default' => '0',
'desc_tip' => true,
'class' => 'manage_stock_field',
),
array(
'title' => __( 'Out of stock visibility', 'woocommerce' ),
'desc' => __( 'Hide out of stock items from the catalog', 'woocommerce' ),
'id' => 'woocommerce_hide_out_of_stock_items',
'default' => 'no',
'type' => 'checkbox',
),
array(
'title' => __( 'Stock display format', 'woocommerce' ),
'desc' => __( 'This controls how stock quantities are displayed on the frontend.', 'woocommerce' ),
'id' => 'woocommerce_stock_format',
'css' => 'min-width:150px;',
'class' => 'wc-enhanced-select',
'default' => '',
'type' => 'select',
'options' => array(
'' => __( 'Always show quantity remaining in stock e.g. "12 in stock"', 'woocommerce' ),
'low_amount' => __( 'Only show quantity remaining in stock when low e.g. "Only 2 left in stock"', 'woocommerce' ),
'no_amount' => __( 'Never show quantity remaining in stock', 'woocommerce' ),
),
'desc_tip' => true,
),
array(
'type' => 'sectionend',
'id' => 'product_inventory_options',
),
);
return apply_filters( 'woocommerce_inventory_settings', $settings );
}
/**
* Get settings for the downloadable section.
*
* @return array
*/
protected function get_settings_for_downloadable_section() {
$settings =
array(
array(
'title' => __( 'Downloadable products', 'woocommerce' ),
'type' => 'title',
'id' => 'digital_download_options',
),
array(
'title' => __( 'File download method', 'woocommerce' ),
'desc_tip' => sprintf(
/* translators: 1: X-Accel-Redirect 2: X-Sendfile 3: mod_xsendfile */
__( 'Forcing downloads will keep URLs hidden, but some servers may serve large files unreliably. If supported, %1$s / %2$s can be used to serve downloads instead (server requires %3$s).', 'woocommerce' ),
'<code>X-Accel-Redirect</code>',
'<code>X-Sendfile</code>',
'<code>mod_xsendfile</code>'
),
'id' => 'woocommerce_file_download_method',
'type' => 'select',
'class' => 'wc-enhanced-select',
'css' => 'min-width:300px;',
'default' => 'force',
'desc' => sprintf(
// translators: Link to WooCommerce Docs.
__( "If you are using X-Accel-Redirect download method along with NGINX server, make sure that you have applied settings as described in <a href='%s'>Digital/Downloadable Product Handling</a> guide.", 'woocommerce' ),
'https://woocommerce.com/document/digital-downloadable-product-handling#nginx-setting'
),
'options' => array(
'force' => __( 'Force downloads', 'woocommerce' ),
'xsendfile' => __( 'X-Accel-Redirect/X-Sendfile', 'woocommerce' ),
'redirect' => apply_filters( 'woocommerce_redirect_only_method_is_secure', false ) ? __( 'Redirect only', 'woocommerce' ) : __( 'Redirect only (Insecure)', 'woocommerce' ),
),
'autoload' => false,
),
array(
'desc' => __( 'Allow using redirect mode (insecure) as a last resort', 'woocommerce' ),
'id' => 'woocommerce_downloads_redirect_fallback_allowed',
'type' => 'checkbox',
'default' => 'no',
'desc_tip' => sprintf(
/* translators: %1$s is a link to the WooCommerce documentation. */
__( 'If the "Force Downloads" or "X-Accel-Redirect/X-Sendfile" download method is selected but does not work, the system will use the "Redirect" method as a last resort. <a href="%1$s">See this guide</a> for more details.', 'woocommerce' ),
'https://woocommerce.com/document/digital-downloadable-product-handling/'
),
'checkboxgroup' => 'start',
'autoload' => false,
),
array(
'title' => __( 'Access restriction', 'woocommerce' ),
'desc' => __( 'Downloads require login', 'woocommerce' ),
'id' => 'woocommerce_downloads_require_login',
'type' => 'checkbox',
'default' => 'no',
'desc_tip' => __( 'This setting does not apply to guest purchases.', 'woocommerce' ),
'checkboxgroup' => 'start',
'autoload' => false,
),
array(
'desc' => __( 'Grant access to downloadable products after payment', 'woocommerce' ),
'id' => 'woocommerce_downloads_grant_access_after_payment',
'type' => 'checkbox',
'default' => 'yes',
'desc_tip' => __( 'Enable this option to grant access to downloads when orders are "processing", rather than "completed".', 'woocommerce' ),
'checkboxgroup' => 'end',
'autoload' => false,
),
array(
'title' => __( 'Open in browser', 'woocommerce' ),
'desc' => __( 'Open downloadable files in the browser, instead of saving them to the device.', 'woocommerce' ),
'id' => 'woocommerce_downloads_deliver_inline',
'type' => 'checkbox',
'default' => false,
'desc_tip' => __( 'Customers can still save the file to their device, but by default file will be opened instead of being downloaded (does not work with redirects).', 'woocommerce' ),
'autoload' => false,
),
array(
'title' => __( 'Filename', 'woocommerce' ),
'desc' => __( 'Append a unique string to filename for security', 'woocommerce' ),
'id' => 'woocommerce_downloads_add_hash_to_filename',
'type' => 'checkbox',
'default' => 'yes',
'desc_tip' => sprintf(
// translators: Link to WooCommerce Docs.
__( "Not required if your download directory is protected. <a href='%s'>See this guide</a> for more details. Files already uploaded will not be affected.", 'woocommerce' ),
'https://woocommerce.com/document/digital-downloadable-product-handling#unique-string'
),
),
array(
'title' => __( 'Count partial downloads', 'woocommerce' ),
'desc' => __( 'Count downloads even if only part of a file is fetched.', 'woocommerce' ),
'id' => 'woocommerce_downloads_count_partial',
'type' => 'checkbox',
'default' => 'yes',
'desc_tip' => sprintf(
/* Translators: 1: opening link tag 2: closing link tag. */
__( 'Repeat fetches made within a reasonable window of time (by default, 30 minutes) will not be counted twice. This is a generally reasonably way to enforce download limits in relation to ranged requests. %1$sLearn more.%2$s', 'woocommerce' ),
'<a href="https://woocommerce.com/document/digital-downloadable-product-handling/">',
'</a>'
),
),
array(
'type' => 'sectionend',
'id' => 'digital_download_options',
),
);
return apply_filters( 'woocommerce_downloadable_products_settings', $settings );
}
/**
* Save settings and trigger the 'woocommerce_update_options_'.id action.
*/
public function save() {
$this->save_settings_for_current_section();
/*
* Product->Inventory has a setting `Out of stock visibility`.
* Because of this, we need to recount the terms to keep them in-sync.
*/
WC()->call_function( 'wc_recount_all_terms', false );
$this->do_update_options_action();
}
}
return new WC_Settings_Products();

View File

@@ -0,0 +1,465 @@
<?php
/**
* WooCommerce Shipping Settings
*
* @package WooCommerce\Admin
* @version 2.6.0
*/
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Internal\Admin\WCAdminAssets;
defined( 'ABSPATH' ) || exit;
if ( class_exists( 'WC_Settings_Shipping', false ) ) {
return new WC_Settings_Shipping();
}
/**
* WC_Settings_Shipping.
*/
class WC_Settings_Shipping extends WC_Settings_Page {
/**
* Constructor.
*/
public function __construct() {
$this->id = 'shipping';
$this->label = __( 'Shipping', 'woocommerce' );
parent::__construct();
}
/**
* Setting page icon.
*
* @var string
*/
public $icon = 'shipping';
/**
* Add this page to settings.
*
* @param array $pages Current pages.
* @return array|mixed
*/
public function add_settings_page( $pages ) {
return wc_shipping_enabled() ? parent::add_settings_page( $pages ) : $pages;
}
/**
* Get own sections.
*
* @return array
*/
protected function get_own_sections() {
$sections = array(
'' => __( 'Shipping zones', 'woocommerce' ),
'options' => __( 'Shipping settings', 'woocommerce' ),
'classes' => __( 'Classes', 'woocommerce' ),
);
if ( ! $this->wc_is_installing() ) {
// Load shipping methods so we can show any global options they may have.
$shipping_methods = $this->get_shipping_methods();
foreach ( $shipping_methods as $method ) {
if ( ! $method->has_settings() ) {
continue;
}
$title = empty( $method->method_title ) ? ucfirst( $method->id ) : $method->method_title;
$sections[ strtolower( $method->id ) ] = esc_html( $title );
}
}
return $sections;
}
/**
* Is WC_INSTALLING constant defined?
* This method exists to ease unit testing.
*
* @return bool True is the WC_INSTALLING constant is defined.
*/
protected function wc_is_installing() {
return Constants::is_defined( 'WC_INSTALLING' );
}
/**
* Get the currently available shipping methods.
* This method exists to ease unit testing.
*
* @return array Currently available shipping methods.
*/
protected function get_shipping_methods() {
return WC()->shipping()->get_shipping_methods();
}
/**
* Get settings for the options section.
*
* @return array
*/
protected function get_settings_for_options_section() {
$settings =
array(
array(
'title' => __( 'Shipping settings', 'woocommerce' ),
'type' => 'title',
'id' => 'shipping_options',
),
array(
'title' => __( 'Calculations', 'woocommerce' ),
'desc' => __( 'Enable the shipping calculator on the cart page', 'woocommerce' ),
'id' => 'woocommerce_enable_shipping_calc',
'default' => 'yes',
'type' => 'checkbox',
'checkboxgroup' => 'start',
'autoload' => false,
),
array(
'desc' => __( 'Hide shipping costs until an address is entered', 'woocommerce' ),
'id' => 'woocommerce_shipping_cost_requires_address',
'default' => 'no',
'type' => 'checkbox',
'checkboxgroup' => '',
),
array(
'desc' => __( 'Hide shipping rates when free shipping is available', 'woocommerce' ),
'id' => 'woocommerce_shipping_hide_rates_when_free',
'default' => 'no',
'type' => 'checkbox',
'autoload' => false,
'checkboxgroup' => 'end',
),
array(
'title' => __( 'Shipping destination', 'woocommerce' ),
'desc' => __( 'This controls which shipping address is used by default.', 'woocommerce' ),
'id' => 'woocommerce_ship_to_destination',
'default' => 'billing',
'type' => 'radio',
'options' => array(
'shipping' => __( 'Default to customer shipping address', 'woocommerce' ),
'billing' => __( 'Default to customer billing address', 'woocommerce' ),
'billing_only' => __( 'Force shipping to the customer billing address', 'woocommerce' ),
),
'autoload' => false,
'desc_tip' => true,
'show_if_checked' => 'option',
),
array(
'title' => __( 'Debug mode', 'woocommerce' ),
'desc' => __( 'Enable debug mode', 'woocommerce' ),
'desc_tip' => __( 'Enable shipping debug mode to show matching shipping zones and to bypass shipping rate cache.', 'woocommerce' ),
'id' => 'woocommerce_shipping_debug_mode',
'default' => 'no',
'type' => 'checkbox',
),
array(
'type' => 'sectionend',
'id' => 'shipping_options',
),
);
return apply_filters( 'woocommerce_shipping_settings', $settings );
}
/**
* Output the settings.
*/
public function output() {
global $current_section, $hide_save_button;
// Load shipping methods so we can show any global options they may have.
$shipping_methods = $this->get_shipping_methods();
if ( '' === $current_section ) {
$this->output_zones_screen();
} elseif ( 'classes' === $current_section ) {
$hide_save_button = true;
$this->output_shipping_class_screen();
} else {
$is_shipping_method = false;
foreach ( $shipping_methods as $method ) {
if ( in_array( $current_section, array( $method->id, sanitize_title( get_class( $method ) ) ), true ) && $method->has_settings() ) {
$is_shipping_method = true;
$method->admin_options();
}
}
if ( ! $is_shipping_method ) {
parent::output();
}
}
}
/**
* Save settings.
*/
public function save() {
global $current_section;
switch ( $current_section ) {
case 'options':
$this->save_settings_for_current_section();
$this->do_update_options_action();
break;
case 'classes':
$this->do_update_options_action();
break;
case '':
break;
default:
$is_shipping_method = false;
foreach ( $this->get_shipping_methods() as $method_id => $method ) {
if ( in_array( $current_section, array( $method->id, sanitize_title( get_class( $method ) ) ), true ) ) {
$is_shipping_method = true;
$this->do_update_options_action( $method->id );
}
}
if ( ! $is_shipping_method ) {
$this->save_settings_for_current_section();
}
break;
}
// Increments the transient version to invalidate cache.
WC_Cache_Helper::get_transient_version( 'shipping', true );
}
/**
* Handles output of the shipping zones page in admin.
*/
protected function output_zones_screen() {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
global $hide_save_button;
if ( isset( $_REQUEST['zone_id'] ) ) {
$hide_save_button = true;
$this->zone_methods_screen( wc_clean( wp_unslash( $_REQUEST['zone_id'] ) ) );
} elseif ( isset( $_REQUEST['instance_id'] ) ) {
$this->instance_settings_screen( absint( wp_unslash( $_REQUEST['instance_id'] ) ) );
} else {
$hide_save_button = true;
$this->zones_screen();
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended
}
/**
* Get all available regions.
*
* @param int $allowed_countries Zone ID.
* @param int $shipping_continents Zone ID.
*/
protected function get_region_options( $allowed_countries, $shipping_continents ) {
$options = array();
foreach ( $shipping_continents as $continent_code => $continent ) {
$continent_data = array(
'value' => 'continent:' . esc_attr( $continent_code ),
'label' => esc_html( $continent['name'] ),
'children' => array(),
);
$countries = array_intersect( array_keys( $allowed_countries ), $continent['countries'] );
foreach ( $countries as $country_code ) {
$country_data = array(
'value' => 'country:' . esc_attr( $country_code ),
'label' => esc_html( $allowed_countries[ $country_code ] ),
'children' => array(),
);
$states = WC()->countries->get_states( $country_code );
if ( $states ) {
foreach ( $states as $state_code => $state_name ) {
$country_data['children'][] = array(
'value' => 'state:' . esc_attr( $country_code . ':' . $state_code ),
'label' => esc_html( $state_name . ', ' . $allowed_countries[ $country_code ] ),
);
}
}
$continent_data['children'][] = $country_data;
}
$options[] = $continent_data;
}
return $options;
}
/**
* Show method for a zone
*
* @param int $zone_id Zone ID.
*/
protected function zone_methods_screen( $zone_id ) {
if ( 'new' === $zone_id ) {
$zone = new WC_Shipping_Zone();
} else {
$zone = WC_Shipping_Zones::get_zone( absint( $zone_id ) );
}
if ( ! $zone ) {
wp_die( esc_html__( 'Zone does not exist!', 'woocommerce' ) );
}
$allowed_countries = WC()->countries->get_shipping_countries();
$shipping_continents = WC()->countries->get_shipping_continents();
// Prepare locations.
$locations = array();
$postcodes = array();
foreach ( $zone->get_zone_locations() as $location ) {
if ( 'postcode' === $location->type ) {
$postcodes[] = $location->code;
} else {
$locations[] = $location->type . ':' . $location->code;
}
}
$localized_object = array(
'methods' => $zone->get_shipping_methods( false, 'json' ),
'zone_name' => $zone->get_zone_name(),
'zone_id' => $zone->get_id(),
'locations' => $locations,
'wc_shipping_zones_nonce' => wp_create_nonce( 'wc_shipping_zones_nonce' ),
'strings' => array(
'unload_confirmation_msg' => __( 'Your changed data will be lost if you leave this page without saving.', 'woocommerce' ),
'save_changes_prompt' => __( 'Do you wish to save your changes first? Your changed data will be discarded if you choose to cancel.', 'woocommerce' ),
'save_failed' => __( 'Your changes were not saved. Please retry.', 'woocommerce' ),
'add_method_failed' => __( 'Shipping method could not be added. Please retry.', 'woocommerce' ),
'remove_method_failed' => __( 'Shipping method could not be removed. Please retry.', 'woocommerce' ),
'yes' => __( 'Yes', 'woocommerce' ),
'no' => __( 'No', 'woocommerce' ),
'default_zone_name' => __( 'Zone', 'woocommerce' ),
'delete_shipping_method_confirmation' => __( 'Are you sure you want to delete this shipping method?', 'woocommerce' ),
'invalid_number_format' => __( 'Please enter a valid number.', 'woocommerce' ),
),
);
if ( 0 !== $zone->get_id() ) {
WCAdminAssets::register_script( 'wp-admin-scripts', 'shipping-settings-region-picker', true, array( 'wc-shipping-zone-methods' ) );
$localized_object['region_options'] = $this->get_region_options( $allowed_countries, $shipping_continents );
}
wp_localize_script(
'wc-shipping-zone-methods',
'shippingZoneMethodsLocalizeScript',
$localized_object,
);
wp_enqueue_script( 'wc-shipping-zone-methods' );
include_once dirname( __FILE__ ) . '/views/html-admin-page-shipping-zone-methods.php';
}
/**
* Show zones
*/
protected function zones_screen() {
$method_count = wc_get_shipping_method_count( false, true );
wp_localize_script(
'wc-shipping-zones',
'shippingZonesLocalizeScript',
array(
'zones' => WC_Shipping_Zones::get_zones( 'json' ),
'default_zone' => array(
'zone_id' => 0,
'zone_name' => '',
'zone_order' => null,
),
'wc_shipping_zones_nonce' => wp_create_nonce( 'wc_shipping_zones_nonce' ),
'strings' => array(
'unload_confirmation_msg' => __( 'Your changed data will be lost if you leave this page without saving.', 'woocommerce' ),
'delete_confirmation_msg' => __( 'Are you sure you want to delete this zone? This action cannot be undone.', 'woocommerce' ),
'save_failed' => __( 'Your changes were not saved. Please retry.', 'woocommerce' ),
'no_shipping_methods_offered' => __( 'No shipping methods offered to this zone.', 'woocommerce' ),
),
)
);
wp_enqueue_script( 'wc-shipping-zones' );
include_once dirname( __FILE__ ) . '/views/html-admin-page-shipping-zones.php';
}
/**
* Show instance settings
*
* @param int $instance_id Shipping instance ID.
*/
protected function instance_settings_screen( $instance_id ) {
$zone = WC_Shipping_Zones::get_zone_by( 'instance_id', $instance_id );
$shipping_method = WC_Shipping_Zones::get_shipping_method( $instance_id );
if ( ! $shipping_method ) {
wp_die( esc_html__( 'Invalid shipping method!', 'woocommerce' ) );
}
if ( ! $zone ) {
wp_die( esc_html__( 'Zone does not exist!', 'woocommerce' ) );
}
if ( ! $shipping_method->has_settings() ) {
wp_die( esc_html__( 'This shipping method does not have any settings to configure.', 'woocommerce' ) );
}
if ( ! empty( $_POST['save'] ) ) {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
if ( empty( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( wp_unslash( $_REQUEST['_wpnonce'] ), 'woocommerce-settings' ) ) {
echo '<div class="updated error"><p>' . esc_html__( 'Edit failed. Please try again.', 'woocommerce' ) . '</p></div>';
}
$shipping_method->process_admin_options();
$shipping_method->display_errors();
}
include_once dirname( __FILE__ ) . '/views/html-admin-page-shipping-zones-instance.php';
}
/**
* Handles output of the shipping class settings screen.
*/
protected function output_shipping_class_screen() {
$wc_shipping = WC_Shipping::instance();
wp_localize_script(
'wc-shipping-classes',
'shippingClassesLocalizeScript',
array(
'classes' => $wc_shipping->get_shipping_classes(),
'default_shipping_class' => array(
'term_id' => 0,
'name' => '',
'description' => '',
),
'wc_shipping_classes_nonce' => wp_create_nonce( 'wc_shipping_classes_nonce' ),
'strings' => array(
'unload_confirmation_msg' => __( 'Your changed data will be lost if you leave this page without saving.', 'woocommerce' ),
'save_failed' => __( 'Your changes were not saved. Please retry.', 'woocommerce' ),
),
)
);
wp_enqueue_script( 'wc-shipping-classes' );
// Extendable columns to show on the shipping classes screen.
$shipping_class_columns = apply_filters(
'woocommerce_shipping_classes_columns',
array(
'wc-shipping-class-name' => __( 'Shipping class', 'woocommerce' ),
'wc-shipping-class-slug' => __( 'Slug', 'woocommerce' ),
'wc-shipping-class-description' => __( 'Description', 'woocommerce' ),
'wc-shipping-class-count' => __( 'Product count', 'woocommerce' ),
)
);
include_once dirname( __FILE__ ) . '/views/html-admin-page-shipping-classes.php';
}
}
return new WC_Settings_Shipping();

View File

@@ -0,0 +1,52 @@
<?php
/**
* WooCommerce site visibility settings
*
* @package WooCommerce\Admin
*/
defined( 'ABSPATH' ) || exit;
/**
* Settings for API.
*/
if ( class_exists( 'WC_Settings_Site_Visibility', false ) ) {
return new WC_Settings_Site_Visibility();
}
/**
* WC_Settings_Advanced.
*/
class WC_Settings_Site_Visibility extends WC_Settings_Page {
/**
* Constructor.
*/
public function __construct() {
$this->id = 'site-visibility';
$this->label = __( 'Site visibility', 'woocommerce' );
parent::__construct();
}
/**
* Get settings for the default section.
*
* @return array
*/
protected function get_settings_for_default_section() {
$settings =
array(
array(
'id' => 'wc_settings_site_visibility_slotfill',
'type' => 'slotfill_placeholder',
),
);
return $settings;
}
}
return new WC_Settings_Site_Visibility();

View File

@@ -0,0 +1,475 @@
<?php
/**
* WooCommerce Tax Settings
*
* @package WooCommerce\Admin
* @version 2.1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( class_exists( 'WC_Settings_Tax', false ) ) {
return new WC_Settings_Tax();
}
/**
* WC_Settings_Tax.
*/
class WC_Settings_Tax extends WC_Settings_Page {
/**
* Constructor.
*/
public function __construct() {
$this->id = 'tax';
$this->label = __( 'Tax', 'woocommerce' );
add_filter( 'woocommerce_settings_tabs_array', array( $this, 'add_settings_page' ), 20 );
add_action( 'woocommerce_admin_field_conflict_error', array( $this, 'conflict_error' ) );
if ( wc_tax_enabled() ) {
add_action( 'woocommerce_sections_' . $this->id, array( $this, 'output_sections' ) );
add_action( 'woocommerce_settings_' . $this->id, array( $this, 'output' ) );
add_action( 'woocommerce_settings_save_' . $this->id, array( $this, 'save' ) );
add_action( 'admin_notices', array( $this, 'tax_configuration_validation_notice' ) );
}
}
/**
* Setting page icon.
*
* @var string
*/
public $icon = 'percent';
/**
* Creates the React mount point for the embedded banner.
*/
public function conflict_error() {
?>
<tr valign="top">
<th scope="row" class="titledesc woocommerce_admin_tax_settings_slotfill_th">
</th>
<td class="forminp forminp-text woocommerce_admin_tax_settings_slotfill_td">
<div id="wc_tax_settings_slotfill"> </div>
</td>
</tr>
<?php
}
/**
* Add this page to settings.
*
* @param array $pages Existing pages.
* @return array|mixed
*/
public function add_settings_page( $pages ) {
if ( wc_tax_enabled() ) {
return parent::add_settings_page( $pages );
} else {
return $pages;
}
}
/**
* Get own sections.
*
* @return array
*/
protected function get_own_sections() {
$sections = array(
'' => __( 'Tax options', 'woocommerce' ),
'standard' => __( 'Standard rates', 'woocommerce' ),
);
// Get tax classes and display as links.
$tax_classes = WC_Tax::get_tax_classes();
foreach ( $tax_classes as $class ) {
/* translators: $s tax rate section name */
$sections[ sanitize_title( $class ) ] = sprintf( __( '%s rates', 'woocommerce' ), $class );
}
return $sections;
}
/**
* Get settings array.
*
* @return array
*/
public function get_settings_for_default_section() {
return include __DIR__ . '/views/settings-tax.php';
}
/**
* Output the settings.
*/
public function output() {
global $current_section;
$tax_classes = WC_Tax::get_tax_class_slugs();
if ( 'standard' === $current_section || in_array( $current_section, array_filter( $tax_classes ), true ) ) {
$this->output_tax_rates();
} else {
parent::output();
}
}
/**
* Save settings.
*/
public function save() {
// phpcs:disable WordPress.Security.NonceVerification.Missing
global $current_section;
if ( ! $current_section ) {
$this->save_settings_for_current_section();
if ( isset( $_POST['woocommerce_tax_classes'] ) ) {
$this->save_tax_classes( wp_unslash( $_POST['woocommerce_tax_classes'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
}
} elseif ( ! empty( $_POST['tax_rate_country'] ) ) {
$this->save_tax_rates();
} else {
$this->save_settings_for_current_section();
}
$this->do_update_options_action();
// Invalidate caches.
WC_Cache_Helper::invalidate_cache_group( 'taxes' );
WC_Cache_Helper::get_transient_version( 'shipping', true );
// phpcs:enable WordPress.Security.NonceVerification.Missing
}
/**
* Saves tax classes defined in the textarea to the tax class table instead of an option.
*
* @param string $raw_tax_classes Posted value.
* @return null
*/
public function save_tax_classes( $raw_tax_classes ) {
$tax_classes = array_filter( array_map( 'trim', explode( "\n", $raw_tax_classes ) ) );
$existing_tax_classes = WC_Tax::get_tax_classes();
$removed = array_diff( $existing_tax_classes, $tax_classes );
$added = array_diff( $tax_classes, $existing_tax_classes );
foreach ( $removed as $name ) {
WC_Tax::delete_tax_class_by( 'name', $name );
}
foreach ( $added as $name ) {
$tax_class = WC_Tax::create_tax_class( $name );
// Display any error that could be triggered while creating tax classes.
if ( is_wp_error( $tax_class ) ) {
WC_Admin_Settings::add_error(
sprintf(
/* translators: 1: tax class name 2: error message */
esc_html__( 'Additional tax class "%1$s" couldn\'t be saved. %2$s.', 'woocommerce' ),
esc_html( $name ),
$tax_class->get_error_message()
)
);
}
}
return null;
}
/**
* Output tax rate tables.
*/
public function output_tax_rates() {
global $current_section;
$current_class = self::get_current_tax_class();
$countries = array();
foreach ( WC()->countries->get_allowed_countries() as $value => $label ) {
$countries[] = array(
'value' => $value,
'label' => esc_js( html_entity_decode( $label ) ),
);
}
$states = array();
foreach ( WC()->countries->get_allowed_country_states() as $label ) {
foreach ( $label as $code => $state ) {
$states[] = array(
'value' => $code,
'label' => esc_js( html_entity_decode( $state ) ),
);
}
}
$base_url = admin_url(
add_query_arg(
array(
'page' => 'wc-settings',
'tab' => 'tax',
'section' => $current_section,
),
'admin.php'
)
);
// Localize and enqueue our js.
wp_localize_script(
'wc-settings-tax',
'htmlSettingsTaxLocalizeScript',
array(
'current_class' => $current_class,
'wc_tax_nonce' => wp_create_nonce( 'wc_tax_nonce-class:' . $current_class ),
'base_url' => $base_url,
'rates' => array_values( WC_Tax::get_rates_for_tax_class( $current_class ) ),
'page' => ! empty( $_GET['p'] ) ? absint( $_GET['p'] ) : 1, // phpcs:ignore WordPress.Security.NonceVerification.Recommended
'limit' => 100,
'countries' => $countries,
'states' => $states,
'default_rate' => array(
'tax_rate_id' => 0,
'tax_rate_country' => '',
'tax_rate_state' => '',
'tax_rate' => '',
'tax_rate_name' => '',
'tax_rate_priority' => 1,
'tax_rate_compound' => 0,
'tax_rate_shipping' => 1,
'tax_rate_order' => null,
'tax_rate_class' => $current_class,
),
'strings' => array(
'no_rows_selected' => __( 'No row(s) selected', 'woocommerce' ),
'unload_confirmation_msg' => __( 'Your changed data will be lost if you leave this page without saving.', 'woocommerce' ),
'csv_data_cols' => array(
__( 'Country code', 'woocommerce' ),
__( 'State code', 'woocommerce' ),
__( 'Postcode / ZIP', 'woocommerce' ),
__( 'City', 'woocommerce' ),
__( 'Rate %', 'woocommerce' ),
__( 'Tax name', 'woocommerce' ),
__( 'Priority', 'woocommerce' ),
__( 'Compound', 'woocommerce' ),
__( 'Shipping', 'woocommerce' ),
__( 'Tax class', 'woocommerce' ),
),
),
)
);
wp_enqueue_script( 'wc-settings-tax' );
include __DIR__ . '/views/html-settings-tax.php';
}
/**
* Get tax class being edited.
*
* @return string
*/
private static function get_current_tax_class() {
global $current_section;
$tax_classes = WC_Tax::get_tax_classes();
$current_class = '';
foreach ( $tax_classes as $class ) {
if ( sanitize_title( $class ) === $current_section ) {
$current_class = $class;
}
}
return $current_class;
}
/**
* Get a posted tax rate.
*
* @param string $key Key of tax rate in the post data array.
* @param int $order Position/order of rate.
* @param string $class Tax class for rate.
* @return array
*/
private function get_posted_tax_rate( $key, $order, $class ) {
// phpcs:disable WordPress.Security.NonceVerification.Missing -- this is called from 'save_tax_rates' only, where nonce is already verified.
$tax_rate = array();
$tax_rate_keys = array(
'tax_rate_country',
'tax_rate_state',
'tax_rate',
'tax_rate_name',
'tax_rate_priority',
);
// phpcs:disable WordPress.Security.NonceVerification.Missing
foreach ( $tax_rate_keys as $tax_rate_key ) {
if ( isset( $_POST[ $tax_rate_key ], $_POST[ $tax_rate_key ][ $key ] ) ) {
$tax_rate[ $tax_rate_key ] = wc_clean( wp_unslash( $_POST[ $tax_rate_key ][ $key ] ) );
}
}
$tax_rate['tax_rate_compound'] = isset( $_POST['tax_rate_compound'][ $key ] ) ? 1 : 0;
$tax_rate['tax_rate_shipping'] = isset( $_POST['tax_rate_shipping'][ $key ] ) ? 1 : 0;
$tax_rate['tax_rate_order'] = $order;
$tax_rate['tax_rate_class'] = $class;
// phpcs:enable WordPress.Security.NonceVerification.Missing
return $tax_rate;
// phpcs:enable WordPress.Security.NonceVerification.Missing
}
/**
* Save tax rates.
*/
public function save_tax_rates() {
// phpcs:disable WordPress.Security.NonceVerification.Missing -- this is called via "do_action('woocommerce_settings_save_'...") in base class, where nonce is verified first.
global $wpdb;
$current_class = sanitize_title( self::get_current_tax_class() );
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.NonceVerification.Missing
$posted_countries = wc_clean( wp_unslash( $_POST['tax_rate_country'] ) );
// get the tax rate id of the first submitted row.
$first_tax_rate_id = key( $posted_countries );
// get the order position of the first tax rate id.
$tax_rate_order = absint( $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate_order FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %s", $first_tax_rate_id ) ) );
$index = isset( $tax_rate_order ) ? $tax_rate_order : 0;
// Loop posted fields.
// phpcs:disable WordPress.Security.NonceVerification.Missing
foreach ( $posted_countries as $key => $value ) {
$mode = ( 0 === strpos( $key, 'new-' ) ) ? 'insert' : 'update';
$tax_rate = $this->get_posted_tax_rate( $key, $index ++, $current_class );
if ( 'insert' === $mode ) {
$tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
} elseif ( isset( $_POST['remove_tax_rate'][ $key ] ) && 1 === absint( $_POST['remove_tax_rate'][ $key ] ) ) {
$tax_rate_id = absint( $key );
WC_Tax::_delete_tax_rate( $tax_rate_id );
continue;
} else {
$tax_rate_id = absint( $key );
WC_Tax::_update_tax_rate( $tax_rate_id, $tax_rate );
}
if ( isset( $_POST['tax_rate_postcode'][ $key ] ) ) {
WC_Tax::_update_tax_rate_postcodes( $tax_rate_id, wc_clean( wp_unslash( $_POST['tax_rate_postcode'][ $key ] ) ) );
}
if ( isset( $_POST['tax_rate_city'][ $key ] ) ) {
WC_Tax::_update_tax_rate_cities( $tax_rate_id, wc_clean( wp_unslash( $_POST['tax_rate_city'][ $key ] ) ) );
}
}
// phpcs:enable WordPress.Security.NonceVerification.Missing
}
/**
* Display admin notice when tax-inclusive pricing is enabled without a base tax rate.
*
* @since 10.5.0
* @internal This method is public only because it is used as a hook callback.
*
* @return void
*/
public function tax_configuration_validation_notice(): void {
// Only show on WooCommerce settings pages.
$screen = get_current_screen();
if ( ! $screen || 'woocommerce_page_wc-settings' !== $screen->id ) {
return;
}
// Don't show if taxes are disabled.
if ( ! wc_tax_enabled() ) {
return;
}
// Check if prices are entered with tax.
if ( 'yes' !== get_option( 'woocommerce_prices_include_tax' ) ) {
return;
}
/**
* Filters if taxes should be removed from locations outside the store base location.
*
* The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing
* with out of base locations. e.g. If a product costs 10 including tax, all users will pay 10
* regardless of location and taxes.
*
* @since 2.4.7
*
* @param bool $adjust_non_base_location_prices True by default.
*/
if ( ! apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) {
return;
}
// Check if base location has tax rates configured.
$base_location = wc_get_base_location();
if ( empty( $base_location['country'] ) ) {
return;
}
$has_base_rate = $this->has_standard_tax_rate_for_country( $base_location['country'] );
// If no base rates exist, show warning.
if ( ! $has_base_rate ) {
/**
* Filter whether to show the tax configuration incomplete notice.
*
* @since 10.5.0
*
* @param bool $show_notice Whether to show the notice. Default true.
*/
if ( ! apply_filters( 'woocommerce_show_tax_configuration_notice', true ) ) {
return;
}
?>
<div class="notice notice-warning">
<p>
<strong><?php esc_html_e( 'Tax configuration incomplete', 'woocommerce' ); ?></strong>
</p>
<p>
<?php
echo wp_kses_post(
sprintf(
/* translators: 1: country code, 2: opening link tag, 3: closing link tag */
__( 'You have enabled "Prices entered with tax" but have not configured a standard tax rate for your base location (%1$s). Please %2$sconfigure standard tax rates%3$s to ensure accurate tax calculations.', 'woocommerce' ),
esc_html( $base_location['country'] ),
'<a href="' . esc_url( admin_url( 'admin.php?page=wc-settings&tab=tax&section=standard' ) ) . '">',
'</a>'
)
);
?>
</p>
</div>
<?php
}
}
/**
* Check if a standard tax rate exists for a given country.
*
* @since 10.5.0
*
* @param string $country Country code.
* @return bool True if at least one standard tax rate exists for the country.
*/
private function has_standard_tax_rate_for_country( $country ) {
global $wpdb;
$count = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->prefix}woocommerce_tax_rates WHERE (tax_rate_country = %s OR tax_rate_country = '') AND tax_rate_class = ''",
$country
)
);
return $count > 0;
}
}
return new WC_Settings_Tax();

View File

@@ -0,0 +1,11 @@
<?php // @codingStandardsIgnoreFile.
/**
* Settings class file.
*
* @deprecated 3.4.0 Replaced with class-wc-settings-advanced.php.
* @todo remove in 4.0.
*/
defined( 'ABSPATH' ) || exit;
return include __DIR__ . '/class-wc-settings-advanced.php';

View File

@@ -0,0 +1,166 @@
<?php
/**
* Shipping classes admin
*
* @package WooCommerce\Admin\Shipping
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<h2 class="wc-shipping-zones-heading">
<span><?php esc_html_e( 'Shipping classes', 'woocommerce' ); ?></span>
<a class="page-title-action wc-shipping-class-add-new" href="#"><?php esc_html_e( 'Add shipping class', 'woocommerce' ); ?></a>
</h2>
<p class="wc-shipping-zone-help-text">
<?php esc_html_e( 'Use shipping classes to customize the shipping rates for different groups of products, such as heavy items that require higher postage fees.', 'woocommerce' ); ?> <a target="_blank" href="https://woocommerce.com/document/product-shipping-classes/"><?php esc_html_e( 'Learn more', 'woocommerce' ); ?></a>
</p>
<table class="wc-shipping-classes widefat">
<thead>
<tr>
<?php foreach ( $shipping_class_columns as $class => $heading ) : ?>
<th class="<?php echo esc_attr( $class ); ?>"><?php echo esc_html( $heading ); ?></th>
<?php endforeach; ?>
<th />
</tr>
</thead>
<tbody class="wc-shipping-class-rows wc-shipping-tables-tbody"></tbody>
</table>
<script type="text/html" id="tmpl-wc-shipping-class-row-blank">
<tr>
<td class="wc-shipping-classes-blank-state" colspan="<?php echo absint( count( $shipping_class_columns ) + 1 ); ?>"><p><?php esc_html_e( 'No shipping classes have been created.', 'woocommerce' ); ?></p></td>
</tr>
</script>
<!-- 1. Placeholder becomes the "label" in view class div -->
<!-- 1. Add labelFor or some kind of attribute for semantic HTML-->
<script type="text/html" id="tmpl-wc-shipping-class-configure">
<div class="wc-backbone-modal wc-shipping-class-modal">
<div class="wc-backbone-modal-content" data-id="{{ data.term_id }}">
<section class="wc-backbone-modal-main" role="main">
<header class="wc-backbone-modal-header">
<h1><?php esc_html_e( 'Add shipping class', 'woocommerce' ); ?></h1>
<button class="modal-close modal-close-link dashicons dashicons-no-alt">
<span class="screen-reader-text"><?php esc_html_e( 'Close modal panel', 'woocommerce' ); ?></span>
</button>
</header>
<article>
<form action="" method="post">
<input type="hidden" name="term_id" value="{{{ data.term_id }}}" />
<?php
foreach ( $shipping_class_columns as $class => $heading ) {
echo '<div class="wc-shipping-class-modal-input ' . esc_attr( $class ) . '">';
switch ( $class ) {
case 'wc-shipping-class-name':
?>
<div class="view">
<?php echo esc_html( $heading ); ?> *
</div>
<div class="edit">
<input type="text" name="name" data-attribute="name" value="{{ data.name }}" placeholder="<?php esc_attr_e( 'e.g. Heavy', 'woocommerce' ); ?>" />
</div>
<div class="wc-shipping-class-modal-help-text"><?php esc_html_e( 'Give your shipping class a name for easy identification', 'woocommerce' ); ?></div>
<?php
break;
case 'wc-shipping-class-slug':
?>
<div class="view">
<?php echo esc_html( $heading ); ?>
</div>
<div class="edit">
<input type="text" name="slug" data-attribute="slug" value="{{ data.slug }}" placeholder="<?php esc_attr_e( 'e.g. heavy-packages', 'woocommerce' ); ?>" />
</div>
<div class="wc-shipping-class-modal-help-text"><?php esc_html_e( 'Slug (unique identifier) can be left blank and auto-generated, or you can enter one', 'woocommerce' ); ?></div>
<?php
break;
case 'wc-shipping-class-description':
?>
<div class="view">
<?php echo esc_html( $heading ); ?> *
</div>
<div class="edit">
<input type="text" name="description" data-attribute="description" value="{{ data.description }}" placeholder="<?php esc_attr_e( 'e.g. For heavy items requiring higher postage', 'woocommerce' ); ?>" />
</div>
<?php
break;
case 'wc-shipping-class-count':
break;
default:
?>
<div class="view wc-shipping-class-hide-sibling-view">
<?php echo esc_html( $heading ); ?>
</div>
<?php
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
do_action( 'woocommerce_shipping_classes_column_' . $class );
break;
}
echo '</div>';
}
?>
</form>
</article>
<footer>
<div class="inner">
<button id="btn-ok" disabled class="button button-primary button-large disabled">
<div class="wc-backbone-modal-action-{{ data.action === 'create' ? 'active' : 'inactive' }}"><?php esc_html_e( 'Create', 'woocommerce' ); ?></div>
<div class="wc-backbone-modal-action-{{ data.action === 'edit' ? 'active' : 'inactive' }}"><?php esc_html_e( 'Save', 'woocommerce' ); ?></div>
</button>
</div>
</footer>
</section>
</div>
</div>
<div class="wc-backbone-modal-backdrop modal-close"></div>
</script>
<script type="text/html" id="tmpl-wc-shipping-class-row">
<tr data-id="{{ data.term_id }}">
<?php
foreach ( $shipping_class_columns as $class => $heading ) {
echo '<td class="' . esc_attr( $class ) . '">';
switch ( $class ) {
case 'wc-shipping-class-name':
?>
<div class="view">
{{ data.name }}
</div>
<?php
break;
case 'wc-shipping-class-slug':
?>
<div class="view">{{ data.slug }}</div>
<?php
break;
case 'wc-shipping-class-description':
?>
<div class="view">{{ data.description }}</div>
<?php
break;
case 'wc-shipping-class-count':
?>
<a href="<?php echo esc_url( admin_url( 'edit.php?post_type=product&product_shipping_class=' ) ); ?>{{data.slug}}">{{ data.count }}</a>
<?php
break;
default:
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
do_action( 'woocommerce_shipping_classes_column_' . $class );
break;
}
echo '</td>';
}
?>
<td class="wc-shipping-zone-actions">
<div>
<a class="wc-shipping-class-edit wc-shipping-zone-action-edit" href="#"><?php esc_html_e( 'Edit', 'woocommerce' ); ?></a> | <a href="#" class="wc-shipping-class-delete wc-shipping-zone-actions"><?php esc_html_e( 'Delete', 'woocommerce' ); ?></a>
</div>
</td>
</tr>
</script>

View File

@@ -0,0 +1,278 @@
<?php
/**
* Shipping zone admin
*
* @package WooCommerce\Admin\Shipping
*/
use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;
use Automattic\WooCommerce\Blocks\Shipping\ShippingController;
use Automattic\WooCommerce\StoreApi\Utilities\LocalPickupUtils;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<?php wc_back_header( $zone->get_zone_name() ? $zone->get_zone_name() : __( 'Add zone', 'woocommerce' ), __( 'Return to shipping', 'woocommerce' ), admin_url( 'admin.php?page=wc-settings&tab=shipping' ) ); ?>
<?php
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
do_action( 'woocommerce_shipping_zone_before_methods_table', $zone );
?>
<table class="form-table wc-shipping-zone-settings">
<tbody>
<?php if ( 0 !== $zone->get_id() ) : ?>
<tr valign="top" class="">
<th scope="row" class="titledesc">
<label for="zone_name">
<?php esc_html_e( 'Zone name', 'woocommerce' ); ?>
</label>
<p class="wc-shipping-zone-help-text">
<?php esc_html_e( 'Give your zone a name! E.g. Local, or Worldwide.', 'woocommerce' ); ?>
</p>
</th>
<td class="forminp">
<input type="text" data-attribute="zone_name" name="zone_name" id="zone_name" value="<?php echo esc_attr( $zone->get_zone_name( 'edit' ) ); ?>" placeholder="<?php esc_attr_e( 'Zone name', 'woocommerce' ); ?>">
</td>
</tr>
<tr valign="top" class="">
<th scope="row" class="titledesc">
<label for="zone_locations">
<?php esc_html_e( 'Zone regions', 'woocommerce' ); ?>
</label>
<p class="wc-shipping-zone-help-text">
<?php esc_html_e( 'List the regions you\'d like to include in your shipping zone. Customers will be matched against these regions.', 'woocommerce' ); ?>
</p>
</th>
<td>
<div>
<div id="wc-shipping-zone-region-picker-root"/>
</div>
<?php if ( empty( $postcodes ) ) : ?>
<a class="wc-shipping-zone-postcodes-toggle" href="#"><?php esc_html_e( 'Limit to specific ZIP/postcodes', 'woocommerce' ); ?></a>
<?php endif; ?>
<div class="wc-shipping-zone-postcodes">
<textarea name="zone_postcodes" data-attribute="zone_postcodes" id="zone_postcodes" placeholder="<?php esc_attr_e( 'List 1 postcode per line', 'woocommerce' ); ?>" class="input-text large-text" cols="25" rows="5"><?php echo esc_textarea( implode( "\n", $postcodes ) ); ?></textarea>
<?php /* translators: WooCommerce link to setting up shipping zones */ ?>
<span class="description"><?php printf( __( 'Postcodes containing wildcards (e.g. CB23*) or fully numeric ranges (e.g. <code>90210...99000</code>) are also supported. Please see the shipping zones <a href="%s" target="_blank">documentation</a> for more information.', 'woocommerce' ), 'https://woocommerce.com/document/setting-up-shipping-zones/#section-3' ); ?></span><?php // @codingStandardsIgnoreLine. ?>
</div>
</td>
</tr>
<?php endif; ?>
<tr valign="top" class="">
<th scope="row" class="titledesc">
<label>
<?php esc_html_e( 'Shipping methods', 'woocommerce' ); ?>
</label>
<p class="wc-shipping-zone-help-text">
<?php esc_html_e( 'Add the shipping methods you\'d like to make available to customers in this zone.', 'woocommerce' ); ?>
</p>
</th>
<td class="">
<table class="wc-shipping-zone-methods widefat">
<thead>
<tr>
<th class="wc-shipping-zone-method-sort"></th>
<th class="wc-shipping-zone-method-title"><?php esc_html_e( 'Title', 'woocommerce' ); ?></th>
<th class="wc-shipping-zone-method-enabled"><?php esc_html_e( 'Enabled', 'woocommerce' ); ?></th>
<th class="wc-shipping-zone-method-description"><?php esc_html_e( 'Description', 'woocommerce' ); ?></th>
<th></th>
</tr>
</thead>
<tbody class="wc-shipping-zone-method-rows wc-shipping-tables-tbody"></tbody>
</table>
</td>
</tr>
<tr>
<th scope="row"></th>
<td>
<button type="submit" class="wc-shipping-zone-add-method components-button is-primary" value="<?php esc_attr_e( 'Add shipping method', 'woocommerce' ); ?>"><?php esc_html_e( 'Add shipping method', 'woocommerce' ); ?></button>
</td>
</tr>
</tbody>
</table>
<?php
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
do_action( 'woocommerce_shipping_zone_after_methods_table', $zone );
?>
<p class="submit">
<button type="submit" name="submit" id="submit" class="button-primary button-large wc-shipping-zone-method-save components-button is-primary" value="<?php esc_attr_e( 'Save changes', 'woocommerce' ); ?>" disabled><?php esc_html_e( 'Save changes', 'woocommerce' ); ?></button>
</p>
<script type="text/html" id="tmpl-wc-shipping-zone-method-row-blank">
<tr>
<td class="wc-shipping-zone-method-blank-state" colspan="5">
<p><?php esc_html_e( 'You can add multiple shipping methods within this zone. Only customers within the zone will see them.', 'woocommerce' ); ?></p>
</td>
</tr>
</script>
<script type="text/html" id="tmpl-wc-shipping-zone-method-row">
<tr data-id="{{ data.instance_id }}" data-enabled="{{ data.enabled }}">
<td width="1%" class="wc-shipping-zone-method-sort"></td>
<td class="wc-shipping-zone-method-title">
{{{ data.title }}}
</td>
<td width="1%" class="wc-shipping-zone-method-enabled"><a href="#">{{{ data.enabled_icon }}}</a></td>
<td class="wc-shipping-zone-method-description">
{{{ data.method_description }}}
</td>
<td class="wc-shipping-zone-actions">
<div>
<a class="wc-shipping-zone-method-settings wc-shipping-zone-action-edit" href="admin.php?page=wc-settings&amp;tab=shipping&amp;instance_id={{ data.instance_id }}"><?php esc_html_e( 'Edit', 'woocommerce' ); ?></a> | <a href="#" class="wc-shipping-zone-method-delete wc-shipping-zone-actions"><?php esc_html_e( 'Delete', 'woocommerce' ); ?></a>
</div>
</td>
</tr>
</script>
<script type="text/template" id="tmpl-wc-modal-shipping-method-settings">
<div class="wc-backbone-modal wc-backbone-modal-shipping-method-settings">
<div class="wc-backbone-modal-content">
<section class="wc-backbone-modal-main" role="main">
<header class="wc-backbone-modal-header">
<h1>
<?php
printf(
/* translators: %s: shipping method title */
esc_html__( 'Set up %s', 'woocommerce' ),
'{{{ data.method.method_title.toLowerCase() }}}'
);
?>
</h1>
<button class="modal-close modal-close-link dashicons dashicons-no-alt">
<span class="screen-reader-text"><?php esc_html_e( 'Close modal panel', 'woocommerce' ); ?></span>
</button>
</header>
<article class="wc-modal-shipping-method-settings" data-id="{{{ data.instance_id }}}" data-status="{{{ data.status }}}" data-shipping-classes-count="<?php echo count( WC()->shipping()->get_shipping_classes() ); ?>">
<form action="" method="post">
{{{ data.method.settings_html }}}
<input type="hidden" name="instance_id" value="{{{ data.instance_id }}}" />
</form>
<a class="wc-shipping-method-add-class-costs" style="display:none;" target="_blank" href="<?php echo esc_url( admin_url( 'admin.php?page=wc-settings&tab=shipping&section=classes' ) ); ?>"><?php esc_html_e( 'Add shipping class costs', 'woocommerce' ); ?></a>
</article>
<footer>
<div class="inner">
<div>
<button id="btn-back" class="button button-large wc-backbone-modal-back-{{ data.status === 'new' ? 'active' : 'inactive' }}"><?php esc_html_e( 'Back', 'woocommerce' ); ?></button>
<button id="btn-ok" data-status='{{ data.status }}' class="button button-primary button-large">
<div class="wc-backbone-modal-action-{{ data.status === 'new' ? 'active' : 'inactive' }}"><?php esc_html_e( 'Create and save', 'woocommerce' ); ?></div>
<div class="wc-backbone-modal-action-{{ data.status === 'existing' ? 'active' : 'inactive' }}"><?php esc_html_e( 'Save', 'woocommerce' ); ?></div>
</button>
</div>
<div class="wc-shipping-zone-method-modal-info wc-shipping-zone-method-modal-info-{{ data.status === 'existing' ? 'inactive' : 'active' }}"><?php esc_html_e( 'STEP 2 OF 2', 'woocommerce' ); ?></div>
</div>
</footer>
</section>
</div>
</div>
<div class="wc-backbone-modal-backdrop modal-close"></div>
</script>
<script type="text/template" id="tmpl-wc-modal-add-shipping-method">
<div class="wc-backbone-modal wc-backbone-modal-add-shipping-method">
<div class="wc-backbone-modal-content">
<section class="wc-backbone-modal-main" role="main">
<header class="wc-backbone-modal-header">
<h1><?php esc_html_e( 'Create shipping method', 'woocommerce' ); ?></h1>
<button class="modal-close modal-close-link dashicons dashicons-no-alt">
<span class="screen-reader-text"><?php esc_html_e( 'Close modal panel', 'woocommerce' ); ?></span>
</button>
</header>
<article>
<form action="" method="post">
<fieldset class="wc-shipping-zone-method-selector">
<legend class="screen-reader-text"><?php esc_html_e( 'Choose the shipping method you wish to add. Only shipping methods which support zones are listed.', 'woocommerce' ); ?></legend>
<?php
$methods = WC()->shipping()->load_shipping_methods();
$methods_placed_in_order = array();
$first_methods_ids = array( 'free_shipping', 'flat_rate', 'local_pickup' );
foreach ( $first_methods_ids as $first_method_id ) {
foreach ( $methods as $key => $obj ) {
if ( $obj->id === $first_method_id ) {
$methods_placed_in_order[] = $obj;
unset( $methods[ $key ] );
break;
}
}
}
$methods_placed_in_order = array_merge( $methods_placed_in_order, array_values( $methods ) );
foreach ( $methods_placed_in_order as $method ) {
if ( CartCheckoutUtils::is_checkout_block_default() && ! ShippingController::is_legacy_local_pickup_active() && 'local_pickup' === $method->id ) {
continue;
}
if ( ! $method->supports( 'shipping-zones' ) ) {
continue;
}
echo '<div class="wc-shipping-zone-method-input"><input type="radio" value="' . esc_attr( $method->id ) . '" id="' . esc_attr( $method->id ) . '" name="add_method_id"/><label for="' . esc_attr( $method->id ) . '">' . esc_html( $method->get_method_title() ) . '<span class="dashicons dashicons-yes"></span></label></div>';
}
echo '<div class="wc-shipping-zone-method-input-help-text-container">';
foreach ( $methods_placed_in_order as $method ) {
if ( ! $method->supports( 'shipping-zones' ) ) {
continue;
}
echo '<div id=' . esc_attr( $method->id ) . '-description class="wc-shipping-zone-method-input-help-text"><span>' . wp_kses_post( wpautop( $method->get_method_description() ) ) . '</span></div>';
}
if ( CartCheckoutUtils::is_checkout_block_default() ) {
echo '<p class="wc-shipping-legacy-local-pickup-help-text-container">';
if ( ShippingController::is_legacy_local_pickup_active() ) {
printf(
wp_kses(
/* translators: %s: Local pickup settings page URL. */
__( 'Explore a new enhanced delivery method that allows you to easily offer one or more pickup locations to your customers in the <a href="%s">Local pickup settings page</a>.', 'woocommerce' ),
array( 'a' => array( 'href' => array() ) )
),
esc_url( admin_url( 'admin.php?page=wc-settings&tab=shipping&section=pickup_location' ) )
);
} else {
/* translators: %s: Local pickup settings page URL. */
$message = __( 'Local pickup: Set up pickup locations in the <a href="%s">Local pickup settings page</a>.', 'woocommerce' );
if ( LocalPickupUtils::is_local_pickup_enabled() ) {
/* translators: %s: Local pickup settings page URL. */
$message = __( 'Local pickup: Manage existing pickup locations in the <a href="%s">Local pickup settings page</a>.', 'woocommerce' );
}
printf(
wp_kses(
$message,
array( 'a' => array( 'href' => array() ) )
),
esc_url( admin_url( 'admin.php?page=wc-settings&tab=shipping&section=pickup_location' ) )
);
}
echo '</p>';
}
echo '</div>';
?>
</fieldset>
</form>
</article>
<footer>
<div class="inner">
<button id="btn-next" disabled class="button button-primary button-large disabled"><?php esc_html_e( 'Continue', 'woocommerce' ); ?></button>
<div class="wc-shipping-zone-method-modal-info"><?php esc_html_e( 'STEP 1 OF 2', 'woocommerce' ); ?></div>
</div>
</footer>
</section>
</div>
</div>
<div class="wc-backbone-modal-backdrop modal-close"></div>
</script>

View File

@@ -0,0 +1,12 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<h2>
<a href="<?php echo admin_url( 'admin.php?page=wc-settings&tab=shipping' ); ?>"><?php _e( 'Shipping zones', 'woocommerce' ); ?></a> &gt;
<a href="<?php echo admin_url( 'admin.php?page=wc-settings&tab=shipping&zone_id=' . absint( $zone->get_id() ) ); ?>"><?php echo esc_html( $zone->get_zone_name() ); ?></a> &gt;
<?php echo esc_html( $shipping_method->get_method_title() ); ?>
</h2>
<?php $shipping_method->admin_options(); ?>

View File

@@ -0,0 +1,158 @@
<?php
/**
* Shipping zones admin page.
*
* @package WooCommerce\Admin\Shipping
*/
use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<h2 class="wc-shipping-zones-heading">
<span><?php esc_html_e( 'Shipping zones', 'woocommerce' ); ?></span>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-settings&tab=shipping&zone_id=new' ) ); ?>" class="page-title-action"><?php esc_html_e( 'Add zone', 'woocommerce' ); ?></a>
</h2>
<p class="wc-shipping-zone-heading-help-text">
<?php
esc_html_e(
"A shipping zone consists of the region(s) you'd like to ship to and the shipping method(s) offered. A shopper can only be matched to one zone, and we'll use their shipping address to show them the methods available in their area.",
'woocommerce'
);
if ( CartCheckoutUtils::is_checkout_block_default() ) {
echo ' ' . wp_kses_post(
sprintf(
/* translators: %s: URL to local pickup settings */
__(
"To offer local pickup, configure your pickup locations in the <a href='%s'>local pickup settings</a>.",
'woocommerce'
),
esc_url( admin_url( 'admin.php?page=wc-settings&tab=shipping&section=pickup_location' ) )
)
);
}
?>
</p>
<table class="wc-shipping-zones widefat">
<thead>
<tr>
<th class="wc-shipping-zone-sort"><?php echo wc_help_tip( __( 'Drag and drop to re-order your custom zones. This is the order in which they will be matched against the customer address.', 'woocommerce' ) ); ?></th>
<th class="wc-shipping-zone-name"><?php esc_html_e( 'Zone name', 'woocommerce' ); ?></th>
<th class="wc-shipping-zone-region"><?php esc_html_e( 'Region(s)', 'woocommerce' ); ?></th>
<th class="wc-shipping-zone-methods"><?php esc_html_e( 'Shipping method(s)', 'woocommerce' ); ?></th>
<th></th>
</tr>
</thead>
<tbody class="wc-shipping-zone-rows wc-shipping-tables-tbody"></tbody>
<tfoot data-id="0" class="wc-shipping-zone-worldwide wc-shipping-zone-rows-tfoot">
<td width="1%" class="wc-shipping-zone-worldwide"></td>
<td class="wc-shipping-zone-name">
<?php esc_html_e( 'Rest of the world', 'woocommerce' ); ?>
</td>
<td class="wc-shipping-zone-region"><?php esc_html_e( 'An optional zone you can use to set the shipping method(s) available to any regions that have not been listed above.', 'woocommerce' ); ?></td>
<td class="wc-shipping-zone-methods">
<ul>
<?php
$worldwide = new WC_Shipping_Zone( 0 );
$methods = $worldwide->get_shipping_methods();
uasort( $methods, 'wc_shipping_zone_method_order_uasort_comparison' );
if ( ! empty( $methods ) ) {
foreach ( $methods as $method ) {
$class_name = 'yes' === $method->enabled ? 'method_enabled' : 'method_disabled';
echo '<li class="wc-shipping-zone-method ' . esc_attr( $class_name ) . '" data-id="' . esc_attr( $method->instance_id ) . '">' . esc_html( $method->get_title() ) . '</li>';
}
} else {
echo '<li>' . esc_html__( 'No shipping methods offered to this zone.', 'woocommerce' ) . '</li>';
}
?>
</ul>
</td>
<td class="wc-shipping-zone-actions">
<a class="wc-shipping-zone-action-edit" href="admin.php?page=wc-settings&amp;tab=shipping&amp;zone_id=0"><?php esc_html_e( 'Edit', 'woocommerce' ); ?></a>
</td>
</tfoot>
</table>
<script type="text/html" id="tmpl-wc-shipping-zone-row-blank">
<?php if ( 0 === $method_count ) : ?>
<tr>
<td class="wc-shipping-zones-blank-state" colspan="5">
<p class="main"><?php esc_html_e( 'A shipping zone is a geographic region where a certain set of shipping methods and rates apply.', 'woocommerce' ); ?></p>
<p><?php esc_html_e( 'For example:', 'woocommerce' ); ?></p>
<ul>
<li><?php esc_html_e( 'US domestic zone = All US states = Flat rate shipping', 'woocommerce' ); ?>
<li><?php esc_html_e( 'Europe zone = Any country in Europe = Flat rate shipping', 'woocommerce' ); ?>
</ul>
<p><?php esc_html_e( 'Add as many zones as you need &ndash; customers will only see the methods available for their address.', 'woocommerce' ); ?></p>
<a class="button button-primary wc-shipping-zone-add" href="<?php echo esc_url( admin_url( 'admin.php?page=wc-settings&tab=shipping&zone_id=new' ) ); ?>"><?php _e( 'Add shipping zone', 'woocommerce' ); ?></a>
</td>
</tr>
<?php endif; ?>
</script>
<script type="text/html" id="tmpl-wc-shipping-zone-row">
<tr data-id="{{ data.zone_id }}">
<td width="1%" class="wc-shipping-zone-sort"></td>
<td class="wc-shipping-zone-name">
{{ data.zone_name }}
</td>
<td class="wc-shipping-zone-region">
{{ data.formatted_zone_location }}
</td>
<td class="wc-shipping-zone-methods">
<div><ul></ul></div>
</td>
<td class="wc-shipping-zone-actions">
<div>
<a class="wc-shipping-zone-action-edit" href="admin.php?page=wc-settings&amp;tab=shipping&amp;zone_id={{ data.zone_id }}"><?php esc_html_e( 'Edit', 'woocommerce' ); ?></a> | <a href="#" class="wc-shipping-zone-delete wc-shipping-zone-actions"><?php esc_html_e( 'Delete', 'woocommerce' ); ?></a>
</div>
</td>
</tr>
</script>
<script type="text/template" id="tmpl-wc-modal-add-shipping-method">
<div class="wc-backbone-modal">
<div class="wc-backbone-modal-content">
<section class="wc-backbone-modal-main" role="main">
<header class="wc-backbone-modal-header">
<h1><?php esc_html_e( 'Add shipping method', 'woocommerce' ); ?></h1>
<button class="modal-close modal-close-link dashicons dashicons-no-alt">
<span class="screen-reader-text"><?php esc_html_e( 'Close modal panel', 'woocommerce' ); ?></span>
</button>
</header>
<article>
<form action="" method="post">
<div class="wc-shipping-zone-method-selector">
<p><?php esc_html_e( 'Choose the shipping method you wish to add. Only shipping methods which support zones are listed.', 'woocommerce' ); ?></p>
<select name="add_method_id">
<?php
foreach ( WC()->shipping()->load_shipping_methods() as $method ) {
if ( ! $method->supports( 'shipping-zones' ) ) {
continue;
}
echo '<option data-description="' . esc_attr( wp_kses_post( wpautop( $method->get_method_description() ) ) ) . '" value="' . esc_attr( $method->id ) . '">' . esc_html( $method->get_method_title() ) . '</li>';
}
?>
</select>
<input type="hidden" name="zone_id" value="{{{ data.zone_id }}}" />
</div>
</form>
</article>
<footer>
<div class="inner">
<button id="btn-ok" class="button button-primary button-large"><?php _e( 'Add shipping method', 'woocommerce' ); ?></button>
</div>
</footer>
</section>
</div>
</div>
<div class="wc-backbone-modal-backdrop modal-close"></div>
</script>

View File

@@ -0,0 +1,169 @@
<?php
/**
* Admin view: Edit API keys
*
* @package WooCommerce\Admin\Settings
*/
defined( 'ABSPATH' ) || exit;
?>
<div id="key-fields" class="settings-panel">
<h2><?php esc_html_e( 'Key details', 'woocommerce' ); ?></h2>
<div class="inline notice">
<ul class="advice">
<li><?php esc_html_e( 'API keys open up access to potentially sensitive information. Only share them with organizations you trust.', 'woocommerce' ); ?></li>
<li><?php esc_html_e( 'Stick to one key per client: this makes it easier to revoke access in the future for a single client, without causing disruption for others.', 'woocommerce' ); ?></li>
</ul>
</div>
<input type="hidden" id="key_id" value="<?php echo esc_attr( $key_id ); ?>" />
<table id="api-keys-options" class="form-table">
<tbody>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="key_description">
<?php esc_html_e( 'Description', 'woocommerce' ); ?>
<?php echo wc_help_tip( __( 'Friendly name for identifying this key.', 'woocommerce' ) ); ?>
</label>
</th>
<td class="forminp">
<input maxlength="200" id="key_description" type="text" class="input-text regular-input" value="<?php echo esc_attr( $key_data['description'] ); ?>" />
<p class="description">
<?php esc_html_e( 'Add a meaningful description, including a note of the person, company or app you are sharing the key with.', 'woocommerce' ); ?>
</p>
</td>
</tr>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="key_user">
<?php esc_html_e( 'User', 'woocommerce' ); ?>
<?php echo wc_help_tip( __( 'Owner of these keys.', 'woocommerce' ) ); ?>
</label>
</th>
<td class="forminp">
<?php
$current_user_id = get_current_user_id();
$user_id = ! empty( $key_data['user_id'] ) ? absint( $key_data['user_id'] ) : $current_user_id;
$user = get_user_by( 'id', $user_id );
$user_string = sprintf(
/* translators: 1: user display name 2: user ID 3: user email */
esc_html__( '%1$s (#%2$s &ndash; %3$s)', 'woocommerce' ),
$user->display_name,
absint( $user->ID ),
$user->user_email
);
?>
<select class="wc-customer-search" id="key_user" data-placeholder="<?php esc_attr_e( 'Search for a user&hellip;', 'woocommerce' ); ?>" data-allow_clear="true">
<option value="<?php echo esc_attr( $user_id ); ?>" selected="selected"><?php echo htmlspecialchars( wp_kses_post( $user_string ) ); // htmlspecialchars to prevent XSS when rendered by selectWoo. ?></option>
</select>
</td>
</tr>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="key_permissions">
<?php esc_html_e( 'Permissions', 'woocommerce' ); ?>
<?php echo wc_help_tip( __( 'Select the access type of these keys.', 'woocommerce' ) ); ?>
</label>
</th>
<td class="forminp">
<select id="key_permissions" class="wc-enhanced-select">
<?php
$permissions = array(
'read' => __( 'Read', 'woocommerce' ),
'write' => __( 'Write', 'woocommerce' ),
'read_write' => __( 'Read/Write', 'woocommerce' ),
);
foreach ( $permissions as $permission_id => $permission_name ) :
?>
<option value="<?php echo esc_attr( $permission_id ); ?>" <?php selected( $key_data['permissions'], $permission_id, true ); ?>><?php echo esc_html( $permission_name ); ?></option>
<?php endforeach; ?>
</select>
<p class="conditional description" data-depends-on="#key_permissions" data-show-if-equals="write">
<?php esc_html_e( 'Write-only keys do not prevent clients from seeing information about the entities they are updating.', 'woocommerce' ); ?>
</p>
</td>
</tr>
<?php if ( 0 !== $key_id ) : ?>
<tr valign="top">
<th scope="row" class="titledesc">
<?php esc_html_e( 'Consumer key ending in', 'woocommerce' ); ?>
</th>
<td class="forminp">
<code>&hellip;<?php echo esc_html( $key_data['truncated_key'] ); ?></code>
</td>
</tr>
<tr valign="top">
<th scope="row" class="titledesc">
<?php esc_html_e( 'Last access', 'woocommerce' ); ?>
</th>
<td class="forminp">
<span>
<?php
if ( ! empty( $key_data['last_access'] ) ) {
/* translators: 1: last access date 2: last access time */
$date = sprintf( __( '%1$s at %2$s', 'woocommerce' ), date_i18n( wc_date_format(), strtotime( $key_data['last_access'] ) ), date_i18n( wc_time_format(), strtotime( $key_data['last_access'] ) ) );
echo esc_html( apply_filters( 'woocommerce_api_key_last_access_datetime', $date, $key_data['last_access'] ) );
} else {
esc_html_e( 'Unknown', 'woocommerce' );
}
?>
</span>
</td>
</tr>
<?php endif ?>
</tbody>
</table>
<?php do_action( 'woocommerce_admin_key_fields', $key_data ); ?>
<?php
if ( 0 === intval( $key_id ) ) {
submit_button( __( 'Generate API key', 'woocommerce' ), 'primary', 'update_api_key' );
} else {
?>
<p class="submit">
<?php submit_button( __( 'Save changes', 'woocommerce' ), 'primary', 'update_api_key', false ); ?>
<a style="color: #a00; text-decoration: none; margin-left: 10px;" href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'revoke-key' => $key_id ), admin_url( 'admin.php?page=wc-settings&tab=advanced&section=keys' ) ), 'revoke' ) ); ?>"><?php esc_html_e( 'Revoke key', 'woocommerce' ); ?></a>
</p>
<?php
}
?>
</div>
<script type="text/template" id="tmpl-api-keys-template">
<p id="copy-error"></p>
<table class="form-table">
<tbody>
<tr valign="top">
<th scope="row" class="titledesc">
<?php esc_html_e( 'Consumer key', 'woocommerce' ); ?>
</th>
<td class="forminp">
<input id="key_consumer_key" type="text" value="{{ data.consumer_key }}" size="55" readonly="readonly"> <button type="button" class="button-secondary copy-key" data-tip="<?php esc_attr_e( 'Copied!', 'woocommerce' ); ?>"><?php esc_html_e( 'Copy', 'woocommerce' ); ?></button>
</td>
</tr>
<tr valign="top">
<th scope="row" class="titledesc">
<?php esc_html_e( 'Consumer secret', 'woocommerce' ); ?>
</th>
<td class="forminp">
<input id="key_consumer_secret" type="text" value="{{ data.consumer_secret }}" size="55" readonly="readonly"> <button type="button" class="button-secondary copy-secret" data-tip="<?php esc_attr_e( 'Copied!', 'woocommerce' ); ?>"><?php esc_html_e( 'Copy', 'woocommerce' ); ?></button>
</td>
</tr>
<tr valign="top">
<th scope="row" class="titledesc">
<?php esc_html_e( 'QRCode', 'woocommerce' ); ?>
</th>
<td class="forminp">
<div id="keys-qrcode"></div>
</td>
</tr>
</tbody>
</table>
</script>

View File

@@ -0,0 +1,164 @@
<?php
/**
* Admin view: Settings tax
*
* @package WooCommerce\Admin\Settings
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<div class="wc-tax-rates-search" id="rates-search">
<input type="search" class="wc-tax-rates-search-field" placeholder="<?php esc_attr_e( 'Search&hellip;', 'woocommerce' ); ?>" value="<?php echo isset( $_GET['s'] ) ? esc_attr( $_GET['s'] ) : ''; ?>" />
</div>
<div id="rates-pagination"></div>
<h3>
<?php
/* translators: %s: tax rate */
printf(
__( '"%s" tax rates', 'woocommerce' ),
$current_class ? esc_html( $current_class ) : __( 'Standard', 'woocommerce' )
);
?>
</h3>
<table class="wc_tax_rates wc_input_table widefat">
<thead>
<tr>
<th width="8%"><a href="https://en.wikipedia.org/wiki/ISO_3166-1#Current_codes" target="_blank"><?php _e( 'Country&nbsp;code', 'woocommerce' ); ?></a>&nbsp;<?php echo wc_help_tip( __( 'A 2 digit country code, e.g. US. Leave blank to apply to all.', 'woocommerce' ) ); ?></th>
<th width="8%"><?php _e( 'State code', 'woocommerce' ); ?>&nbsp;<?php echo wc_help_tip( __( 'A 2 digit state code, e.g. AL. Leave blank to apply to all.', 'woocommerce' ) ); ?></th>
<th><?php _e( 'Postcode / ZIP', 'woocommerce' ); ?>&nbsp;<?php echo wc_help_tip( __( 'Postcode for this rule. Semi-colon (;) separate multiple values. Leave blank to apply to all areas. Wildcards (*) and ranges for numeric postcodes (e.g. 12345...12350) can also be used.', 'woocommerce' ) ); ?></th>
<th><?php _e( 'City', 'woocommerce' ); ?>&nbsp;<?php echo wc_help_tip( __( 'Cities for this rule. Semi-colon (;) separate multiple values. Leave blank to apply to all cities.', 'woocommerce' ) ); ?></th>
<th width="8%"><?php _e( 'Rate&nbsp;%', 'woocommerce' ); ?>&nbsp;<?php echo wc_help_tip( __( 'Enter a tax rate (percentage) to 4 decimal places.', 'woocommerce' ) ); ?></th>
<th width="8%"><?php _e( 'Tax name', 'woocommerce' ); ?>&nbsp;<?php echo wc_help_tip( __( 'Enter a name for this tax rate.', 'woocommerce' ) ); ?></th>
<th width="8%"><?php _e( 'Priority', 'woocommerce' ); ?>&nbsp;<?php echo wc_help_tip( __( 'Choose a priority for this tax rate. Only 1 matching rate per priority will be used. To define multiple tax rates for a single area you need to specify a different priority per rate.', 'woocommerce' ) ); ?></th>
<th width="8%"><?php _e( 'Compound', 'woocommerce' ); ?>&nbsp;<?php echo wc_help_tip( __( 'Choose whether or not this is a compound rate. Compound tax rates are applied on top of other tax rates.', 'woocommerce' ) ); ?></th>
<th width="8%"><?php _e( 'Shipping', 'woocommerce' ); ?>&nbsp;<?php echo wc_help_tip( __( 'Choose whether or not this tax rate also gets applied to shipping.', 'woocommerce' ) ); ?></th>
</tr>
</thead>
<tfoot>
<tr>
<th colspan="9">
<a href="#" class="button plus insert"><?php _e( 'Insert row', 'woocommerce' ); ?></a>
<a href="#" class="button minus remove_tax_rates"><?php _e( 'Remove selected row(s)', 'woocommerce' ); ?></a>
<a href="#" download="tax_rates.csv" class="button export"><?php _e( 'Export CSV', 'woocommerce' ); ?></a>
<a href="<?php echo admin_url( 'admin.php?import=woocommerce_tax_rate_csv' ); ?>" class="button import"><?php _e( 'Import CSV', 'woocommerce' ); ?></a>
</th>
</tr>
</tfoot>
<tbody id="rates">
<tr>
<th colspan="9" style="text-align: center;"><?php esc_html_e( 'Loading&hellip;', 'woocommerce' ); ?></th>
</tr>
</tbody>
</table>
<div id="rates-bottom-pagination"></div>
<script type="text/html" id="tmpl-wc-tax-table-row">
<tr class="tips" data-tip="<?php printf( esc_attr__( 'Tax rate ID: %s', 'woocommerce' ), '{{ data.tax_rate_id }}' ); ?>" data-id="{{ data.tax_rate_id }}">
<td class="country">
<input type="text" value="{{ data.tax_rate_country }}" placeholder="*" name="tax_rate_country[{{ data.tax_rate_id }}]" class="wc_input_country_iso" data-attribute="tax_rate_country" style="text-transform:uppercase" />
</td>
<td class="state">
<input type="text" value="{{ data.tax_rate_state }}" placeholder="*" name="tax_rate_state[{{ data.tax_rate_id }}]" data-attribute="tax_rate_state" />
</td>
<td class="postcode">
<input type="text" value="<# if ( data.postcode ) print( _.escape( data.postcode.join( '; ' ) ) ); #>" placeholder="*" data-name="tax_rate_postcode[{{ data.tax_rate_id }}]" data-attribute="postcode" />
</td>
<td class="city">
<input type="text" value="<# if ( data.city ) print( _.escape( data.city.join( '; ' ) ) ); #>" placeholder="*" data-name="tax_rate_city[{{ data.tax_rate_id }}]" data-attribute="city" />
</td>
<td class="rate">
<input type="text" value="{{ data.tax_rate }}" placeholder="0" name="tax_rate[{{ data.tax_rate_id }}]" data-attribute="tax_rate" />
</td>
<td class="name">
<input type="text" value="{{ data.tax_rate_name }}" name="tax_rate_name[{{ data.tax_rate_id }}]" data-attribute="tax_rate_name" />
</td>
<td class="priority">
<input type="number" step="1" min="1" value="{{ data.tax_rate_priority }}" name="tax_rate_priority[{{ data.tax_rate_id }}]" data-attribute="tax_rate_priority" />
</td>
<td class="compound">
<input type="checkbox" class="checkbox" name="tax_rate_compound[{{ data.tax_rate_id }}]" <# if ( parseInt( data.tax_rate_compound, 10 ) ) { #> checked="checked" <# } #> data-attribute="tax_rate_compound" />
</td>
<td class="apply_to_shipping">
<input type="checkbox" class="checkbox" name="tax_rate_shipping[{{ data.tax_rate_id }}]" <# if ( parseInt( data.tax_rate_shipping, 10 ) ) { #> checked="checked" <# } #> data-attribute="tax_rate_shipping" />
</td>
</tr>
</script>
<script type="text/html" id="tmpl-wc-tax-table-row-empty">
<tr>
<th colspan="9" style="text-align:center"><?php esc_html_e( 'No matching tax rates found.', 'woocommerce' ); ?></th>
</tr>
</script>
<script type="text/html" id="tmpl-wc-tax-table-pagination">
<div class="tablenav">
<div class="tablenav-pages">
<span class="displaying-num">
<?php
/* translators: %s: number */
printf(
__( '%s items', 'woocommerce' ), // %s will be a number eventually, but must be a string for now.
'{{ data.qty_rates }}'
);
?>
</span>
<span class="pagination-links">
<# if ( data.current_page === 1 ) { #>
<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&laquo;</span>
<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&lsaquo;</span>
<# } else { #>
<a class="button" data-goto="1">
<span class="screen-reader-text"><?php esc_html_e( 'First page', 'woocommerce' ); ?></span>
<span aria-hidden="true">&laquo;</span>
</a>
<a class="button" data-goto="<# print( Math.max( 1, parseInt( data.current_page, 10 ) - 1 ) ) #>">
<span class="screen-reader-text"><?php esc_html_e( 'Previous page', 'woocommerce' ); ?></span>
<span aria-hidden="true">&lsaquo;</span>
</a>
<# } #>
<span class="paging-input">
<label for="current-page-selector" class="screen-reader-text"><?php esc_html_e( 'Current page', 'woocommerce' ); ?></label>
<?php
/* translators: 1: current page 2: total pages */
printf(
esc_html_x( '%1$s of %2$s', 'Pagination', 'woocommerce' ),
'<input class="current-page" id="current-page-selector" type="text" name="paged" value="{{ data.current_page }}" size="<# print( data.qty_pages.toString().length ) #>" aria-describedby="table-paging">',
'<span class="total-pages">{{ data.qty_pages }}</span>'
);
?>
</span>
<# if ( data.current_page === data.qty_pages ) { #>
<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&rsaquo;</span>
<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&raquo;</span>
<# } else { #>
<a class="button" data-goto="<# print( Math.min( data.qty_pages, parseInt( data.current_page, 10 ) + 1 ) ) #>">
<span class="screen-reader-text"><?php esc_html_e( 'Next page', 'woocommerce' ); ?></span>
<span aria-hidden="true">&rsaquo;</span>
</a>
<a class="button" data-goto="{{ data.qty_pages }}">
<span class="screen-reader-text"><?php esc_html_e( 'Last page', 'woocommerce' ); ?></span>
<span aria-hidden="true">&raquo;</span>
</a>
<# } #>
</span>
</div>
</div>
</script>

View File

@@ -0,0 +1,242 @@
<?php
/**
* Admin View: Edit Webhooks
*
* @package WooCommerce\Admin\Webhooks\Views
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<input type="hidden" name="webhook_id" value="<?php echo esc_attr( $webhook->get_id() ); ?>" />
<div id="webhook-options" class="settings-panel">
<h2><?php esc_html_e( 'Webhook data', 'woocommerce' ); ?></h2>
<table class="form-table">
<tbody>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="webhook_name">
<?php esc_html_e( 'Name', 'woocommerce' ); ?>
<?php
/* translators: %s: date */
echo wc_help_tip( sprintf( __( 'Friendly name for identifying this webhook, defaults to Webhook created on %s.', 'woocommerce' ), (new DateTime('now'))->format( _x( 'M d, Y @ h:i A', 'Webhook created on date parsed by DateTime::format', 'woocommerce' ) ) ) ); // @codingStandardsIgnoreLine
?>
</label>
</th>
<td class="forminp">
<input name="webhook_name" id="webhook_name" type="text" class="input-text regular-input" value="<?php echo esc_attr( $webhook->get_name() ); ?>" />
</td>
</tr>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="webhook_status">
<?php esc_html_e( 'Status', 'woocommerce' ); ?>
<?php echo wc_help_tip( __( 'The options are &quot;Active&quot; (delivers payload), &quot;Paused&quot; (does not deliver), or &quot;Disabled&quot; (does not deliver due delivery failures).', 'woocommerce' ) ); ?>
</label>
</th>
<td class="forminp">
<select name="webhook_status" id="webhook_status" class="wc-enhanced-select">
<?php
$statuses = wc_get_webhook_statuses();
$current_status = $webhook->get_status();
foreach ( $statuses as $status_slug => $status_name ) :
?>
<option value="<?php echo esc_attr( $status_slug ); ?>" <?php selected( $current_status, $status_slug, true ); ?>><?php echo esc_html( $status_name ); ?></option>
<?php endforeach; ?>
</select>
</td>
</tr>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="webhook_topic">
<?php esc_html_e( 'Topic', 'woocommerce' ); ?>
<?php echo wc_help_tip( __( 'Select when the webhook will fire.', 'woocommerce' ) ); ?>
</label>
</th>
<td class="forminp">
<select name="webhook_topic" id="webhook_topic" class="wc-enhanced-select">
<?php
$topic_data = WC_Admin_Webhooks::get_topic_data( $webhook );
$topics = apply_filters(
'woocommerce_webhook_topics',
array(
'' => __( 'Select an option&hellip;', 'woocommerce' ),
'coupon.created' => __( 'Coupon created', 'woocommerce' ),
'coupon.updated' => __( 'Coupon updated', 'woocommerce' ),
'coupon.deleted' => __( 'Coupon deleted', 'woocommerce' ),
'coupon.restored' => __( 'Coupon restored', 'woocommerce' ),
'customer.created' => __( 'Customer created', 'woocommerce' ),
'customer.updated' => __( 'Customer updated', 'woocommerce' ),
'customer.deleted' => __( 'Customer deleted', 'woocommerce' ),
'order.created' => __( 'Order created', 'woocommerce' ),
'order.updated' => __( 'Order updated', 'woocommerce' ),
'order.deleted' => __( 'Order deleted', 'woocommerce' ),
'order.restored' => __( 'Order restored', 'woocommerce' ),
'product.created' => __( 'Product created', 'woocommerce' ),
'product.updated' => __( 'Product updated', 'woocommerce' ),
'product.deleted' => __( 'Product deleted', 'woocommerce' ),
'product.restored' => __( 'Product restored', 'woocommerce' ),
'action' => __( 'Action', 'woocommerce' ),
)
);
foreach ( $topics as $topic_slug => $topic_name ) :
$selected = $topic_slug === $topic_data['topic'] || $topic_slug === $topic_data['resource'] . '.' . $topic_data['event'];
?>
<option value="<?php echo esc_attr( $topic_slug ); ?>" <?php selected( $selected, true, true ); ?>><?php echo esc_html( $topic_name ); ?></option>
<?php endforeach; ?>
</select>
</td>
</tr>
<tr valign="top" id="webhook-action-event-wrap">
<th scope="row" class="titledesc">
<label for="webhook_action_event">
<?php esc_html_e( 'Action event', 'woocommerce' ); ?>
<?php echo wc_help_tip( __( 'Enter the action that will trigger this webhook.', 'woocommerce' ) ); ?>
</label>
</th>
<td class="forminp">
<input name="webhook_action_event" id="webhook_action_event" type="text" class="input-text regular-input" value="<?php echo esc_attr( $topic_data['event'] ); ?>" />
</td>
</tr>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="webhook_delivery_url">
<?php esc_html_e( 'Delivery URL', 'woocommerce' ); ?>
<?php echo wc_help_tip( __( 'URL where the webhook payload is delivered.', 'woocommerce' ) ); ?>
</label>
</th>
<td class="forminp">
<input name="webhook_delivery_url" id="webhook_delivery_url" type="text" class="input-text regular-input" value="<?php echo esc_attr( $webhook->get_delivery_url() ); ?>" />
</td>
</tr>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="webhook_secret">
<?php esc_html_e( 'Secret', 'woocommerce' ); ?>
<?php echo wc_help_tip( __( 'The secret key is used to generate a hash of the delivered webhook and provided in the request headers.', 'woocommerce' ) ); ?>
</label>
</th>
<td class="forminp">
<input name="webhook_secret" id="webhook_secret" type="text" class="input-text regular-input" value="<?php echo esc_attr( $webhook->get_secret() ); ?>" />
</td>
</tr>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="webhook_api_version">
<?php esc_html_e( 'API Version', 'woocommerce' ); ?>
<?php echo wc_help_tip( __( 'REST API version used in the webhook deliveries.', 'woocommerce' ) ); ?>
</label>
</th>
<td class="forminp">
<select name="webhook_api_version" id="webhook_api_version">
<?php foreach ( array_reverse( wc_get_webhook_rest_api_versions() ) as $version ) : ?>
<option value="<?php echo esc_attr( $version ); ?>" <?php selected( $version, $webhook->get_api_version(), true ); ?>>
<?php
/* translators: %d: rest api version number */
echo esc_html( sprintf( __( 'WP REST API Integration v%d', 'woocommerce' ), str_replace( 'wp_api_v', '', $version ) ) );
?>
</option>
<?php endforeach; ?>
<?php
$legacy_api_option_name =
WC()->legacy_rest_api_is_available() ?
__( 'Legacy API v3 (deprecated)', 'woocommerce' ) :
__( 'Legacy API v3 (⚠️ NOT AVAILABLE)', 'woocommerce' );
?>
<option value="legacy_v3" <?php selected( 'legacy_v3', $webhook->get_api_version(), true ); ?>><?php echo esc_html( $legacy_api_option_name ); ?></option>
</select>
</td>
</tr>
</tbody>
</table>
<?php
/**
* Fires within the webhook editor, after the Webhook Data fields have rendered.
*
* @param WC_Webhook $webhook
*/
do_action( 'woocommerce_webhook_options', $webhook );
?>
</div>
<div id="webhook-actions" class="settings-panel">
<h2><?php esc_html_e( 'Webhook actions', 'woocommerce' ); ?></h2>
<table class="form-table">
<tbody>
<?php if ( $webhook->get_date_created() && '0000-00-00 00:00:00' !== $webhook->get_date_created()->date( 'Y-m-d H:i:s' ) ) : ?>
<?php if ( is_null( $webhook->get_date_modified() ) ) : ?>
<tr valign="top">
<th scope="row" class="titledesc">
<?php esc_html_e( 'Created at', 'woocommerce' ); ?>
</th>
<td class="forminp">
<?php echo esc_html( date_i18n( __( 'M j, Y @ G:i', 'woocommerce' ), strtotime( $webhook->get_date_created()->date( 'Y-m-d H:i:s' ) ) ) ); ?>
</td>
</tr>
<?php else : ?>
<tr valign="top">
<th scope="row" class="titledesc">
<?php esc_html_e( 'Created at', 'woocommerce' ); ?>
</th>
<td class="forminp">
<?php echo esc_html( date_i18n( __( 'M j, Y @ G:i', 'woocommerce' ), strtotime( $webhook->get_date_created()->date( 'Y-m-d H:i:s' ) ) ) ); ?>
</td>
</tr>
<tr valign="top">
<th scope="row" class="titledesc">
<?php esc_html_e( 'Updated at', 'woocommerce' ); ?>
</th>
<td class="forminp">
<?php echo esc_html( date_i18n( __( 'M j, Y @ G:i', 'woocommerce' ), strtotime( $webhook->get_date_modified()->date( 'Y-m-d H:i:s' ) ) ) ); ?>
</td>
</tr>
<?php endif; ?>
<?php endif; ?>
<tr valign="top">
<td colspan="2" scope="row" style="padding-left: 0;">
<p class="submit">
<button type="submit" class="button button-primary button-large" name="save" id="publish" accesskey="p"><?php esc_html_e( 'Save webhook', 'woocommerce' ); ?></button>
<?php
if ( $webhook->get_id() ) :
$delete_url = wp_nonce_url(
add_query_arg(
array(
'delete' => $webhook->get_id(),
),
admin_url( 'admin.php?page=wc-settings&tab=advanced&section=webhooks' )
),
'delete-webhook'
);
?>
<a style="color: #a00; text-decoration: none; margin-left: 10px;" href="<?php echo esc_url( $delete_url ); ?>"><?php esc_html_e( 'Delete permanently', 'woocommerce' ); ?></a>
<?php endif; ?>
</p>
</td>
</tr>
</tbody>
</table>
</div>
<script type="text/javascript">
jQuery( function ( $ ) {
$( '#webhook-options' ).find( '#webhook_topic' ).on( 'change', function() {
var current = $( this ).val(),
action_event_field = $( '#webhook-options' ).find( '#webhook-action-event-wrap' );
action_event_field.hide();
if ( 'action' === current ) {
action_event_field.show();
}
}).trigger( 'change' );
});
</script>

View File

@@ -0,0 +1,136 @@
<?php
/**
* Tax settings.
*
* @package WooCommerce\Admin\Settings.
*/
defined( 'ABSPATH' ) || exit;
$settings = array(
array(
'title' => __( 'Tax options', 'woocommerce' ),
'type' => 'title',
'desc' => '',
'id' => 'tax_options',
),
array(
'title' => __( 'Prices entered with tax', 'woocommerce' ),
'id' => 'woocommerce_prices_include_tax',
'default' => 'no',
'type' => 'radio',
'desc_tip' => __( 'This option is important as it will affect how you input prices. If you select "Yes", enter prices including your base location\'s tax rate, the baseline for tax calculations. Changing this option will not update existing products.', 'woocommerce' ),
'options' => array(
'yes' => __( 'Yes, I will enter prices inclusive of tax', 'woocommerce' ),
'no' => __( 'No, I will enter prices exclusive of tax', 'woocommerce' ),
),
),
array(
'title' => __( 'Calculate tax based on', 'woocommerce' ),
'id' => 'woocommerce_tax_based_on',
'desc_tip' => __( 'This option determines which address is used to calculate tax.', 'woocommerce' ),
'default' => 'shipping',
'type' => 'select',
'class' => 'wc-enhanced-select',
'options' => array(
'shipping' => __( 'Customer shipping address', 'woocommerce' ),
'billing' => __( 'Customer billing address', 'woocommerce' ),
'base' => __( 'Shop base address', 'woocommerce' ),
),
),
'shipping-tax-class' => array(
'title' => __( 'Shipping tax class', 'woocommerce' ),
'desc' => __( 'Optionally control which tax class shipping gets, or leave it so shipping tax is based on the cart items themselves.', 'woocommerce' ),
'id' => 'woocommerce_shipping_tax_class',
'css' => 'min-width:150px;',
'default' => 'inherit',
'type' => 'select',
'class' => 'wc-enhanced-select',
'options' => array( 'inherit' => __( 'Shipping tax class based on cart items', 'woocommerce' ) ) + wc_get_product_tax_class_options(),
'desc_tip' => true,
),
array(
'title' => __( 'Rounding', 'woocommerce' ),
'desc' => __( 'Round tax at subtotal level, instead of rounding per line', 'woocommerce' ),
'id' => 'woocommerce_tax_round_at_subtotal',
'default' => 'no',
'type' => 'checkbox',
),
array(
'title' => __( 'Additional tax classes', 'woocommerce' ),
'desc_tip' => __( 'List additional tax classes you need below (1 per line, e.g. Reduced Rates). These are in addition to "Standard rate" which exists by default.', 'woocommerce' ),
'id' => 'woocommerce_tax_classes',
'css' => 'height: 65px;',
'type' => 'textarea',
'default' => '',
'is_option' => false,
'value' => implode( "\n", WC_Tax::get_tax_classes() ),
),
array(
'title' => __( 'Display prices in the shop', 'woocommerce' ),
'id' => 'woocommerce_tax_display_shop',
'default' => 'excl',
'type' => 'select',
'class' => 'wc-enhanced-select',
'options' => array(
'incl' => __( 'Including tax', 'woocommerce' ),
'excl' => __( 'Excluding tax', 'woocommerce' ),
),
),
array(
'title' => __( 'Display prices during cart and checkout', 'woocommerce' ),
'id' => 'woocommerce_tax_display_cart',
'default' => 'excl',
'type' => 'select',
'class' => 'wc-enhanced-select',
'options' => array(
'incl' => __( 'Including tax', 'woocommerce' ),
'excl' => __( 'Excluding tax', 'woocommerce' ),
),
),
array( 'type' => 'conflict_error' ), // React mount point for embedded banner slotfill.
array( 'type' => 'add_settings_slot' ), // React mount point for settings slotfill.
array(
'title' => __( 'Price display suffix', 'woocommerce' ),
'id' => 'woocommerce_price_display_suffix',
'default' => '',
'placeholder' => __( 'N/A', 'woocommerce' ),
'type' => 'text',
'desc_tip' => __( 'Define text to show after your product prices. This could be, for example, "inc. Vat" to explain your pricing. You can also have prices substituted here using one of the following: {price_including_tax}, {price_excluding_tax}.', 'woocommerce' ),
),
array(
'title' => __( 'Display tax totals', 'woocommerce' ),
'id' => 'woocommerce_tax_total_display',
'default' => 'itemized',
'type' => 'select',
'class' => 'wc-enhanced-select',
'options' => array(
'single' => __( 'As a single total', 'woocommerce' ),
'itemized' => __( 'Itemized', 'woocommerce' ),
),
'autoload' => false,
),
array(
'type' => 'sectionend',
'id' => 'tax_options',
),
);
if ( ! wc_shipping_enabled() ) {
unset( $settings['shipping-tax-class'] );
}
return apply_filters( 'woocommerce_tax_settings', $settings );