Merge branch 'master' into add_to_cart_variation

This commit is contained in:
Claudio Sanches 2017-09-06 14:33:57 -03:00 committed by GitHub
commit f8f9c7b04c
19 changed files with 215 additions and 162 deletions

View File

@ -13,7 +13,7 @@ If you are not a developer, please use the [WooCommerce plugin page](https://wor
To disclose a security issue to our team, [please submit a report via HackerOne here](https://hackerone.com/automattic/).
## Support
This repository is not suitable for support. Please don't use our issue tracker for support requests, but for core, WooCommerce issues only. Support can take place through the appropriate channels:
This repository is not suitable for support. Please don't use our issue tracker for support requests, but for core WooCommerce issues only. Support can take place through the appropriate channels:
* The [WooCommerce premium support portal](https://woocommerce.com/my-account/create-a-ticket/) for customers who have purchased themes or extensions.
* [Our community forum on wp.org](https://wordpress.org/support/plugin/woocommerce) which is available for all WooCommerce users.
@ -24,4 +24,4 @@ Support requests in issues on this repository will be closed on sight.
If you have a patch or have stumbled upon an issue with WooCommerce core, you can contribute this back to the code. Please read our [contributor guidelines](https://github.com/woocommerce/woocommerce/blob/master/.github/CONTRIBUTING.md) for more information how you can do this.
## Contributing new features to the WooCommerce REST API
If you're like to add a feature to the next version of the REST API, contribute here: https://github.com/woocommerce/wc-api-dev
If you'd like to add a feature to the next version of the REST API, contribute here: https://github.com/woocommerce/wc-api-dev

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4355,7 +4355,7 @@ img.help_tip {
width: 100%;
vertical-align: middle;
margin: 2px 0 0;
padding: 6px;
padding: 5px;
}
select {

File diff suppressed because one or more lines are too long

View File

@ -137,7 +137,6 @@ $color_button_secondary: $woo_pink2;
font-size: 14px;
label,
span,
li {
line-height: 21px;
padding: 8px 16px;
@ -148,7 +147,6 @@ $color_button_secondary: $woo_pink2;
}
}
span,
li {
border-bottom: 1px solid #E1E1E1;
}
@ -158,12 +156,15 @@ $color_button_secondary: $woo_pink2;
display: block;
}
span,
li{
label {
text-decoration: none;
}
li {
display: none;
}
li{
li {
&::before {
display: none;
}
@ -183,12 +184,11 @@ $color_button_secondary: $woo_pink2;
&:hover {
box-shadow: 0 3px 5px rgba(0, 0, 0, 0.2);
label{
label {
border-bottom: 1px solid #E1E1E1;
}
span,
li{
li {
display: block;
}

View File

@ -1020,7 +1020,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
$coupon_object = apply_filters( 'woocommerce_order_recalculate_coupons_coupon_object', $coupon_object, $coupon_code, $coupon_item, $this );
if ( $coupon_object ) {
$discounts->apply_coupon( $coupon_object );
$discounts->apply_coupon( $coupon_object, false );
}
}

View File

@ -388,28 +388,7 @@ class WC_REST_System_Status_Tools_Controller extends WC_REST_Controller {
$message = __( 'Product transients cleared', 'woocommerce' );
break;
case 'clear_expired_transients' :
/*
* Deletes all expired transients. The multi-table delete syntax is used.
* to delete the transient record from table a, and the corresponding.
* transient_timeout record from table b.
*
* Based on code inside core's upgrade_network() function.
*/
$sql = "DELETE a, b FROM $wpdb->options a, $wpdb->options b
WHERE a.option_name LIKE %s
AND a.option_name NOT LIKE %s
AND b.option_name = CONCAT( '_transient_timeout_', SUBSTRING( a.option_name, 12 ) )
AND b.option_value < %d";
$rows = $wpdb->query( $wpdb->prepare( $sql, $wpdb->esc_like( '_transient_' ) . '%', $wpdb->esc_like( '_transient_timeout_' ) . '%', time() ) );
$sql = "DELETE a, b FROM $wpdb->options a, $wpdb->options b
WHERE a.option_name LIKE %s
AND a.option_name NOT LIKE %s
AND b.option_name = CONCAT( '_site_transient_timeout_', SUBSTRING( a.option_name, 17 ) )
AND b.option_value < %d";
$rows2 = $wpdb->query( $wpdb->prepare( $sql, $wpdb->esc_like( '_site_transient_' ) . '%', $wpdb->esc_like( '_site_transient_timeout_' ) . '%', time() ) );
$message = sprintf( __( '%d transients rows cleared', 'woocommerce' ), $rows + $rows2 );
$message = sprintf( __( '%d transients rows cleared', 'woocommerce' ), wc_delete_expired_transients() );
break;
case 'delete_orphaned_variations' :
/**

View File

@ -1206,12 +1206,12 @@ class WC_Cart extends WC_Legacy_Cart {
*/
public function set_quantity( $cart_item_key, $quantity = 1, $refresh_totals = true ) {
if ( 0 === $quantity || $quantity < 0 ) {
do_action( 'woocommerce_before_cart_item_quantity_zero', $cart_item_key );
do_action( 'woocommerce_before_cart_item_quantity_zero', $cart_item_key, $this );
unset( $this->cart_contents[ $cart_item_key ] );
} else {
$old_quantity = $this->cart_contents[ $cart_item_key ]['quantity'];
$this->cart_contents[ $cart_item_key ]['quantity'] = $quantity;
do_action( 'woocommerce_after_cart_item_quantity_update', $cart_item_key, $quantity, $old_quantity );
do_action( 'woocommerce_after_cart_item_quantity_update', $cart_item_key, $quantity, $old_quantity, $this );
}
if ( $refresh_totals ) {

View File

@ -299,7 +299,9 @@ class WC_Checkout {
$order->{"set_{$key}"}( $value );
// Store custom fields prefixed with wither shipping_ or billing_. This is for backwards compatibility with 2.6.x.
} elseif ( 0 === stripos( $key, 'billing_' ) || 0 === stripos( $key, 'shipping_' ) ) {
// TODO: Fix conditional to only include shipping/billing address fields in a smarter way without str(i)pos.
} elseif ( ( 0 === stripos( $key, 'billing_' ) || 0 === stripos( $key, 'shipping_' ) )
&& ! in_array( $key, array( 'shipping_method', 'shipping_total', 'shipping_tax' ) ) ) {
$order->update_meta_data( '_' . $key, $value );
}
}

View File

@ -215,7 +215,7 @@ class WC_Countries {
*/
public function get_allowed_countries() {
if ( 'all' === get_option( 'woocommerce_allowed_countries' ) ) {
return $this->countries;
return apply_filters( 'woocommerce_countries_allowed_countries', $this->countries );
}
if ( 'all_except' === get_option( 'woocommerce_allowed_countries' ) ) {

View File

@ -206,14 +206,15 @@ class WC_Discounts {
*
* @since 3.2.0
* @param WC_Coupon $coupon Coupon object being applied to the items.
* @param bool $validate Set to false to skip coupon validation.
* @return bool|WP_Error True if applied or WP_Error instance in failure.
*/
public function apply_coupon( $coupon ) {
public function apply_coupon( $coupon, $validate = true ) {
if ( ! is_a( $coupon, 'WC_Coupon' ) ) {
return new WP_Error( 'invalid_coupon', __( 'Invalid coupon', 'woocommerce' ) );
}
$is_coupon_valid = $this->is_coupon_valid( $coupon );
$is_coupon_valid = $validate ? $this->is_coupon_valid( $coupon ) : true;
if ( is_wp_error( $is_coupon_valid ) ) {
return $is_coupon_valid;

View File

@ -734,26 +734,18 @@ class WC_Form_Handler {
$add_to_cart_handler = apply_filters( 'woocommerce_add_to_cart_handler', $adding_to_cart->get_type(), $adding_to_cart );
// Variable product handling
if ( 'variable' === $add_to_cart_handler ) {
if ( 'variable' === $add_to_cart_handler || 'variation' === $add_to_cart_handler ) {
$was_added_to_cart = self::add_to_cart_handler_variable( $product_id );
// Grouped Products
} elseif ( 'grouped' === $add_to_cart_handler ) {
$was_added_to_cart = self::add_to_cart_handler_grouped( $product_id );
// Custom Handler
} elseif ( has_action( 'woocommerce_add_to_cart_handler_' . $add_to_cart_handler ) ) {
do_action( 'woocommerce_add_to_cart_handler_' . $add_to_cart_handler, $url );
// Simple Products
do_action( 'woocommerce_add_to_cart_handler_' . $add_to_cart_handler, $url ); // Custom handler.
} else {
$was_added_to_cart = self::add_to_cart_handler_simple( $product_id );
}
// If we added the product to the cart we can now optionally do a redirect.
if ( 0 === $was_added_to_cart && wc_notice_count( 'error' ) ) {
// If has custom URL redirect there
if ( $was_added_to_cart && 0 === wc_notice_count( 'error' ) ) {
if ( $url = apply_filters( 'woocommerce_add_to_cart_redirect', $url ) ) {
wp_safe_redirect( $url );
exit;
@ -766,8 +758,9 @@ class WC_Form_Handler {
/**
* Handle adding simple products to the cart.
* @since 2.4.6 Split from add_to_cart_action
* @param int $product_id
*
* @since 2.4.6 Split from add_to_cart_action.
* @param int $product_id Product ID to add to the cart.
* @return bool success or not
*/
private static function add_to_cart_handler_simple( $product_id ) {
@ -783,8 +776,9 @@ class WC_Form_Handler {
/**
* Handle adding grouped products to the cart.
* @since 2.4.6 Split from add_to_cart_action
* @param int $product_id
*
* @since 2.4.6 Split from add_to_cart_action.
* @param int $product_id Product ID to add to the cart.
* @return bool success or not
*/
private static function add_to_cart_handler_grouped( $product_id ) {
@ -830,33 +824,48 @@ class WC_Form_Handler {
/**
* Handle adding variable products to the cart.
* @since 2.4.6 Split from add_to_cart_action
* @param int $product_id
*
* @since 2.4.6 Split from add_to_cart_action.
* @param int $product_id Product ID to add to the cart.
* @return bool success or not
*/
private static function add_to_cart_handler_variable( $product_id ) {
$adding_to_cart = wc_get_product( $product_id );
$variation_id = empty( $_REQUEST['variation_id'] ) ? '' : absint( $_REQUEST['variation_id'] );
$quantity = empty( $_REQUEST['quantity'] ) ? 1 : wc_stock_amount( $_REQUEST['quantity'] );
$missing_attributes = array();
$variations = array();
$attributes = $adding_to_cart->get_attributes();
// If no variation ID is set, attempt to get a variation ID from posted attributes.
if ( empty( $variation_id ) ) {
$data_store = WC_Data_Store::load( 'product' );
$variation_id = $data_store->find_matching_product_variation( $adding_to_cart, wp_unslash( $_REQUEST ) );
}
// Validate the attributes.
try {
$variation_id = empty( $_REQUEST['variation_id'] ) ? '' : absint( $_REQUEST['variation_id'] );
$quantity = empty( $_REQUEST['quantity'] ) ? 1 : wc_stock_amount( $_REQUEST['quantity'] );
$missing_attributes = array();
$variations = array();
$adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) {
return false;
}
// If the $product_id was in fact a variation ID, update the variables.
if ( $adding_to_cart->is_type( 'variation' ) ) {
$variation_id = $product_id;
$product_id = $adding_to_cart->get_parent_id();
$adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) {
return false;
}
}
// If no variation ID is set, attempt to get a variation ID from posted attributes.
if ( empty( $variation_id ) ) {
$data_store = WC_Data_Store::load( 'product' );
$variation_id = $data_store->find_matching_product_variation( $adding_to_cart, array_map( 'sanitize_title', wp_unslash( $_REQUEST ) ) );
}
// Validate the attributes.
if ( empty( $variation_id ) ) {
throw new Exception( __( 'Please choose product options&hellip;', 'woocommerce' ) );
}
$variation_data = wc_get_product_variation_attributes( $variation_id );
foreach ( $attributes as $attribute ) {
foreach ( $adding_to_cart->get_attributes() as $attribute ) {
if ( ! $attribute['is_variation'] ) {
continue;
}
@ -864,22 +873,21 @@ class WC_Form_Handler {
$taxonomy = 'attribute_' . sanitize_title( $attribute['name'] );
if ( isset( $_REQUEST[ $taxonomy ] ) ) {
// Get value from post data
if ( $attribute['is_taxonomy'] ) {
// Don't use wc_clean as it destroys sanitized characters
$value = sanitize_title( stripslashes( $_REQUEST[ $taxonomy ] ) );
// Don't use wc_clean as it destroys sanitized characters.
$value = sanitize_title( wp_unslash( $_REQUEST[ $taxonomy ] ) );
} else {
$value = wc_clean( stripslashes( $_REQUEST[ $taxonomy ] ) );
$value = wc_clean( wp_unslash( $_REQUEST[ $taxonomy ] ) );
}
// Get valid value from variation
// Get valid value from variation data.
$valid_value = isset( $variation_data[ $taxonomy ] ) ? $variation_data[ $taxonomy ] : '';
// Allow if valid or show error.
if ( $valid_value === $value ) {
$variations[ $taxonomy ] = $value;
// If valid values are empty, this is an 'any' variation so get all possible values.
} elseif ( '' === $valid_value && in_array( $value, $attribute->get_slugs() ) ) {
// If valid values are empty, this is an 'any' variation so get all possible values.
$variations[ $taxonomy ] = $value;
} else {
throw new Exception( sprintf( __( 'Invalid value posted for %s', 'woocommerce' ), wc_attribute_label( $attribute['name'] ) ) );
@ -889,14 +897,13 @@ class WC_Form_Handler {
}
}
if ( ! empty( $missing_attributes ) ) {
throw new Exception( sprintf( _n( '%s is a required field', '%s are required fields', sizeof( $missing_attributes ), 'woocommerce' ), wc_format_list_of_items( $missing_attributes ) ) );
throw new Exception( sprintf( _n( '%s is a required field', '%s are required fields', count( $missing_attributes ), 'woocommerce' ), wc_format_list_of_items( $missing_attributes ) ) );
}
} catch ( Exception $e ) {
wc_add_notice( $e->getMessage(), 'error' );
return false;
}
// Add to cart validation
$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity, $variation_id, $variations );
if ( $passed_validation && false !== WC()->cart->add_to_cart( $product_id, $quantity, $variation_id, $variations ) ) {

View File

@ -17,7 +17,11 @@ if ( ! defined( 'ABSPATH' ) ) {
*/
class WC_Install {
/** @var array DB updates and callbacks that need to be run per version */
/**
* DB updates and callbacks that need to be run per version.
*
* @var array
*/
private static $db_updates = array(
'2.0.0' => array(
'wc_update_200_file_paths',
@ -94,7 +98,11 @@ class WC_Install {
),
);
/** @var object Background update class */
/**
* Background update class.
*
* @var object
*/
private static $background_updater;
/**
@ -153,81 +161,103 @@ class WC_Install {
* Install WC.
*/
public static function install() {
global $wpdb;
if ( ! is_blog_installed() ) {
return;
}
if ( ! defined( 'WC_INSTALLING' ) ) {
define( 'WC_INSTALLING', true );
}
// Ensure needed classes are loaded
include_once( dirname( __FILE__ ) . '/admin/class-wc-admin-notices.php' );
wc_maybe_define_constant( 'WC_INSTALLING', true );
self::remove_admin_notices();
self::create_options();
self::create_tables();
self::create_roles();
self::setup_environment();
self::create_terms();
self::create_cron_jobs();
self::create_files();
self::maybe_enable_setup_wizard();
self::update_wc_version();
self::maybe_update_db_version();
// Register post types
do_action( 'woocommerce_flush_rewrite_rules' );
do_action( 'woocommerce_installed' );
}
/**
* Reset any notices added to admin.
*
* @since 3.2.0
*/
private static function remove_admin_notices() {
include_once( dirname( __FILE__ ) . '/admin/class-wc-admin-notices.php' );
WC_Admin_Notices::remove_all_notices();
}
/**
* Setup WC environment - post types, taxonomies, endpoints.
*
* @since 3.2.0
*/
private static function setup_environment() {
WC_Post_types::register_post_types();
WC_Post_types::register_taxonomies();
// Also register endpoints - this needs to be done prior to rewrite rule flush
WC()->query->init_query_vars();
WC()->query->add_endpoints();
WC_API::add_endpoint();
WC_Auth::add_endpoint();
}
self::create_terms();
self::create_cron_jobs();
self::create_files();
/**
* Is this a brand new WC install?
*
* @since 3.2.0
* @return boolean
*/
private static function is_new_install() {
return is_null( get_option( 'woocommerce_version', null ) ) && is_null( get_option( 'woocommerce_db_version', null ) );
}
// Queue upgrades/setup wizard
$current_wc_version = get_option( 'woocommerce_version', null );
$current_db_version = get_option( 'woocommerce_db_version', null );
/**
* Is a DB update needed?
*
* @since 3.2.0
* @return boolean
*/
private static function needs_db_update() {
$current_db_version = get_option( 'woocommerce_db_version', null );
$updates = self::get_db_update_callbacks();
WC_Admin_Notices::remove_all_notices();
return ! is_null( $current_db_version ) && version_compare( $current_db_version, max( array_keys( $updates ) ), '<' );
}
// No versions? This is a new install :)
if ( is_null( $current_wc_version ) && is_null( $current_db_version ) && apply_filters( 'woocommerce_enable_setup_wizard', true ) ) {
/**
* See if we need the wizard or not.
*
* @since 3.2.0
*/
private static function maybe_enable_setup_wizard() {
if ( apply_filters( 'woocommerce_enable_setup_wizard', self::is_new_install() ) ) {
WC_Admin_Notices::add_notice( 'install' );
set_transient( '_wc_activation_redirect', 1, 30 );
// No page? Let user run wizard again..
} elseif ( ! get_option( 'woocommerce_cart_page_id' ) ) {
WC_Admin_Notices::add_notice( 'install' );
}
}
if ( ! is_null( $current_db_version ) && version_compare( $current_db_version, max( array_keys( self::$db_updates ) ), '<' ) ) {
WC_Admin_Notices::add_notice( 'update' );
/**
* See if we need to show or run database updates during install.
*
* @since 3.2.0
*/
private static function maybe_update_db_version() {
if ( self::needs_db_update() ) {
if ( apply_filters( 'woocommerce_enable_auto_update_db', false ) ) {
self::init_background_updater();
self::update();
} else {
WC_Admin_Notices::add_notice( 'update' );
}
} else {
self::update_db_version();
}
self::update_wc_version();
// Flush rules after install
do_action( 'woocommerce_flush_rewrite_rules' );
delete_transient( 'wc_attribute_taxonomies' );
/*
* Deletes all expired transients. The multi-table delete syntax is used
* to delete the transient record from table a, and the corresponding
* transient_timeout record from table b.
*
* Based on code inside core's upgrade_network() function.
*/
$sql = "DELETE a, b FROM $wpdb->options a, $wpdb->options b
WHERE a.option_name LIKE %s
AND a.option_name NOT LIKE %s
AND b.option_name = CONCAT( '_transient_timeout_', SUBSTRING( a.option_name, 12 ) )
AND b.option_value < %d";
$wpdb->query( $wpdb->prepare( $sql, $wpdb->esc_like( '_transient_' ) . '%', $wpdb->esc_like( '_transient_timeout_' ) . '%', time() ) );
// Trigger action
do_action( 'woocommerce_installed' );
}
/**

View File

@ -66,7 +66,6 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
'_shipping_address_index',
'_recorded_sales',
'_recorded_coupon_usage_counts',
'_shipping_method',
);
/**

View File

@ -190,22 +190,6 @@ class WC_Email extends WC_Settings_API {
*/
protected $placeholders = array();
/**
* Strings to find in subjects/headings.
*
* @deprecated 3.2.0 in favour of placeholders
* @var array
*/
public $find = array();
/**
* Strings to replace in subjects/headings.
*
* @deprecated 3.2.0 in favour of placeholders
* @var array
*/
public $replace = array();
/**
* Constructor.
*/
@ -250,14 +234,23 @@ class WC_Email extends WC_Settings_API {
/**
* Format email string.
*
* @param mixed $string
* @param mixed $string Text to replace placeholders in.
* @return string
*/
public function format_string( $string ) {
// Legacy placeholders. @todo deprecate in 4.0.0.
$find = array_keys( $this->placeholders );
$replace = array_values( $this->placeholders );
// If using legacy find replace, add those to our find/replace arrays first. @todo deprecate in 4.0.0.
if ( isset( $this->find, $this->replace ) ) {
$find = array_merge( $this->find, $find );
$replace = array_merge( $this->replace, $replace );
}
// If using the older style filters for find and replace, ensure the array is associative and then pass through filters. @todo deprecate in 4.0.0.
if ( has_filter( 'woocommerce_email_format_string_replace' ) || has_filter( 'woocommerce_email_format_string_find' ) ) {
$legacy_find = array();
$legacy_replace = array();
$legacy_find = isset( $this->find ) ? $this->find : array();
$legacy_replace = isset( $this->replace ) ? $this->replace : array();
foreach ( $this->placeholders as $find => $replace ) {
$legacy_key = sanitize_title( str_replace( '_', '-', trim( $find, '{}' ) ) );
@ -267,7 +260,13 @@ class WC_Email extends WC_Settings_API {
$string = str_replace( apply_filters( 'woocommerce_email_format_string_find', $legacy_find, $this ), apply_filters( 'woocommerce_email_format_string_replace', $legacy_replace, $this ), $string );
}
return str_replace( $this->find, $this->replace, $string );
/**
* woocommerce_email_format_string filter for main find/replace code.
*
* @since 3.2.0
*/
return apply_filters( 'woocommerce_email_format_string', str_replace( $find, $replace, $string ), $this );
}
/**

View File

@ -1822,3 +1822,36 @@ function wc_prevent_dangerous_auto_updates( $should_update, $plugin ) {
return $should_update;
}
add_filter( 'auto_update_plugin', 'wc_prevent_dangerous_auto_updates', 99, 2 );
/**
* Delete expired transients.
*
* Deletes all expired transients. The multi-table delete syntax is used.
* to delete the transient record from table a, and the corresponding.
* transient_timeout record from table b.
*
* Based on code inside core's upgrade_network() function.
*
* @since 3.2.0
* @return int Number of transients that were cleared.
*/
function wc_delete_expired_transients() {
global $wpdb;
$sql = "DELETE a, b FROM $wpdb->options a, $wpdb->options b
WHERE a.option_name LIKE %s
AND a.option_name NOT LIKE %s
AND b.option_name = CONCAT( '_transient_timeout_', SUBSTRING( a.option_name, 12 ) )
AND b.option_value < %d";
$rows = $wpdb->query( $wpdb->prepare( $sql, $wpdb->esc_like( '_transient_' ) . '%', $wpdb->esc_like( '_transient_timeout_' ) . '%', time() ) );
$sql = "DELETE a, b FROM $wpdb->options a, $wpdb->options b
WHERE a.option_name LIKE %s
AND a.option_name NOT LIKE %s
AND b.option_name = CONCAT( '_site_transient_timeout_', SUBSTRING( a.option_name, 17 ) )
AND b.option_value < %d";
$rows2 = $wpdb->query( $wpdb->prepare( $sql, $wpdb->esc_like( '_site_transient_' ) . '%', $wpdb->esc_like( '_site_transient_timeout_' ) . '%', time() ) );
return absint( $rows + $rows2 );
}
add_action( 'woocommerce_installed', 'wc_delete_expired_transients' );

View File

@ -508,11 +508,12 @@ function wc_price( $price, $args = array() ) {
/**
* Filters the string of price markup.
*
* @param string $return Price HTML markup
* @param float $unformatted_price Price as float to allow plugins custom formatting
* @param array $args Pass on the args
* @param string $return Price HTML markup.
* @param string $price Formatted price.
* @param array $args Pass on the args.
* @param float $unformatted_price Price as float to allow plugins custom formatting. Since 3.2.0.
*/
return apply_filters( 'wc_price', $return, $unformatted_price, $args );
return apply_filters( 'wc_price', $return, $price, $args, $unformatted_price );
}
/**

View File

@ -19,8 +19,10 @@
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<h2 class="woocommerce-order-downloads__title"><?php _e( 'Downloads', 'woocommerce' ); ?></h2>
$text_align = is_rtl() ? 'right' : 'left';
?><h2 class="woocommerce-order-downloads__title"><?php _e( 'Downloads', 'woocommerce' ); ?></h2>
<table class="td" cellspacing="0" cellpadding="6" style="width: 100%; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; margin-bottom: 40px;" border="1">
<thead>