Merge branch 'master' into update/gdpr-order-cleanup

This commit is contained in:
Mike Jolley 2018-04-18 11:21:43 +01:00
commit 9dfb91835e
39 changed files with 1667 additions and 974 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -491,7 +491,7 @@ body {
.wc-setup-footer-links {
font-size: 0.85em;
color: #b5b5b5;
color: #7b7b7b;
margin: 1.18em auto;
display: inline-block;
text-align: center;
@ -634,6 +634,14 @@ body {
}
}
&.eway-logo .wc-wizard-service-name img {
max-width: 87px;
}
&.payfast-logo .wc-wizard-service-name img {
max-width: 140px;
}
.wc-wizard-service-description {
flex-grow: 1;
padding: 20px;
@ -1200,3 +1208,64 @@ p.jetpack-terms {
margin-left: 1.5em;
}
}
.recommended-step {
border: 1px solid #ebebeb;
border-radius: 4px;
padding: 2.5em;
li {
list-style: none;
&:last-child .recommended-item {
margin-bottom: 0; // Avoid extra space at the end of the list.
}
}
.recommended-item {
display: flex;
align-items: center;
margin-bottom: 1.5em;
}
.recommended-item-icon {
border: 1px solid #fff;
border-radius: 7px;
height: 3.5em;
margin-right: 1em;
margin-left: 1em;
&.recommended-item-icon-storefront_theme {
background-color: #f4a224;
max-height: 3em;
max-width: 3em;
padding: ( 3.5em - 3em ) / 2;
}
&.recommended-item-icon-automated_taxes {
background-color: #d0011b;
max-height: 1.75em;
padding: ( 3.5em - 1.75em ) / 2;
}
&.recommended-item-icon-mailchimp {
background-color: #209bbb;
height: 2em;
padding: ( 3.5em - 2em ) / 2;
}
}
.recommended-item-description-container {
h3 {
font-size: 15px;
font-weight: bold;
letter-spacing: 0.5px;
margin-bottom: 0;
}
p {
margin-top: 0;
line-height: 1.5em;
}
}
}

BIN
assets/images/eway-logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 367 300"><path fill="#FFF" d="M251 135.1c-6.4 4.9-14.4 7.8-23 7.8-8.9 0-17-3.1-23.5-8.1-6.5 5.1-14.6 8.1-23.5 8.1s-17.1-3.1-23.6-8.2c-6.5 5.1-14.7 8.2-23.6 8.2-8.9 0-17-3.1-23.5-8.1-6.5 5.1-14.6 8.1-23.5 8.1-2 0-4-.2-6-.5v73.4c0 6.3 5.7 12 12 12h54.8c6.3 0 10.9-5.7 10.9-12V185.1c6.9 2.2 14.1 3.5 22.8 3.5s17.1-1.3 22.8-3.5V215.8c0 6.3 5.7 12 12 12h54.8c6.3 0 10.9-5.7 10.9-12V142c-2.6.6-5.3.9-8 .9-8.4-.1-16.4-3-22.8-7.8zm-115.5 49.8c0 5-4.1 9.1-9.1 9.1h-14.1c-5 0-9.1-4.1-9.1-9.1v-14.1c0-5 4.1-9.1 9.1-9.1h14.1c5 0 9.1 4.1 9.1 9.1v14.1zm123.3 0c0 5-4.1 9.1-9.1 9.1h-14.1c-5 0-9.1-4.1-9.1-9.1v-14.1c0-5 4.1-9.1 9.1-9.1h14.1c5 0 9.1 4.1 9.1 9.1v14.1zM294.9 99.9l-30.1-30.1c-3.9-3.9-11.6-7.1-17.1-7.1H113.3c-5.5 0-13.2 3.2-17.1 7.1L66.1 99.9l-7 7c.9 12.5 10 22.7 21.9 25.3h.1c.4.1.9.2 1.3.3h.1c.4.1.8.1 1.3.2h.2c.4 0 .8.1 1.2.1H87c.7 0 1.4 0 2-.1.2 0 .4 0 .6-.1.5 0 .9-.1 1.4-.2.2 0 .5-.1.7-.1.4-.1.8-.1 1.2-.2.2-.1.5-.1.7-.2l1.2-.3c.2-.1.4-.1.6-.2.5-.2 1-.3 1.5-.5.1 0 .2-.1.4-.1.6-.2 1.2-.5 1.7-.8.2-.1.4-.2.5-.3.4-.2.8-.4 1.1-.6.2-.1.4-.3.7-.4l.9-.6c.1-.1.2-.1.3-.2 3.1-2.1 5.7-4.8 7.7-7.9 5 7.7 13.6 12.7 23.5 12.7s18.6-5.1 23.6-12.9c2 3.2 4.7 5.9 7.8 8.1.5.4 1.1.7 1.6 1 .1.1.2.1.3.2 1.1.6 2.2 1.2 3.4 1.6.1 0 .2.1.3.1l1.8.6c.6.2 1.2.3 1.8.5.1 0 .3.1.4.1.6.1 1.2.3 1.8.4.6.1 1.3.2 1.9.2h.5c.6 0 1.3.1 1.9.1s1.3 0 1.9-.1h.5c.6-.1 1.3-.1 1.9-.2.6-.1 1.2-.2 1.8-.4.1 0 .3-.1.4-.1.6-.1 1.2-.3 1.8-.5l1.8-.6c.1 0 .2-.1.3-.1 1.2-.5 2.3-1 3.4-1.6.1-.1.2-.1.3-.2.5-.3 1.1-.7 1.6-1 3.1-2.1 5.7-4.8 7.7-7.9 5 7.7 13.6 12.7 23.5 12.7 9.5 0 18-4.8 23-12.1 2 2.8 4.4 5.3 7.3 7.2.5.4 1.1.7 1.6 1 .1.1.2.1.3.2 1.1.6 2.2 1.2 3.4 1.6.1 0 .2.1.3.1l1.8.6c.6.2 1.2.3 1.8.5.1 0 .3.1.4.1.6.1 1.2.3 1.8.4.6.1 1.3.2 1.9.2h.5c.6 0 1.3.1 1.9.1.6 0 1.2 0 1.9-.1h.4c.6 0 1.2-.1 1.7-.2h.1c.6-.1 1.3-.2 1.9-.4h.1c11.9-2.7 20.9-12.8 21.8-25.3l-6.7-6.7zM150 107c-.1 8.8-7.3 15.9-16.1 15.9s-16-7.1-16.1-15.9l15-37h25l-7.8 37zm77.8 15.9c-8.8 0-16-7.1-16.1-15.9l-7.8-37h25l15 37c-.1 8.8-7.3 15.9-16.1 15.9z"/></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="266 396.70001220703125 25.5 23.5999755859375"><path fill="#FFF" d="M266 402.5c0-3.5 2.2-5.8 5.5-5.8s5.5 2.3 5.5 5.8-2.2 5.8-5.5 5.8-5.5-2.3-5.5-5.8zm2.9 17.3l8.5-11.8 6.2-10.7h5.2l-8.6 11.9-6 10.7h-5.3zm4.1-17.3c0-1.6-.5-2.5-1.5-2.5s-1.5.9-1.5 2.5c0 1.5.6 2.5 1.5 2.5.9-.1 1.5-1 1.5-2.5zm7.5 12c0-3.5 2.2-5.8 5.5-5.8s5.5 2.3 5.5 5.8-2.2 5.8-5.5 5.8-5.5-2.3-5.5-5.8zm7 0c0-1.6-.6-2.5-1.5-2.5s-1.5.9-1.5 2.5c0 1.5.6 2.5 1.5 2.5.9-.1 1.5-1 1.5-2.5z"/></svg>

After

Width:  |  Height:  |  Size: 504 B

BIN
assets/images/payfast.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -3,7 +3,6 @@
* Bangladeshi states (districts)
*
* @package WooCommerce/i18n
* @version 2.0.0
*/
global $states;
@ -11,68 +10,68 @@ global $states;
defined( 'ABSPATH' ) || exit;
$states['BD'] = array(
'BAG' => __( 'Bagerhat', 'woocommerce' ),
'BAN' => __( 'Bandarban', 'woocommerce' ),
'BAR' => __( 'Barguna', 'woocommerce' ),
'BARI' => __( 'Barisal', 'woocommerce' ),
'BHO' => __( 'Bhola', 'woocommerce' ),
'BOG' => __( 'Bogra', 'woocommerce' ),
'BRA' => __( 'Brahmanbaria', 'woocommerce' ),
'CHA' => __( 'Chandpur', 'woocommerce' ),
'CHI' => __( 'Chittagong', 'woocommerce' ),
'CHU' => __( 'Chuadanga', 'woocommerce' ),
'COM' => __( 'Comilla', 'woocommerce' ),
'COX' => __( "Cox's Bazar", 'woocommerce' ),
'DHA' => __( 'Dhaka', 'woocommerce' ),
'DIN' => __( 'Dinajpur', 'woocommerce' ),
'FAR' => __( 'Faridpur ', 'woocommerce' ),
'FEN' => __( 'Feni', 'woocommerce' ),
'GAI' => __( 'Gaibandha', 'woocommerce' ),
'GAZI' => __( 'Gazipur', 'woocommerce' ),
'GOP' => __( 'Gopalganj', 'woocommerce' ),
'HAB' => __( 'Habiganj', 'woocommerce' ),
'JAM' => __( 'Jamalpur', 'woocommerce' ),
'JES' => __( 'Jessore', 'woocommerce' ),
'JHA' => __( 'Jhalokati', 'woocommerce' ),
'JHE' => __( 'Jhenaidah', 'woocommerce' ),
'JOY' => __( 'Joypurhat', 'woocommerce' ),
'KHA' => __( 'Khagrachhari', 'woocommerce' ),
'KHU' => __( 'Khulna', 'woocommerce' ),
'KIS' => __( 'Kishoreganj', 'woocommerce' ),
'KUR' => __( 'Kurigram', 'woocommerce' ),
'KUS' => __( 'Kushtia', 'woocommerce' ),
'LAK' => __( 'Lakshmipur', 'woocommerce' ),
'LAL' => __( 'Lalmonirhat', 'woocommerce' ),
'MAD' => __( 'Madaripur', 'woocommerce' ),
'MAG' => __( 'Magura', 'woocommerce' ),
'MAN' => __( 'Manikganj ', 'woocommerce' ),
'MEH' => __( 'Meherpur', 'woocommerce' ),
'MOU' => __( 'Moulvibazar', 'woocommerce' ),
'MUN' => __( 'Munshiganj', 'woocommerce' ),
'MYM' => __( 'Mymensingh', 'woocommerce' ),
'NAO' => __( 'Naogaon', 'woocommerce' ),
'NAR' => __( 'Narail', 'woocommerce' ),
'NARG' => __( 'Narayanganj', 'woocommerce' ),
'NARD' => __( 'Narsingdi', 'woocommerce' ),
'NAT' => __( 'Natore', 'woocommerce' ),
'NAW' => __( 'Nawabganj', 'woocommerce' ),
'NET' => __( 'Netrakona', 'woocommerce' ),
'NIL' => __( 'Nilphamari', 'woocommerce' ),
'NOA' => __( 'Noakhali', 'woocommerce' ),
'PAB' => __( 'Pabna', 'woocommerce' ),
'PAN' => __( 'Panchagarh', 'woocommerce' ),
'PAT' => __( 'Patuakhali', 'woocommerce' ),
'PIR' => __( 'Pirojpur', 'woocommerce' ),
'RAJB' => __( 'Rajbari', 'woocommerce' ),
'RAJ' => __( 'Rajshahi', 'woocommerce' ),
'RAN' => __( 'Rangamati', 'woocommerce' ),
'RANP' => __( 'Rangpur', 'woocommerce' ),
'SAT' => __( 'Satkhira', 'woocommerce' ),
'SHA' => __( 'Shariatpur', 'woocommerce' ),
'SHE' => __( 'Sherpur', 'woocommerce' ),
'SIR' => __( 'Sirajganj', 'woocommerce' ),
'SUN' => __( 'Sunamganj', 'woocommerce' ),
'SYL' => __( 'Sylhet', 'woocommerce' ),
'TAN' => __( 'Tangail', 'woocommerce' ),
'THA' => __( 'Thakurgaon', 'woocommerce' ),
'BD-05' => __( 'Bagerhat', 'woocommerce' ),
'BD-01' => __( 'Bandarban', 'woocommerce' ),
'BD-02' => __( 'Barguna', 'woocommerce' ),
'BD-06' => __( 'Barishal', 'woocommerce' ),
'BD-07' => __( 'Bhola', 'woocommerce' ),
'BD-03' => __( 'Bogura', 'woocommerce' ),
'BD-04' => __( 'Brahmanbaria', 'woocommerce' ),
'BD-09' => __( 'Chandpur', 'woocommerce' ),
'BD-10' => __( 'Chattogram', 'woocommerce' ),
'BD-12' => __( 'Chuadanga', 'woocommerce' ),
'BD-11' => __( "Cox's Bazar", 'woocommerce' ),
'BD-08' => __( 'Cumilla', 'woocommerce' ),
'BD-13' => __( 'Dhaka', 'woocommerce' ),
'BD-14' => __( 'Dinajpur', 'woocommerce' ),
'BD-15' => __( 'Faridpur ', 'woocommerce' ),
'BD-16' => __( 'Feni', 'woocommerce' ),
'BD-19' => __( 'Gaibandha', 'woocommerce' ),
'BD-18' => __( 'Gazipur', 'woocommerce' ),
'BD-17' => __( 'Gopalganj', 'woocommerce' ),
'BD-20' => __( 'Habiganj', 'woocommerce' ),
'BD-21' => __( 'Jamalpur', 'woocommerce' ),
'BD-22' => __( 'Jashore', 'woocommerce' ),
'BD-25' => __( 'Jhalokati', 'woocommerce' ),
'BD-23' => __( 'Jhenaidah', 'woocommerce' ),
'BD-24' => __( 'Joypurhat', 'woocommerce' ),
'BD-29' => __( 'Khagrachhari', 'woocommerce' ),
'BD-27' => __( 'Khulna', 'woocommerce' ),
'BD-26' => __( 'Kishoreganj', 'woocommerce' ),
'BD-28' => __( 'Kurigram', 'woocommerce' ),
'BD-30' => __( 'Kushtia', 'woocommerce' ),
'BD-31' => __( 'Lakshmipur', 'woocommerce' ),
'BD-32' => __( 'Lalmonirhat', 'woocommerce' ),
'BD-36' => __( 'Madaripur', 'woocommerce' ),
'BD-37' => __( 'Magura', 'woocommerce' ),
'BD-33' => __( 'Manikganj ', 'woocommerce' ),
'BD-39' => __( 'Meherpur', 'woocommerce' ),
'BD-38' => __( 'Moulvibazar', 'woocommerce' ),
'BD-35' => __( 'Munshiganj', 'woocommerce' ),
'BD-34' => __( 'Mymensingh', 'woocommerce' ),
'BD-48' => __( 'Naogaon', 'woocommerce' ),
'BD-43' => __( 'Narail', 'woocommerce' ),
'BD-40' => __( 'Narayanganj', 'woocommerce' ),
'BD-42' => __( 'Narsingdi', 'woocommerce' ),
'BD-44' => __( 'Natore', 'woocommerce' ),
'BD-45' => __( 'Nawabganj', 'woocommerce' ),
'BD-41' => __( 'Netrakona', 'woocommerce' ),
'BD-46' => __( 'Nilphamari', 'woocommerce' ),
'BD-47' => __( 'Noakhali', 'woocommerce' ),
'BD-49' => __( 'Pabna', 'woocommerce' ),
'BD-52' => __( 'Panchagarh', 'woocommerce' ),
'BD-51' => __( 'Patuakhali', 'woocommerce' ),
'BD-50' => __( 'Pirojpur', 'woocommerce' ),
'BD-53' => __( 'Rajbari', 'woocommerce' ),
'BD-54' => __( 'Rajshahi', 'woocommerce' ),
'BD-56' => __( 'Rangamati', 'woocommerce' ),
'BD-55' => __( 'Rangpur', 'woocommerce' ),
'BD-58' => __( 'Satkhira', 'woocommerce' ),
'BD-62' => __( 'Shariatpur', 'woocommerce' ),
'BD-57' => __( 'Sherpur', 'woocommerce' ),
'BD-59' => __( 'Sirajganj', 'woocommerce' ),
'BD-61' => __( 'Sunamganj', 'woocommerce' ),
'BD-60' => __( 'Sylhet', 'woocommerce' ),
'BD-63' => __( 'Tangail', 'woocommerce' ),
'BD-64' => __( 'Thakurgaon', 'woocommerce' ),
);

View File

@ -31,13 +31,13 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
public function admin_styles() {
global $wp_scripts;
$screen = get_current_screen();
$screen_id = $screen ? $screen->id : '';
$screen = get_current_screen();
$screen_id = $screen ? $screen->id: '';
// Register admin styles.
wp_register_style( 'woocommerce_admin_menu_styles', WC()->plugin_url() . '/assets/css/menu.css', array(), WC_VERSION );
wp_register_style( 'woocommerce_admin_styles', WC()->plugin_url() . '/assets/css/admin.css', array(), WC_VERSION );
wp_register_style( 'jquery-ui-style', WC()->plugin_url() . '/assets/css/jquery-ui.min.css', array(), WC_VERSION );
wp_register_style( 'jquery-ui-style', WC()->plugin_url() . '/assets/css/jquery-ui/jquery-ui.min.css', array(), WC_VERSION );
wp_register_style( 'woocommerce_admin_dashboard_styles', WC()->plugin_url() . '/assets/css/dashboard.css', array(), WC_VERSION );
wp_register_style( 'woocommerce_admin_print_reports_styles', WC()->plugin_url() . '/assets/css/reports-print.css', array(), WC_VERSION, 'print' );

View File

@ -71,7 +71,7 @@ class WC_Admin_Setup_Wizard {
*
* @return boolean
*/
protected function should_show_theme_extra() {
protected function should_show_theme() {
$support_woocommerce = current_theme_supports( 'woocommerce' ) && ! $this->is_default_theme();
return (
@ -106,7 +106,7 @@ class WC_Admin_Setup_Wizard {
* The "automated tax" extra should only be shown if the current user can
* install plugins and the store is in a supported country.
*/
protected function should_show_automated_tax_extra() {
protected function should_show_automated_tax() {
if ( ! current_user_can( 'install_plugins' ) ) {
return false;
}
@ -121,6 +121,28 @@ class WC_Admin_Setup_Wizard {
return in_array( $country_code, $tax_supported_countries, true );
}
/**
* Should we show the MailChimp install option?
* True only if the user can install plugins.
*
* @return boolean
*/
protected function should_show_mailchimp() {
return current_user_can( 'install_plugins' );
}
/**
* Should we display the 'Recommended' step?
* True if at least one of the recommendations will be displayed.
*
* @return boolean
*/
protected function should_show_recommended_step() {
return $this->should_show_theme()
|| $this->should_show_automated_tax()
|| $this->should_show_mailchimp();
}
/**
* Show the setup wizard.
*/
@ -144,10 +166,10 @@ class WC_Admin_Setup_Wizard {
'view' => array( $this, 'wc_setup_shipping' ),
'handler' => array( $this, 'wc_setup_shipping_save' ),
),
'extras' => array(
'name' => __( 'Extras', 'woocommerce' ),
'view' => array( $this, 'wc_setup_extras' ),
'handler' => array( $this, 'wc_setup_extras_save' ),
'recommended' => array(
'name' => __( 'Recommended', 'woocommerce' ),
'view' => array( $this, 'wc_setup_recommended' ),
'handler' => array( $this, 'wc_setup_recommended_save' ),
),
'activate' => array(
'name' => __( 'Activate', 'woocommerce' ),
@ -161,9 +183,9 @@ class WC_Admin_Setup_Wizard {
),
);
// Hide the extras step if this store/user isn't eligible for them.
if ( ! $this->should_show_theme_extra() && ! $this->should_show_automated_tax_extra() ) {
unset( $default_steps['extras'] );
// Hide recommended step if nothing is going to be shown there.
if ( ! $this->should_show_recommended_step() ) {
unset( $default_steps['recommended'] );
}
// Hide shipping step if the store is selling digital products only.
@ -281,7 +303,7 @@ class WC_Admin_Setup_Wizard {
?>
<?php if ( 'store_setup' === $this->step ) : ?>
<a class="wc-setup-footer-links" href="<?php echo esc_url( admin_url() ); ?>"><?php esc_html_e( 'Not right now', 'woocommerce' ); ?></a>
<?php elseif ( 'activate' === $this->step ) : ?>
<?php elseif ( 'recommended' === $this->step || 'activate' === $this->step ) : ?>
<a class="wc-setup-footer-links" href="<?php echo esc_url( $this->get_next_step_link() ); ?>"><?php esc_html_e( 'Skip this step', 'woocommerce' ); ?></a>
<?php endif; ?>
</body>
@ -383,18 +405,17 @@ class WC_Admin_Setup_Wizard {
<label class="location-prompt" for="store_address_2"><?php esc_html_e( 'Address line 2', 'woocommerce' ); ?></label>
<input type="text" id="store_address_2" class="location-input" name="store_address_2" value="<?php echo esc_attr( $address_2 ); ?>" />
<div class="store-state-container hidden">
<label for="store_state" class="location-prompt">
<?php esc_html_e( 'State', 'woocommerce' ); ?>
</label>
<select id="store_state" name="store_state" data-placeholder="<?php esc_attr_e( 'Choose a state&hellip;', 'woocommerce' ); ?>" aria-label="<?php esc_attr_e( 'State', 'woocommerce' ); ?>" class="location-input wc-enhanced-select dropdown"></select>
</div>
<div class="city-and-postcode">
<div>
<label class="location-prompt" for="store_city"><?php esc_html_e( 'City', 'woocommerce' ); ?></label>
<input type="text" id="store_city" class="location-input" name="store_city" required value="<?php echo esc_attr( $city ); ?>" />
</div>
<div class="store-state-container hidden">
<label for="store_state" class="location-prompt">
<?php esc_html_e( 'State', 'woocommerce' ); ?>
</label>
<select id="store_state" name="store_state" data-placeholder="<?php esc_attr_e( 'Choose a state&hellip;', 'woocommerce' ); ?>" aria-label="<?php esc_attr_e( 'State', 'woocommerce' ); ?>" class="location-input wc-enhanced-select dropdown"></select>
</div>
<div>
<label class="location-prompt" for="store_postcode"><?php esc_html_e( 'Postcode / ZIP', 'woocommerce' ); ?></label>
<input type="text" id="store_postcode" class="location-input" name="store_postcode" required value="<?php echo esc_attr( $postcode ); ?>" />
@ -516,15 +537,17 @@ class WC_Admin_Setup_Wizard {
$locale_info = include WC()->plugin_path() . '/i18n/locale-info.php';
// Set currency formatting options based on chosen location and currency.
if (
isset( $locale_info[ $country ] ) &&
$locale_info[ $country ]['currency_code'] === $currency_code
) {
update_option( 'woocommerce_currency_pos', $locale_info[ $country ]['currency_pos'] );
update_option( 'woocommerce_price_decimal_sep', $locale_info[ $country ]['decimal_sep'] );
update_option( 'woocommerce_price_num_decimals', $locale_info[ $country ]['num_decimals'] );
update_option( 'woocommerce_price_thousand_sep', $locale_info[ $country ]['thousand_sep'] );
if ( isset( $locale_info[ $country ] ) ) {
update_option( 'woocommerce_weight_unit', $locale_info[ $country ]['weight_unit'] );
update_option( 'woocommerce_dimension_unit', $locale_info[ $country ]['dimension_unit'] );
// Set currency formatting options based on chosen location and currency.
if ( $locale_info[ $country ]['currency_code'] === $currency_code ) {
update_option( 'woocommerce_currency_pos', $locale_info[ $country ]['currency_pos'] );
update_option( 'woocommerce_price_decimal_sep', $locale_info[ $country ]['decimal_sep'] );
update_option( 'woocommerce_price_num_decimals', $locale_info[ $country ]['num_decimals'] );
update_option( 'woocommerce_price_thousand_sep', $locale_info[ $country ]['thousand_sep'] );
}
}
if ( $tracking ) {
@ -593,7 +616,7 @@ class WC_Admin_Setup_Wizard {
* Helper method to queue the background install of a plugin.
*
* @param string $plugin_id Plugin id used for background install.
* @param array $plugin_info Plugin info array containing at least main file and repo slug.
* @param array $plugin_info Plugin info array containing name and repo-slug, and optionally file if different from [repo-slug].php.
*/
protected function install_plugin( $plugin_id, $plugin_info ) {
// Make sure we don't trigger multiple simultaneous installs.
@ -601,7 +624,8 @@ class WC_Admin_Setup_Wizard {
return;
}
if ( ! empty( $plugin_info['file'] ) && is_plugin_active( $plugin_info['file'] ) ) {
$plugin_file = isset( $plugin_info['file'] ) ? $plugin_info['file'] : $plugin_info['repo-slug'] . '.php';
if ( is_plugin_active( $plugin_info['repo-slug'] . '/' . $plugin_file ) ) {
return;
}
@ -647,7 +671,6 @@ class WC_Admin_Setup_Wizard {
$this->install_plugin(
'jetpack',
array(
'file' => 'jetpack/jetpack.php',
'name' => __( 'Jetpack', 'woocommerce' ),
'repo-slug' => 'jetpack',
)
@ -662,7 +685,6 @@ class WC_Admin_Setup_Wizard {
$this->install_plugin(
'woocommerce-services',
array(
'file' => 'woocommerce-services/woocommerce-services.php',
'name' => __( 'WooCommerce Services', 'woocommerce' ),
'repo-slug' => 'woocommerce-services',
)
@ -798,19 +820,6 @@ class WC_Admin_Setup_Wizard {
$existing_zones = WC_Shipping_Zones::get_zones();
$dimension_unit = get_option( 'woocommerce_dimension_unit' );
$weight_unit = get_option( 'woocommerce_weight_unit' );
$locale_info = include WC()->plugin_path() . '/i18n/locale-info.php';
if ( ! $weight_unit && isset( $locale_info[ $country_code ] ) ) {
$weight_unit = $locale_info[ $country_code ]['weight_unit'];
} else {
$weight_unit = $weight_unit ? $weight_unit : 'kg';
}
if ( ! $dimension_unit && isset( $locale_info[ $country_code ] ) ) {
$dimension_unit = $locale_info[ $country_code ]['dimension_unit'];
} else {
$dimension_unit = $dimension_unit ? $dimension_unit : 'cm';
}
if ( ! empty( $existing_zones ) ) {
$intro_text = __( 'How would you like units on your store displayed?', 'woocommerce' );
@ -1111,6 +1120,19 @@ class WC_Admin_Setup_Wizard {
return in_array( $country_code, $square_supported_countries, true );
}
/**
* Is eWAY Payments country supported
*
* @param string $country_code Country code.
*/
protected function is_eway_payments_supported_country( $country_code ) {
$supported_countries = array(
'AU', // Australia.
'NZ', // New Zealand.
);
return in_array( $country_code, $supported_countries, true );
}
/**
* Helper method to retrieve the current user's email address.
*
@ -1245,6 +1267,23 @@ class WC_Admin_Setup_Wizard {
'enabled' => true,
'repo-slug' => 'woocommerce-square',
),
'eway' => array(
'name' => __( 'eWAY', 'woocommerce' ),
'description' => __( 'The eWAY extension for WooCommerce allows you to take credit card payments directly on your store without redirecting your customers to a third party site to make payment.', 'woocommerce' ),
'image' => WC()->plugin_url() . '/assets/images/eway-logo.jpg',
'enabled' => false,
'class' => 'eway-logo',
'repo-slug' => 'woocommerce-gateway-eway',
),
'payfast' => array(
'name' => __( 'PayFast', 'woocommerce' ),
'description' => __( 'The PayFast extension for WooCommerce enables you to accept payments by Credit Card and EFT via one of South Africas most popular payment gateways. No setup fees or monthly subscription costs.', 'woocommerce' ),
'image' => WC()->plugin_url() . '/assets/images/payfast.png',
'class' => 'payfast-logo',
'enabled' => false,
'repo-slug' => 'woocommerce-gateway-payfast',
'file' => 'gateway-payfast.php',
),
);
}
@ -1260,8 +1299,10 @@ class WC_Admin_Setup_Wizard {
return array( 'paypal' => $gateways['paypal'] );
}
$country = WC()->countries->get_base_country();
$can_stripe = $this->is_stripe_supported_country( $country );
$country = WC()->countries->get_base_country();
$can_stripe = $this->is_stripe_supported_country( $country );
$can_eway = $this->is_eway_payments_supported_country( $country );
$can_payfast = ( 'ZA' === $country ); // South Africa.
if ( $this->is_klarna_checkout_supported_country( $country ) ) {
$spotlight = 'klarna_checkout';
@ -1276,19 +1317,40 @@ class WC_Admin_Setup_Wizard {
$spotlight => $gateways[ $spotlight ],
'ppec_paypal' => $gateways['ppec_paypal'],
);
if ( $can_stripe ) {
$offered_gateways += array( 'stripe' => $gateways['stripe'] );
}
if ( $can_eway ) {
$offered_gateways += array( 'eway' => $gateways['eway'] );
}
if ( $can_payfast ) {
$offered_gateways += array( 'payfast' => $gateways['payfast'] );
}
return $offered_gateways;
}
$offered_gateways = array();
if ( $can_stripe ) {
$gateways['stripe']['enabled'] = true;
$gateways['stripe']['featured'] = true;
$offered_gateways += array( 'stripe' => $gateways['stripe'] );
}
$offered_gateways += array( 'ppec_paypal' => $gateways['ppec_paypal'] );
if ( $can_eway ) {
$offered_gateways += array( 'eway' => $gateways['eway'] );
}
if ( $can_payfast ) {
$offered_gateways += array( 'payfast' => $gateways['payfast'] );
}
return $offered_gateways;
}
@ -1559,74 +1621,100 @@ class WC_Admin_Setup_Wizard {
exit;
}
/**
* Extras.
*/
public function wc_setup_extras() {
protected function display_recommended_item( $item_info ) {
$type = $item_info['type'];
$title = $item_info['title'];
$description = $item_info['description'];
$img_url = $item_info['img_url'];
$img_alt = $item_info['img_alt'];
?>
<h1><?php esc_html_e( 'Recommended Extras', 'woocommerce' ); ?></h1>
<li>
<label class="recommended-item">
<input
class="recommended-item-checkbox"
type="checkbox"
name="<?php echo esc_attr( 'setup_' . $type ); ?>"
value="yes"
checked />
<img
src="<?php echo esc_url( $img_url ); ?>"
class="<?php echo esc_attr( 'recommended-item-icon-' . $type ); ?> recommended-item-icon"
alt="<?php echo esc_attr( $img_alt ); ?>" />
<div class="recommended-item-description-container">
<h3><?php echo esc_html( $title ); ?></h3>
<p><?php echo wp_kses( $description, array(
'a' => array(
'href' => array(),
'target' => array(),
'rel' => array(),
),
'em' => array(),
) ); ?></p>
</div>
</label>
</li>
<?php
}
/**
* Recommended step
*/
public function wc_setup_recommended() {
?>
<h1><?php esc_html_e( 'Recommended for All WooCommerce Stores', 'woocommerce' ); ?></h1>
<p><?php
// If we're displaying all of the recommended features, show the full description. Otherwise, display a placeholder.
// We're not translating all of the different permutations to save on translations,
// and the default is the most common.
if (
$this->should_show_theme()
&& $this->should_show_automated_tax()
&& $this->should_show_mailchimp()
) :
esc_html_e( 'Select from the list below to enable automated taxes and MailChimps best-in-class email services — and design your store with our official, free WooCommerce theme.', 'woocommerce' );
else :
esc_html_e( 'Enhance your store with these recommended features.', 'woocommerce' );
endif;
?></p>
<form method="post">
<?php if ( $this->should_show_theme_extra() ) : ?>
<ul class="wc-wizard-services featured">
<li class="wc-wizard-service-item">
<div class="wc-wizard-service-description">
<h3><?php esc_html_e( 'Storefront Theme', 'woocommerce' ); ?></h3>
<p>
<?php
$theme = wp_get_theme();
$theme_name = $theme['Name'];
<ul class="recommended-step">
<?php
if ( $this->should_show_theme() ) :
$theme = wp_get_theme();
$theme_name = $theme['Name'];
$this->display_recommended_item( array(
'type' => 'storefront_theme',
'title' => __( 'Storefront Theme', 'woocommerce' ),
'description' => sprintf( __(
'Design your store with deep WooCommerce integration. If toggled on, well install <a href="https://woocommerce.com/storefront/" target="_blank" rel="noopener noreferrer">Storefront</a>, and your current theme <em>%s</em> will be deactivated.', 'woocommerce' ),
$theme_name
),
'img_url' => WC()->plugin_url() . '/assets/images/obw-storefront-icon.svg',
'img_alt' => __( 'Storefront icon', 'woocommerce' ),
) );
endif;
if ( $this->is_default_theme() ) {
echo wp_kses_post( sprintf( __( 'The theme you are currently using is not optimized for WooCommerce. We recommend you switch to <a href="%s" title="Learn more about Storefront" target="_blank">Storefront</a>; our official, free, WooCommerce theme.', 'woocommerce' ), esc_url( 'https://woocommerce.com/storefront/' ) ) );
} else {
echo wp_kses_post( sprintf( __( 'The theme you are currently using does not fully support WooCommerce. We recommend you switch to <a href="%s" title="Learn more about Storefront" target="_blank">Storefront</a>; our official, free, WooCommerce theme.', 'woocommerce' ), esc_url( 'https://woocommerce.com/storefront/' ) ) );
}
?>
</p>
<p>
<?php echo wp_kses_post( sprintf( __( 'If toggled on, Storefront will be installed for you, and <em>%s</em> theme will be deactivated.', 'woocommerce' ), esc_html( $theme_name ) ) ); ?>
</p>
</div>
if ( $this->should_show_automated_tax() ) :
$this->display_recommended_item( array(
'type' => 'automated_taxes',
'title' => __( 'Automated Taxes', 'woocommerce' ),
'description' => __( 'Save time and errors with automated tax calculation and collection at checkout. Powered by WooCommerce Services and Jetpack.', 'woocommerce' ),
'img_url' => WC()->plugin_url() . '/assets/images/obw-taxes-icon.svg',
'img_alt' => __( 'automated taxes icon', 'woocommerce' ),
) );
endif;
<div class="wc-wizard-service-enable">
<span class="wc-wizard-service-toggle">
<input id="setup_storefront_theme" type="checkbox" name="setup_storefront_theme" value="yes" checked="checked" />
<label for="setup_storefront_theme">
</span>
</div>
</li>
</ul>
<?php endif; ?>
<?php if ( $this->should_show_automated_tax_extra() ) : ?>
<ul class="wc-wizard-services featured">
<li class="wc-wizard-service-item <?php echo get_option( 'woocommerce_setup_automated_taxes' ) ? 'checked' : ''; ?>">
<div class="wc-wizard-service-description">
<h3><?php esc_html_e( 'Automated Taxes (powered by WooCommerce Services)', 'woocommerce' ); ?></h3>
<p>
<?php esc_html_e( 'Automatically calculate and charge the correct rate of tax for each time a customer checks out. If toggled on, WooCommerce Services and Jetpack will be installed and activated for you.', 'woocommerce' ); ?>
</p>
<p class="wc-wizard-service-learn-more">
<a href="<?php echo esc_url( 'https://wordpress.org/plugins/woocommerce-services/' ); ?>" target="_blank">
<?php esc_html_e( 'Learn more about WooCommerce Services', 'woocommerce' ); ?>
</a>
</p>
</div>
<div class="wc-wizard-service-enable">
<span class="wc-wizard-service-toggle <?php echo get_option( 'woocommerce_setup_automated_taxes' ) ? '' : 'disabled'; ?>">
<input
id="setup_automated_taxes"
type="checkbox"
name="setup_automated_taxes"
value="yes"
<?php checked( get_option( 'woocommerce_setup_automated_taxes', 'no' ), 'yes' ); ?>
/>
<label for="setup_automated_taxes">
</span>
</div>
</li>
</ul>
<?php endif; ?>
if ( $this->should_show_mailchimp() ) :
$this->display_recommended_item( array(
'type' => 'mailchimp',
'title' => __( 'MailChimp', 'woocommerce' ),
'description' => __( 'Join the 16 million customers who use MailChimp. Sync list and store data to send automated emails, and targeted campaigns.', 'woocommerce' ),
'img_url' => WC()->plugin_url() . '/assets/images/obw-mailchimp-icon.svg',
'img_alt' => __( 'MailChimp icon', 'woocommerce' ),
) );
endif;
?>
</ul>
<p class="wc-setup-actions step">
<button type="submit" class="button-primary button button-large button-next" value="<?php esc_attr_e( 'Continue', 'woocommerce' ); ?>" name="save_step"><?php esc_html_e( 'Continue', 'woocommerce' ); ?></button>
<?php wp_nonce_field( 'wc-setup' ); ?>
@ -1636,23 +1724,38 @@ class WC_Admin_Setup_Wizard {
}
/**
* Extras step save.
* Recommended step save.
*/
public function wc_setup_extras_save() {
public function wc_setup_recommended_save() {
check_admin_referer( 'wc-setup' );
$setup_automated_tax = isset( $_POST['setup_automated_taxes'] ) && 'yes' === $_POST['setup_automated_taxes'];
$install_storefront = isset( $_POST['setup_storefront_theme'] ) && 'yes' === $_POST['setup_storefront_theme'];
$setup_storefront = isset( $_POST['setup_storefront_theme'] ) && 'yes' === $_POST['setup_storefront_theme'];
$setup_automated_tax = isset( $_POST['setup_automated_taxes'] ) && 'yes' === $_POST['setup_automated_taxes'];
$setup_mailchimp = isset( $_POST['setup_mailchimp'] ) && 'yes' === $_POST['setup_mailchimp'];
update_option( 'woocommerce_calc_taxes', $setup_automated_tax ? 'yes' : 'no' );
update_option( 'woocommerce_setup_automated_taxes', $setup_automated_tax );
if ( $setup_storefront ) {
$this->install_theme( 'storefront' );
}
if ( $setup_automated_tax ) {
$this->install_woocommerce_services();
}
if ( $install_storefront ) {
$this->install_theme( 'storefront' );
if ( $setup_mailchimp ) {
// Prevent MailChimp from redirecting to its settings page during the OBW flow.
add_option( 'mailchimp_woocommerce_plugin_do_activation_redirect', false );
$this->install_plugin(
'mailchimp-for-woocommerce',
array(
'name' => __( 'MailChimp for WooCommerce', 'woocommerce' ),
'repo-slug' => 'mailchimp-for-woocommerce',
'file' => 'mailchimp-woocommerce.php',
)
);
}
wp_redirect( esc_url_raw( $this->get_next_step_link() ) );
@ -1891,7 +1994,6 @@ class WC_Admin_Setup_Wizard {
}
WC_Install::background_installer( 'jetpack', array(
'file' => 'jetpack/jetpack.php',
'name' => __( 'Jetpack', 'woocommerce' ),
'repo-slug' => 'jetpack',
) );

View File

@ -135,9 +135,10 @@ class WC_Admin_List_Table_Orders extends WC_Admin_List_Table {
unset( $actions['edit'] );
}
$actions['mark_processing'] = __( 'Change status to processing', 'woocommerce' );
$actions['mark_on-hold'] = __( 'Change status to on-hold', 'woocommerce' );
$actions['mark_completed'] = __( 'Change status to completed', 'woocommerce' );
$actions['mark_processing'] = __( 'Change status to processing', 'woocommerce' );
$actions['mark_on-hold'] = __( 'Change status to on-hold', 'woocommerce' );
$actions['mark_completed'] = __( 'Change status to completed', 'woocommerce' );
$actions['remove_personal_data'] = __( 'Remove personal data', 'woocommerce' );
return $actions;
}
@ -622,39 +623,46 @@ class WC_Admin_List_Table_Orders extends WC_Admin_List_Table {
* @return string
*/
public function handle_bulk_actions( $redirect_to, $action, $ids ) {
// Bail out if this is not a status-changing action.
if ( false === strpos( $action, 'mark_' ) ) {
return $redirect_to;
}
$order_statuses = wc_get_order_statuses();
$new_status = substr( $action, 5 ); // Get the status name from action.
$report_action = 'marked_' . $new_status;
// Sanity check: bail out if this is actually not a status, or is
// not a registered status.
if ( ! isset( $order_statuses[ 'wc-' . $new_status ] ) ) {
return $redirect_to;
}
$changed = 0;
$ids = array_map( 'absint', $ids );
$changed = 0;
foreach ( $ids as $id ) {
$order = wc_get_order( $id );
$order->update_status( $new_status, __( 'Order status changed by bulk edit:', 'woocommerce' ), true );
do_action( 'woocommerce_order_edit_status', $id, $new_status );
$changed++;
if ( 'remove_personal_data' === $action ) {
$report_action = 'removed_personal_data';
foreach ( $ids as $id ) {
$order = wc_get_order( $id );
if ( $order ) {
do_action( 'woocommerce_remove_order_personal_data', $order );
$changed++;
}
}
} elseif ( false !== strpos( $action, 'mark_' ) ) {
$order_statuses = wc_get_order_statuses();
$new_status = substr( $action, 5 ); // Get the status name from action.
$report_action = 'marked_' . $new_status;
// Sanity check: bail out if this is actually not a status, or is not a registered status.
if ( isset( $order_statuses[ 'wc-' . $new_status ] ) ) {
foreach ( $ids as $id ) {
$order = wc_get_order( $id );
$order->update_status( $new_status, __( 'Order status changed by bulk edit:', 'woocommerce' ), true );
do_action( 'woocommerce_order_edit_status', $id, $new_status );
$changed++;
}
}
}
$redirect_to = add_query_arg(
array(
'post_type' => $this->list_table_type,
$report_action => true,
'changed' => $changed,
'ids' => join( ',', $ids ),
), $redirect_to
);
if ( $changed ) {
$redirect_to = add_query_arg(
array(
'post_type' => $this->list_table_type,
'bulk_action' => $report_action,
'changed' => $changed,
'ids' => join( ',', $ids ),
), $redirect_to
);
}
return esc_url_raw( $redirect_to );
}
@ -666,23 +674,29 @@ class WC_Admin_List_Table_Orders extends WC_Admin_List_Table {
global $post_type, $pagenow;
// Bail out if not on shop order list page.
if ( 'edit.php' !== $pagenow || 'shop_order' !== $post_type ) {
if ( 'edit.php' !== $pagenow || 'shop_order' !== $post_type || ! isset( $_REQUEST['bulk_action'] ) ) { // WPCS: input var ok, CSRF ok.
return;
}
$order_statuses = wc_get_order_statuses();
$number = isset( $_REQUEST['changed'] ) ? absint( $_REQUEST['changed'] ) : 0; // WPCS: input var ok, CSRF ok.
$bulk_action = wc_clean( wp_unslash( $_REQUEST['bulk_action'] ) ); // WPCS: input var ok, CSRF ok.
// Check if any status changes happened.
foreach ( $order_statuses as $slug => $name ) {
if ( isset( $_REQUEST[ 'marked_' . str_replace( 'wc-', '', $slug ) ] ) ) { // WPCS: input var ok.
$number = isset( $_REQUEST['changed'] ) ? absint( $_REQUEST['changed'] ) : 0; // WPCS: input var ok.
/* translators: %s: orders count */
if ( 'marked_' . str_replace( 'wc-', '', $slug ) === $bulk_action ) { // WPCS: input var ok, CSRF ok.
/* translators: %d: orders count */
$message = sprintf( _n( '%d order status changed.', '%d order statuses changed.', $number, 'woocommerce' ), number_format_i18n( $number ) );
echo '<div class="updated"><p>' . esc_html( $message ) . '</p></div>';
break;
}
}
if ( 'removed_personal_data' === $bulk_action ) { // WPCS: input var ok, CSRF ok.
/* translators: %d: orders count */
$message = sprintf( _n( 'Removed personal data from %d order.', 'Removed personal data from %d orders.', $number, 'woocommerce' ), number_format_i18n( $number ) );
echo '<div class="updated"><p>' . esc_html( $message ) . '</p></div>';
}
}
/**

View File

@ -344,7 +344,9 @@ class WC_Meta_Box_Order_Data {
$field_value = make_clickable( esc_html( $field_value ) );
}
echo '<p><strong>' . esc_html( $field['label'] ) . ':</strong> ' . wp_kses_post( $field_value ) . '</p>';
if ( $field_value ) {
echo '<p><strong>' . esc_html( $field['label'] ) . ':</strong> ' . wp_kses_post( $field_value ) . '</p>';
}
}
?>
</div>
@ -450,7 +452,9 @@ class WC_Meta_Box_Order_Data {
$field_value = $order->get_meta( '_' . $field_name );
}
echo '<p><strong>' . esc_html( $field['label'] ) . ':</strong> ' . make_clickable( esc_html( $field_value ) ) . '</p>';
if ( $field_value ) {
echo '<p><strong>' . esc_html( $field['label'] ) . ':</strong> ' . wp_kses_post( $field_value ) . '</p>';
}
}
}
@ -601,6 +605,11 @@ class WC_Meta_Box_Order_Data {
$props['date_created'] = $date;
// Set created via prop if new post.
if ( isset( $_POST['original_post_status'] ) && $_POST['original_post_status'] === 'auto-draft' ) {
$props['created_via'] = 'admin';
}
// Save order data.
$order->set_props( $props );
$order->set_status( wc_clean( $_POST['order_status'] ), '', true );

View File

@ -100,7 +100,7 @@ class WC_Install {
'wc_update_330_db_version',
),
'3.4.0' => array(
'wc_update_340_irish_states',
'wc_update_340_states',
'wc_update_340_db_version',
),
);
@ -529,6 +529,9 @@ class WC_Install {
// used by WC_Comments::wp_count_comments() to get the number of comments by type.
$wpdb->query( "ALTER TABLE {$wpdb->comments} ADD INDEX woo_idx_comment_type (comment_type)" );
}
// Add constraint to download logs.
$wpdb->query( "ALTER TABLE {$wpdb->prefix}wc_download_log ADD FOREIGN KEY (permission_id) REFERENCES {$wpdb->prefix}woocommerce_downloadable_product_permissions(permission_id) ON DELETE CASCADE" );
}
/**
@ -1060,13 +1063,12 @@ CREATE TABLE {$wpdb->prefix}woocommerce_termmeta (
/**
* Get slug from path and associate it with the path.
*
* @param array $plugins Associative array of plugin slugs to paths.
* @param array $plugins Associative array of plugin files to paths.
* @param string $key Plugin relative path. Example: woocommerce/woocommerce.php.
*/
private static function associate_plugin_slug( $plugins, $key ) {
$slug = explode( '/', $key );
$slug = explode( '.', end( $slug ) );
$plugins[ $slug[0] ] = $key;
private static function associate_plugin_file( $plugins, $key ) {
$path = explode( '/', $key );
$plugins[ $path[1] ] = $key;
return $plugins;
}
@ -1093,15 +1095,16 @@ CREATE TABLE {$wpdb->prefix}woocommerce_termmeta (
$skin = new Automatic_Upgrader_Skin();
$upgrader = new WP_Upgrader( $skin );
$installed_plugins = array_reduce( array_keys( get_plugins() ), array( __CLASS__, 'associate_plugin_slug' ), array() );
$installed_plugins = array_reduce( array_keys( get_plugins() ), array( __CLASS__, 'associate_plugin_file' ), array() );
$plugin_slug = $plugin_to_install['repo-slug'];
$plugin_file = isset( $plugin_to_install['file'] ) ? $plugin_to_install['file'] : $plugin_slug . '.php';
$installed = false;
$activate = false;
// See if the plugin is installed already.
if ( isset( $installed_plugins[ $plugin_slug ] ) ) {
if ( isset( $installed_plugins[ $plugin_file ] ) ) {
$installed = true;
$activate = ! is_plugin_active( $installed_plugins[ $plugin_slug ] );
$activate = ! is_plugin_active( $installed_plugins[ $plugin_file ] );
}
// Install this thing!
@ -1113,7 +1116,7 @@ CREATE TABLE {$wpdb->prefix}woocommerce_termmeta (
$plugin_information = plugins_api(
'plugin_information',
array(
'slug' => $plugin_to_install['repo-slug'],
'slug' => $plugin_slug,
'fields' => array(
'short_description' => false,
'sections' => false,
@ -1177,7 +1180,7 @@ CREATE TABLE {$wpdb->prefix}woocommerce_termmeta (
__( '%1$s could not be installed (%2$s). <a href="%3$s">Please install it manually by clicking here.</a>', 'woocommerce' ),
$plugin_to_install['name'],
$e->getMessage(),
esc_url( admin_url( 'index.php?wc-install-plugin-redirect=' . $plugin_to_install['repo-slug'] ) )
esc_url( admin_url( 'index.php?wc-install-plugin-redirect=' . $plugin_slug ) )
)
);
}
@ -1191,7 +1194,7 @@ CREATE TABLE {$wpdb->prefix}woocommerce_termmeta (
// Activate this thing.
if ( $activate ) {
try {
$result = activate_plugin( $installed_plugins[ $plugin_slug ] );
$result = activate_plugin( $installed ? $installed_plugins[ $plugin_file ] : $plugin_slug . '/' . $plugin_file );
if ( is_wp_error( $result ) ) {
throw new Exception( $result->get_error_message() );

View File

@ -28,6 +28,15 @@ class WC_Privacy {
// Cleanup orders daily - this is a callback on a daily cron event.
add_action( 'woocommerce_cleanup_orders', array( __CLASS__, 'order_cleanup_process' ) );
// This hook registers WooCommerce data exporters.
add_filter( 'wp_privacy_personal_data_exporters', array( __CLASS__, 'register_data_exporters' ), 10 );
// When this is fired, data is removed in a given order. Called from bulk actions.
add_action( 'woocommerce_remove_order_personal_data', array( __CLASS__, 'remove_order_personal_data' ) );
// Handles custom anonomization types not included in core.
add_filter( 'wp_privacy_anonymize_data', array( __CLASS__, 'anonymize_custom_data_types' ), 10, 3 );
}
/**
@ -179,6 +188,495 @@ class WC_Privacy {
'customer_id' => 0,
) );
}
/**
* Registers the personal data exporter for comments.
*
* @since 3.4.0
* @param array $exporters An array of personal data exporters.
* @return array An array of personal data exporters.
*/
public static function register_data_exporters( $exporters ) {
$exporters[] = array(
'exporter_friendly_name' => __( 'WooCommerce Customer Data', 'woocommerce' ),
'callback' => array( __CLASS__, 'customer_data_exporter' ),
);
$exporters[] = array(
'exporter_friendly_name' => __( 'WooCommerce Order Data', 'woocommerce' ),
'callback' => array( __CLASS__, 'order_data_exporter' ),
);
$exporters[] = array(
'exporter_friendly_name' => __( 'WooCommerce Downloads', 'woocommerce' ),
'callback' => array( __CLASS__, 'download_data_exporter' ),
);
return $exporters;
}
/**
* Finds and exports customer data by email address.
*
* @since 3.4.0
* @param string $email_address The user email address.
* @param int $page Page.
* @return array An array of personal data in name value pairs
*/
public static function customer_data_exporter( $email_address, $page ) {
$user = get_user_by( 'email', $email_address ); // Check if user has an ID in the DB to load stored personal data.
$data_to_export = array();
if ( $user instanceof WP_User ) {
$data_to_export[] = array(
'group_id' => 'woocommerce_customer',
'group_label' => __( 'Customer Data', 'woocommerce' ),
'item_id' => 'user',
'data' => self::get_user_personal_data( $user ),
);
}
return array(
'data' => $data_to_export,
'done' => true,
);
}
/**
* Finds and exports data which could be used to identify a person from WooCommerce data assocated with an email address.
*
* Orders are exported in blocks of 10 to avoid timeouts.
*
* @since 3.4.0
* @param string $email_address The user email address.
* @param int $page Page.
* @return array An array of personal data in name value pairs
*/
public static function order_data_exporter( $email_address, $page ) {
$done = false;
$page = (int) $page;
$user = get_user_by( 'email', $email_address ); // Check if user has an ID in the DB to load stored personal data.
$data_to_export = array();
$order_query = array(
'limit' => 10,
'page' => $page,
);
if ( $user instanceof WP_User ) {
$order_query['customer_id'] = (int) $user->ID;
} else {
$order_query['billing_email'] = $email_address;
}
$orders = wc_get_orders( $order_query );
if ( 0 < count( $orders ) ) {
foreach ( $orders as $order ) {
$data_to_export[] = array(
'group_id' => 'woocommerce_orders',
'group_label' => __( 'Orders', 'woocommerce' ),
'item_id' => 'order-' . $order->get_id(),
'data' => self::get_order_personal_data( $order ),
);
}
$done = 10 > count( $orders );
} else {
$done = true;
}
return array(
'data' => $data_to_export,
'done' => $done,
);
}
/**
* Finds and exports customer download logs by email address.
*
* @since 3.4.0
* @param string $email_address The user email address.
* @param int $page Page.
* @return array An array of personal data in name value pairs
*/
public static function download_data_exporter( $email_address, $page ) {
$done = false;
$page = (int) $page;
$user = get_user_by( 'email', $email_address ); // Check if user has an ID in the DB to load stored personal data.
$data_to_export = array();
$downloads_query = array(
'limit' => 10,
'page' => $page,
);
if ( $user instanceof WP_User ) {
$downloads_query['user_id'] = (int) $user->ID;
} else {
$downloads_query['user_email'] = $email_address;
}
$customer_download_data_store = WC_Data_Store::load( 'customer-download' );
$customer_download_log_data_store = WC_Data_Store::load( 'customer-download-log' );
$downloads = $customer_download_data_store->get_downloads( $downloads_query );
if ( 0 < count( $downloads ) ) {
foreach ( $downloads as $download ) {
$data_to_export[] = array(
'group_id' => 'woocommerce_downloads',
'group_label' => __( 'Order Downloads', 'woocommerce' ),
'item_id' => 'download-' . $download->get_id(),
'data' => array(
array(
'name' => __( 'Download ID', 'woocommerce' ),
'value' => $download->get_id(),
),
array(
'name' => __( 'Order ID', 'woocommerce' ),
'value' => $download->get_order_id(),
),
array(
'name' => __( 'Product', 'woocommerce' ),
'value' => get_the_title( $download->get_product_id() ),
),
array(
'name' => __( 'User email', 'woocommerce' ),
'value' => $download->get_user_email(),
),
array(
'name' => __( 'Downloads remaining', 'woocommerce' ),
'value' => $download->get_downloads_remaining(),
),
array(
'name' => __( 'Download count', 'woocommerce' ),
'value' => $download->get_download_count(),
),
array(
'name' => __( 'Access granted', 'woocommerce' ),
'value' => date( 'Y-m-d', $download->get_access_granted( 'edit' )->getTimestamp() ),
),
array(
'name' => __( 'Access expires', 'woocommerce' ),
'value' => ! is_null( $download->get_access_expires( 'edit' ) ) ? date( 'Y-m-d', $download->get_access_expires( 'edit' )->getTimestamp() ) : null,
),
),
);
$download_logs = $customer_download_log_data_store->get_download_logs_for_permission( $download->get_id() );
foreach ( $download_logs as $download_log ) {
$data_to_export[] = array(
'group_id' => 'woocommerce_download_logs',
'group_label' => __( 'Download Logs', 'woocommerce' ),
'item_id' => 'download-log-' . $download_log->get_id(),
'data' => array(
array(
'name' => __( 'Download ID', 'woocommerce' ),
'value' => $download_log->get_permission_id(),
),
array(
'name' => __( 'Timestamp', 'woocommerce' ),
'value' => $download_log->get_timestamp(),
),
array(
'name' => __( 'IP Address', 'woocommerce' ),
'value' => $download_log->get_user_ip_address(),
),
),
);
}
}
$done = 10 > count( $downloads );
} else {
$done = true;
}
return array(
'data' => $data_to_export,
'done' => $done,
);
}
/**
* Get personal data (key/value pairs) for a user object.
*
* @since 3.4.0
* @param WP_User $user user object.
* @return array
*/
protected static function get_user_personal_data( $user ) {
$personal_data = array();
$customer = new WC_Customer( $user->ID );
$props_to_export = array(
'billing_first_name' => __( 'Billing First Name', 'woocommerce' ),
'billing_last_name' => __( 'Billing Last Name', 'woocommerce' ),
'billing_company' => __( 'Billing Company', 'woocommerce' ),
'billing_address_1' => __( 'Billing Address 1', 'woocommerce' ),
'billing_address_2' => __( 'Billing Address 2', 'woocommerce' ),
'billing_city' => __( 'Billing City', 'woocommerce' ),
'billing_postcode' => __( 'Billing Postal/Zip Code', 'woocommerce' ),
'billing_state' => __( 'Billing State', 'woocommerce' ),
'billing_country' => __( 'Billing Country', 'woocommerce' ),
'billing_phone' => __( 'Phone Number', 'woocommerce' ),
'billing_email' => __( 'Email Address', 'woocommerce' ),
'shipping_first_name' => __( 'Shipping First Name', 'woocommerce' ),
'shipping_last_name' => __( 'Shipping Last Name', 'woocommerce' ),
'shipping_company' => __( 'Shipping Company', 'woocommerce' ),
'shipping_address_1' => __( 'Shipping Address 1', 'woocommerce' ),
'shipping_address_2' => __( 'Shipping Address 2', 'woocommerce' ),
'shipping_city' => __( 'Shipping City', 'woocommerce' ),
'shipping_postcode' => __( 'Shipping Postal/Zip Code', 'woocommerce' ),
'shipping_state' => __( 'Shipping State', 'woocommerce' ),
'shipping_country' => __( 'Shipping Country', 'woocommerce' ),
);
foreach ( $props_to_export as $prop => $description ) {
$value = $customer->{"get_$prop"}( 'edit' );
if ( $value ) {
$personal_data[] = array(
'name' => $description,
'value' => $value,
);
}
}
/**
* Allow extensions to register their own personal data for this customer for the export.
*
* @since 3.4.0
* @param array $personal_data Array of name value pairs.
* @param WC_Order $order A customer object.
*/
$personal_data = apply_filters( 'woocommerce_privacy_export_personal_data_customer', $personal_data, $customer );
return $personal_data;
}
/**
* Get personal data (key/value pairs) for an order object.
*
* @since 3.4.0
* @param WC_Order $order Order object.
* @return array
*/
protected static function get_order_personal_data( $order ) {
$personal_data = array();
$props_to_export = array(
'order_number' => __( 'Order Number', 'woocommerce' ),
'date_created' => __( 'Order Date', 'woocommerce' ),
'total' => __( 'Order Total', 'woocommerce' ),
'items' => __( 'Items Purchased', 'woocommerce' ),
'customer_ip_address' => __( 'IP Address', 'woocommerce' ),
'customer_user_agent' => __( 'Browser User Agent', 'woocommerce' ),
'formatted_billing_address' => __( 'Billing Address', 'woocommerce' ),
'formatted_shipping_address' => __( 'Shipping Address', 'woocommerce' ),
'billing_phone' => __( 'Phone Number', 'woocommerce' ),
'billing_email' => __( 'Email Address', 'woocommerce' ),
);
foreach ( $props_to_export as $prop => $name ) {
switch ( $prop ) {
case 'items':
$item_names = array();
foreach ( $order->get_items() as $item ) {
$item_names[] = $item->get_name() . ' x ' . $item->get_quantity();
}
$value = implode( ', ', $item_names );
break;
case 'date_created':
$value = wc_format_datetime( $order->get_date_created(), get_option( 'date_format' ) . ', ' . get_option( 'time_format' ) );
break;
case 'formatted_billing_address':
case 'formatted_shipping_address':
$value = preg_replace( '#<br\s*/?>#i', ', ', $order->{"get_$prop"}() );
break;
default:
$value = $order->{"get_$prop"}();
break;
}
if ( $value ) {
$personal_data[] = array(
'name' => $name,
'value' => $value,
);
}
}
/**
* Allow extensions to register their own personal data for this order for the export.
*
* @since 3.4.0
* @param array $personal_data Array of name value pairs to expose in the export.
* @param WC_Order $order An order object.
*/
$personal_data = apply_filters( 'woocommerce_privacy_export_personal_data_order', $personal_data, $order );
return $personal_data;
}
/**
* Anonymize/remove personal data for a given EMAIL ADDRESS. This user may not have an account.
*
* Note; this is separate to account deletion. WooCommerce handles account deletion/cleanup elsewhere.
* This logic is simply to clean up data for guest users.
*
* @todo Add option to determine if order data should be left alone when removing personal data for a user.
* @todo Hook into core UI.
*
* @param string $email_address Customer email address.
*/
public static function remove_personal_data( $email_address ) {
$user = get_user_by( 'email', $email_address ); // Check if user has an ID in the DB.
$has_account = $user instanceof WP_User;
/**
* Allow 3rd parties to modify this behavior. If true, orders belonging to this user will be anonyimized.
*
* @since 3.4.0
*/
if ( apply_filters( 'woocommerce_privacy_remove_personal_data_includes_orders', true, $email_address ) ) {
$order_query = array(
'limit' => -1,
);
if ( $user instanceof WP_User ) {
$order_query['customer_id'] = (int) $user->ID;
} else {
$order_query['billing_email'] = $email_address;
}
$orders = wc_get_orders( $order_query );
if ( 0 < count( $orders ) ) {
foreach ( $orders as $order ) {
self::remove_order_personal_data( $order );
}
}
}
// Revoke download permissions.
$data_store = WC_Data_Store::load( 'customer-download' );
if ( $user instanceof WP_User ) {
$data_store->delete_by_user_id( (int) $user->ID );
} else {
$data_store->delete_by_user_email( $email_address );
}
/**
* Allow extensions to remove their own personal data for this customer.
*
* @since 3.4.0
* @param string $email_address Customer email address.
*/
do_action( 'woocommerce_privacy_remove_personal_data', $email_address );
}
/**
* Remove personal data specific to WooCommerce from an order object.
*
* Note; this will hinder order processing for obvious reasons!
*
* @param WC_Order $order Order object.
*/
public static function remove_order_personal_data( $order ) {
$anonymized_data = array();
/**
* Expose props and data types we'll be anonymizing.
*
* @since 3.4.0
* @param array $props Keys are the prop names, values are the data type we'll be passing to wp_privacy_anonymize_data().
* @param WC_Order $order A customer object.
*/
$props_to_remove = apply_filters( 'woocommerce_privacy_remove_order_personal_data_props', array(
'customer_ip_address' => 'ip',
'customer_user_agent' => 'text',
'billing_first_name' => 'text',
'billing_last_name' => 'text',
'billing_company' => 'text',
'billing_address_1' => 'text',
'billing_address_2' => 'text',
'billing_city' => 'text',
'billing_postcode' => 'text',
'billing_state' => 'address_state',
'billing_country' => 'address_country',
'billing_phone' => 'phone',
'billing_email' => 'email',
'shipping_first_name' => 'text',
'shipping_last_name' => 'text',
'shipping_company' => 'text',
'shipping_address_1' => 'text',
'shipping_address_2' => 'text',
'shipping_city' => 'text',
'shipping_postcode' => 'text',
'shipping_state' => 'address_state',
'shipping_country' => 'address_country',
), $order );
if ( ! empty( $props_to_remove ) && is_array( $props_to_remove ) ) {
foreach ( $props_to_remove as $prop => $data_type ) {
// Get the current value in edit context.
$value = $order->{"get_$prop"}( 'edit' );
// If the value is empty, it does not need to be anonymized.
if ( empty( $value ) || empty( $data_type ) ) {
continue;
}
if ( function_exists( 'wp_privacy_anonymize_data' ) ) {
$anon_value = wp_privacy_anonymize_data( $data_type, $value );
} else {
$anon_value = '';
}
/**
* Expose a way to control the anonymized value of a prop via 3rd party code.
*
* @since 3.4.0
* @param bool $anonymized_data Value of this prop after anonymization.
* @param string $prop Name of the prop being removed.
* @param string $value Current value of the data.
* @param string $data_type Type of data.
* @param WC_Order $order An order object.
*/
$anonymized_data[ $prop ] = apply_filters( 'woocommerce_privacy_remove_order_personal_data_prop_value', $anon_value, $prop, $value, $data_type, $order );
}
}
// Set all new props and persist the new data to the database.
$order->set_props( $anonymized_data );
$order->save();
/**
* Allow extensions to remove their own personal data for this order.
*
* @since 3.4.0
* @param WC_Order $order A customer object.
*/
do_action( 'woocommerce_privacy_remove_order_personal_data', $order );
// Add note that this event occured.
$order->add_order_note( __( 'Personal data removed.', 'woocommerce' ) );
}
/**
* Handle some custom types of data and anonymize them.
*
* @param string $anonymous Anonymized string.
* @param string $type Type of data.
* @param string $data The data being anonymized.
* @return string Anonymized string.
*/
public static function anonymize_custom_data_types( $anonymous, $type, $data ) {
switch ( $type ) {
case 'address_state':
case 'address_country':
$anonymous = ''; // Empty string - we don't want to store anything after removal.
break;
case 'phone':
$anonymous = preg_replace( '/\d/u', '0', $data );
break;
}
return $anonymous;
}
}
WC_Privacy::init();

View File

@ -20,7 +20,7 @@ final class WooCommerce {
*
* @var string
*/
public $version = '3.3.0';
public $version = '3.4.0';
/**
* The single instance of the class.

View File

@ -225,6 +225,40 @@ class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store
);
}
/**
* Method to delete a download permission from the database by user ID.
*
* @since 3.4.0
* @param int $id user ID of the downloads that will be deleted.
*/
public function delete_by_user_id( $id ) {
global $wpdb;
$wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions
WHERE user_id = %d",
$id
)
);
}
/**
* Method to delete a download permission from the database by user email.
*
* @since 3.4.0
* @param string $email email of the downloads that will be deleted.
*/
public function delete_by_user_email( $email ) {
global $wpdb;
$wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions
WHERE user_email = %s",
$email
)
);
}
/**
* Get a download object.
*
@ -247,6 +281,7 @@ class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store
$args = wp_parse_args(
$args, array(
'user_email' => '',
'user_id' => '',
'order_id' => '',
'order_key' => '',
'product_id' => '',
@ -254,6 +289,7 @@ class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store
'orderby' => 'permission_id',
'order' => 'ASC',
'limit' => -1,
'page' => 1,
'return' => 'objects',
)
);
@ -278,6 +314,10 @@ class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store
$query[] = $wpdb->prepare( 'AND user_email = %s', sanitize_email( $args['user_email'] ) );
}
if ( $args['user_id'] ) {
$query[] = $wpdb->prepare( 'AND user_id = %d', absint( $args['user_id'] ) );
}
if ( $args['order_id'] ) {
$query[] = $wpdb->prepare( 'AND order_id = %d', $args['order_id'] );
}
@ -300,7 +340,7 @@ class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store
$query[] = "ORDER BY {$orderby_sql}";
if ( 0 < $args['limit'] ) {
$query[] = $wpdb->prepare( 'LIMIT %d', $args['limit'] );
$query[] = $wpdb->prepare( 'LIMIT %d, %d', absint( $args['limit'] ) * absint( $args['page'] - 1 ), absint( $args['limit'] ) );
}
// phpcs:ignore WordPress.WP.PreparedSQL.NotPrepared

View File

@ -2,17 +2,14 @@
/**
* Class WC_Customer_Download_Log_Data_Store file.
*
* @package WooCommerce\DataStores
* @version 3.3.0
* @package WooCommerce\Classes
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
defined( 'ABSPATH' ) || exit;
/**
* WC Customer Download Log Data Store.
*
* @version 3.3.0
* WC_Customer_Download_Log_Data_Store class.
*/
class WC_Customer_Download_Log_Data_Store implements WC_Customer_Download_Log_Data_Store_Interface {
@ -74,9 +71,8 @@ class WC_Customer_Download_Log_Data_Store implements WC_Customer_Download_Log_Da
/**
* Method to read a download log from the database.
*
* @param WC_Customer_Download_Log $download_log Customer download log object.
*
* @throws Exception If invalid download log.
* @param WC_Customer_Download_Log $download_log Download log object.
* @throws Exception Exception when read is not possible.
*/
public function read( &$download_log ) {
global $wpdb;
@ -88,13 +84,11 @@ class WC_Customer_Download_Log_Data_Store implements WC_Customer_Download_Log_Da
throw new Exception( __( 'Invalid download log: no ID.', 'woocommerce' ) );
}
$table = $wpdb->prefix . self::get_table_name();
// Query the DB for the download log.
$raw_download_log = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}" . self::get_table_name() . ' WHERE download_log_id = %d', // phpcs:ignore WordPress.WP.PreparedSQL.NotPrepared
$download_log->get_id()
)
);
$raw_download_log = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE download_log_id = %d", $download_log->get_id() ) ); // WPCS: unprepared SQL ok.
if ( ! $raw_download_log ) {
throw new Exception( __( 'Invalid download log: not found.', 'woocommerce' ) );
}
@ -114,7 +108,7 @@ class WC_Customer_Download_Log_Data_Store implements WC_Customer_Download_Log_Da
/**
* Method to update a download log in the database.
*
* @param WC_Customer_Download_Log $download_log Customer download log object.
* @param WC_Customer_Download_Log $download_log Download log object.
*/
public function update( &$download_log ) {
global $wpdb;
@ -163,20 +157,20 @@ class WC_Customer_Download_Log_Data_Store implements WC_Customer_Download_Log_Da
public function get_download_logs( $args = array() ) {
global $wpdb;
$args = wp_parse_args(
$args, array(
'permission_id' => '',
'user_id' => '',
'user_ip_address' => '',
'orderby' => 'download_log_id',
'order' => 'DESC',
'limit' => -1,
'return' => 'objects',
)
);
$args = wp_parse_args( $args, array(
'permission_id' => '',
'user_id' => '',
'user_ip_address' => '',
'orderby' => 'download_log_id',
'order' => 'DESC',
'limit' => -1,
'page' => 1,
'return' => 'objects',
) );
$query = array();
$query[] = "SELECT * FROM {$wpdb->prefix}" . self::get_table_name() . ' WHERE 1=1';
$table = $wpdb->prefix . self::get_table_name();
$query[] = "SELECT * FROM {$table} WHERE 1=1";
if ( $args['permission_id'] ) {
$query[] = $wpdb->prepare( 'AND permission_id = %d', $args['permission_id'] );
@ -197,10 +191,10 @@ class WC_Customer_Download_Log_Data_Store implements WC_Customer_Download_Log_Da
$query[] = "ORDER BY {$orderby_sql}";
if ( 0 < $args['limit'] ) {
$query[] = $wpdb->prepare( 'LIMIT %d', $args['limit'] );
$query[] = $wpdb->prepare( 'LIMIT %d, %d', absint( $args['limit'] ) * absint( $args['page'] - 1 ), absint( $args['limit'] ) );
}
$raw_download_logs = $wpdb->get_results( implode( ' ', $query ) ); // phpcs:ignore WordPress.WP.PreparedSQL.NotPrepared
$raw_download_logs = $wpdb->get_results( implode( ' ', $query ) ); // WPCS: unprepared SQL ok.
switch ( $args['return'] ) {
case 'ids':
@ -213,7 +207,7 @@ class WC_Customer_Download_Log_Data_Store implements WC_Customer_Download_Log_Da
/**
* Get download logs for a given download permission.
*
* @param int $permission_id Permission ID.
* @param int $permission_id Permission to get logs for.
* @return array
*/
public function get_download_logs_for_permission( $permission_id ) {
@ -229,4 +223,14 @@ class WC_Customer_Download_Log_Data_Store implements WC_Customer_Download_Log_Da
);
}
/**
* Method to delete download logs for a given permission ID.
*
* @since 3.4.0
* @param int $id download_id of the downloads that will be deleted.
*/
public function delete_by_permission_id( $id ) {
global $wpdb;
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE permission_id = %d", $id ) );
}
}

View File

@ -532,6 +532,7 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
foreach ( $props_to_update as $meta_key => $prop ) {
$value = $product->{"get_$prop"}( 'edit' );
$value = is_string( $value ) ? wp_slash( $value ) : $value;
switch ( $prop ) {
case 'virtual':
case 'downloadable':
@ -566,12 +567,15 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
// Update extra data associated with the product like button text or product URL for external products.
if ( ! $this->extra_data_saved ) {
foreach ( $extra_data_keys as $key ) {
if ( ! array_key_exists( $key, $props_to_update ) ) {
if ( ! array_key_exists( '_' . $key, $props_to_update ) ) {
continue;
}
$function = 'get_' . $key;
if ( is_callable( array( $product, $function ) ) ) {
if ( update_post_meta( $product->get_id(), '_' . $key, $product->{$function}( 'edit' ) ) ) {
$value = $product->{$function}( 'edit' );
$value = is_string( $value ) ? wp_slash( $value ) : $value;
if ( update_post_meta( $product->get_id(), '_' . $key, $value ) ) {
$this->updated_props[] = $key;
}
}

View File

@ -536,6 +536,19 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
return wc_clean( $value );
}
/**
* Parse an int value field
*
* @param int $value field value.
* @return int
*/
public function parse_int_field( $value ) {
// Remove the ' prepended to fields that start with - if needed
$value = $this->unescape_negative_number( $value );
return intval( $value );
}
/**
* Get formatting callback.
*
@ -578,8 +591,8 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
'grouped_products' => array( $this, 'parse_relative_comma_field' ),
'upsell_ids' => array( $this, 'parse_relative_comma_field' ),
'cross_sell_ids' => array( $this, 'parse_relative_comma_field' ),
'download_limit' => 'intval',
'download_expiry' => 'intval',
'download_limit' => array( $this, 'parse_int_field' ),
'download_expiry' => array( $this, 'parse_int_field' ),
'product_url' => 'esc_url_raw',
'menu_order' => 'intval',
);

View File

@ -1877,7 +1877,7 @@ function wc_restore_locale() {
function wc_make_phone_clickable( $phone ) {
$number = trim( preg_replace( '/[^\d|\+]/', '', $phone ) );
return '<a href="tel:' . esc_attr( $number ) . '">' . esc_html( $phone ) . '</a>';
return $number ? '<a href="tel:' . esc_attr( $number ) . '">' . esc_html( $phone ) . '</a>' : '';
}
/**

View File

@ -512,6 +512,148 @@ function wc_product_post_class( $classes, $class = '', $post_id = '' ) {
return $classes;
}
/**
* Get product taxonomy HTML classes.
*
* @since 3.4.0
* @param array $term_ids Array of terms IDs or objects.
* @param string $taxonomy Taxonomy.
* @return array
*/
function wc_get_product_taxonomy_class( $term_ids, $taxonomy ) {
$classes = array();
foreach ( $term_ids as $term_id ) {
$term = get_term( $term_id, $taxonomy );
if ( empty( $term->slug ) ) {
continue;
}
$term_class = sanitize_html_class( $term->slug, $term->term_id );
if ( is_numeric( $term_class ) || ! trim( $term_class, '-' ) ) {
$term_class = $term->term_id;
}
// 'post_tag' uses the 'tag' prefix for backward compatibility.
if ( 'post_tag' === $taxonomy ) {
$classes[] = 'tag-' . $term_class;
} else {
$classes[] = sanitize_html_class( $taxonomy . '-' . $term_class, $taxonomy . '-' . $term->term_id );
}
}
return $classes;
}
/**
* Retrieves the classes for the post div as an array.
*
* This method is clone from WordPress's get_post_class(), allowing removing taxonomies.
*
* @since 3.4.0
* @param string|array $class One or more classes to add to the class list.
* @param int|WP_Post|WC_Product $product_id Product ID or product object.
* @return array
*/
function wc_get_product_class( $class = '', $product_id = null ) {
if ( is_a( $product_id, 'WC_Product' ) ) {
$product = $product_id;
$product_id = $product_id->get_id();
$post = get_post( $product_id );
} else {
$post = get_post( $product_id );
$product = wc_get_product( $post->ID );
}
$classes = array();
if ( $class ) {
if ( ! is_array( $class ) ) {
$class = preg_split( '#\s+#', $class );
}
$classes = array_map( 'esc_attr', $class );
} else {
// Ensure that we always coerce class to being an array.
$class = array();
}
if ( ! $post || ! $product ) {
return $classes;
}
$classes[] = 'post-' . $post->ID;
if ( ! is_admin() ) {
$classes[] = $post->post_type;
}
$classes[] = 'type-' . $post->post_type;
$classes[] = 'status-' . $post->post_status;
// Post format.
if ( post_type_supports( $post->post_type, 'post-formats' ) ) {
$post_format = get_post_format( $post->ID );
if ( $post_format && ! is_wp_error( $post_format ) ) {
$classes[] = 'format-' . sanitize_html_class( $post_format );
} else {
$classes[] = 'format-standard';
}
}
// Post requires password.
$post_password_required = post_password_required( $post->ID );
if ( $post_password_required ) {
$classes[] = 'post-password-required';
} elseif ( ! empty( $post->post_password ) ) {
$classes[] = 'post-password-protected';
}
// Post thumbnails.
if ( current_theme_supports( 'post-thumbnails' ) && has_post_thumbnail( $post->ID ) && ! is_attachment( $post ) && ! $post_password_required ) {
$classes[] = 'has-post-thumbnail';
}
// Sticky for Sticky Posts.
if ( is_sticky( $post->ID ) ) {
if ( is_home() && ! is_paged() ) {
$classes[] = 'sticky';
} elseif ( is_admin() ) {
$classes[] = 'status-sticky';
}
}
// Hentry for hAtom compliance.
$classes[] = 'hentry';
// Include attributes and any extra taxonomy.
if ( apply_filters( 'woocommerce_get_product_class_include_taxonomies', false ) ) {
$taxonomies = get_taxonomies( array( 'public' => true ) );
foreach ( (array) $taxonomies as $taxonomy ) {
if ( is_object_in_taxonomy( $post->post_type, $taxonomy ) && in_array( $taxonomy, array( 'product_cat', 'product_tag' ), true ) ) {
$classes = array_merge( $classes, wc_get_product_taxonomy_class( (array) get_the_terms( $post->ID, $taxonomy ), $taxonomy ) );
}
}
}
// Categories.
$classes = array_merge( $classes, wc_get_product_taxonomy_class( $product->get_category_ids(), 'product_cat' ) );
// Tags.
$classes = array_merge( $classes, wc_get_product_taxonomy_class( $product->get_tag_ids(), 'product_tag' ) );
return array_filter( array_unique( apply_filters( 'post_class', $classes, $class, $post->ID ) ) );
}
/**
* Display the classes for the product div.
*
* @since 3.4.0
* @param string|array $class One or more classes to add to the class list.
* @param int|WP_Post|WC_Product $product_id Product ID or product object.
*/
function wc_product_class( $class = '', $product_id = null ) {
echo 'class="' . esc_attr( join( ' ', wc_get_product_class( $class, $product_id ) ) ) . '"';
}
/**
* Outputs hidden form inputs for each query string variable.
*

View File

@ -1635,46 +1635,115 @@ function wc_update_330_db_version() {
}
/**
* Update state codes for Ireland.
* Update state codes for Ireland and BD.
*/
function wc_update_340_irish_states() {
function wc_update_340_states() {
global $wpdb;
$ie_states = array(
'CK' => 'CO',
'DN' => 'D',
'GY' => 'G',
'TY' => 'TA',
$country_states = array(
'IE' => array(
'CK' => 'CO',
'DN' => 'D',
'GY' => 'G',
'TY' => 'TA',
),
'BD' => array(
'BAG' => 'BD-05',
'BAN' => 'BD-01',
'BAR' => 'BD-02',
'BARI' => 'BD-06',
'BHO' => 'BD-07',
'BOG' => 'BD-03',
'BRA' => 'BD-04',
'CHA' => 'BD-09',
'CHI' => 'BD-10',
'CHU' => 'BD-12',
'COX' => 'BD-11',
'COM' => 'BD-08',
'DHA' => 'BD-13',
'DIN' => 'BD-14',
'FAR' => 'BD-15',
'FEN' => 'BD-16',
'GAI' => 'BD-19',
'GAZI' => 'BD-18',
'GOP' => 'BD-17',
'HAB' => 'BD-20',
'JAM' => 'BD-21',
'JES' => 'BD-22',
'JHA' => 'BD-25',
'JHE' => 'BD-23',
'JOY' => 'BD-24',
'KHA' => 'BD-29',
'KHU' => 'BD-27',
'KIS' => 'BD-26',
'KUR' => 'BD-28',
'KUS' => 'BD-30',
'LAK' => 'BD-31',
'LAL' => 'BD-32',
'MAD' => 'BD-36',
'MAG' => 'BD-37',
'MAN' => 'BD-33',
'MEH' => 'BD-39',
'MOU' => 'BD-38',
'MUN' => 'BD-35',
'MYM' => 'BD-34',
'NAO' => 'BD-48',
'NAR' => 'BD-43',
'NARG' => 'BD-40',
'NARD' => 'BD-42',
'NAT' => 'BD-44',
'NAW' => 'BD-45',
'NET' => 'BD-41',
'NIL' => 'BD-46',
'NOA' => 'BD-47',
'PAB' => 'BD-49',
'PAN' => 'BD-52',
'PAT' => 'BD-51',
'PIR' => 'BD-50',
'RAJB' => 'BD-53',
'RAJ' => 'BD-54',
'RAN' => 'BD-56',
'RANP' => 'BD-55',
'SAT' => 'BD-58',
'SHA' => 'BD-57',
'SIR' => 'BD-59',
'SUN' => 'BD-61',
'SYL' => 'BD-60',
'TAN' => 'BD-63',
'THA' => 'BD-64',
),
);
foreach ( $ie_states as $old => $new ) {
$wpdb->query(
$wpdb->prepare(
"UPDATE $wpdb->postmeta
SET meta_value = %s
WHERE meta_key IN ( '_billing_state', '_shipping_state' )
AND meta_value = %s",
$new, $old
)
);
$wpdb->update(
"{$wpdb->prefix}woocommerce_shipping_zone_locations",
array(
'location_code' => 'IE:' . $new,
),
array(
'location_code' => 'IE:' . $old,
)
);
$wpdb->update(
"{$wpdb->prefix}woocommerce_tax_rates",
array(
'tax_rate_state' => strtoupper( $new ),
),
array(
'tax_rate_state' => strtoupper( $old ),
)
);
foreach ( $country_states as $country => $states ) {
foreach ( $states as $old => $new ) {
$wpdb->query(
$wpdb->prepare(
"UPDATE $wpdb->postmeta
SET meta_value = %s
WHERE meta_key IN ( '_billing_state', '_shipping_state' )
AND meta_value = %s",
$new, $old
)
);
$wpdb->update(
"{$wpdb->prefix}woocommerce_shipping_zone_locations",
array(
'location_code' => $country . ':' . $new,
),
array(
'location_code' => $country . ':' . $old,
)
);
$wpdb->update(
"{$wpdb->prefix}woocommerce_tax_rates",
array(
'tax_rate_state' => strtoupper( $new ),
),
array(
'tax_rate_state' => strtoupper( $old ),
)
);
}
}
}

View File

@ -635,3 +635,36 @@ function wc_user_search_columns( $search_columns ) {
return $search_columns;
}
add_filter( 'user_search_columns', 'wc_user_search_columns' );
/**
* When a user is deleted in WordPress, delete corresponding WooCommerce data.
*
* @param int $user_id User ID being deleted.
*/
function wc_delete_user_data( $user_id ) {
global $wpdb;
// Clean up sessions.
$wpdb->delete(
$wpdb->prefix . 'woocommerce_sessions',
array(
'session_key' => $user_id,
)
);
// Revoke API keys.
$wpdb->delete(
$wpdb->prefix . 'woocommerce_api_keys',
array(
'user_id' => $user_id,
)
);
// Clean up payment tokens.
$payment_tokens = WC_Payment_Tokens::get_customer_tokens( $user_id );
foreach ( $payment_tokens as $payment_token ) {
$payment_token->delete();
}
}
add_action( 'delete_user', 'wc_delete_user_data' );

View File

@ -70,31 +70,19 @@ class WC_Widget_Recent_Reviews extends WC_Widget {
if ( $comments ) {
$this->widget_start( $args, $instance );
echo '<ul class="product_list_widget">';
echo wp_kses_post( apply_filters( 'woocommerce_before_widget_product_review_list', '<ul class="product_list_widget">' ) );
foreach ( (array) $comments as $comment ) {
$_product = wc_get_product( $comment->comment_post_ID );
$rating = intval( get_comment_meta( $comment->comment_ID, 'rating', true ) );
$rating_html = wc_get_rating_html( $rating );
echo '<li><a href="' . esc_url( get_comment_link( $comment->comment_ID ) ) . '">';
echo $_product->get_image() . wp_kses_post( $_product->get_name() ) . '</a>'; // WPCS: XSS ok.
echo $rating_html; // WPCS: XSS ok.
/* translators: %s: review author */
echo '<span class="reviewer">' . sprintf( esc_html__( 'by %s', 'woocommerce' ), get_comment_author() ) . '</span>'; // WPCS: XSS ok.
echo '</li>';
wc_get_template( 'content-widget-reviews.php', array(
'comment' => $comment,
'product' => wc_get_product( $comment->comment_post_ID ),
) );
}
echo '</ul>';
echo wp_kses_post( apply_filters( 'woocommerce_after_widget_product_review_list', '</ul>' ) );
$this->widget_end( $args );
}
$content = ob_get_clean();

View File

@ -11,33 +11,30 @@
* the readme will list any important changes.
*
* @see https://docs.woocommerce.com/document/template-structure/
* @author WooThemes
* @package WooCommerce/Templates
* @version 3.0.0
* @version 3.4.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
defined( 'ABSPATH' ) || exit;
global $product;
// Ensure visibility
// Ensure visibility.
if ( empty( $product ) || ! $product->is_visible() ) {
return;
}
?>
<li <?php post_class(); ?>>
<li <?php wc_product_class(); ?>>
<?php
/**
* woocommerce_before_shop_loop_item hook.
* Hook: woocommerce_before_shop_loop_item.
*
* @hooked woocommerce_template_loop_product_link_open - 10
*/
do_action( 'woocommerce_before_shop_loop_item' );
/**
* woocommerce_before_shop_loop_item_title hook.
* Hook: woocommerce_before_shop_loop_item_title.
*
* @hooked woocommerce_show_product_loop_sale_flash - 10
* @hooked woocommerce_template_loop_product_thumbnail - 10
@ -45,14 +42,14 @@ if ( empty( $product ) || ! $product->is_visible() ) {
do_action( 'woocommerce_before_shop_loop_item_title' );
/**
* woocommerce_shop_loop_item_title hook.
* Hook: woocommerce_shop_loop_item_title.
*
* @hooked woocommerce_template_loop_product_title - 10
*/
do_action( 'woocommerce_shop_loop_item_title' );
/**
* woocommerce_after_shop_loop_item_title hook.
* Hook: woocommerce_after_shop_loop_item_title.
*
* @hooked woocommerce_template_loop_rating - 5
* @hooked woocommerce_template_loop_price - 10
@ -60,7 +57,7 @@ if ( empty( $product ) || ! $product->is_visible() ) {
do_action( 'woocommerce_after_shop_loop_item_title' );
/**
* woocommerce_after_shop_loop_item hook.
* Hook: woocommerce_after_shop_loop_item.
*
* @hooked woocommerce_template_loop_product_link_close - 5
* @hooked woocommerce_template_loop_add_to_cart - 10

View File

@ -10,18 +10,15 @@
* happen. When this occurs the version of the template file will be bumped and
* the readme will list any important changes.
*
* @see https://docs.woocommerce.com/document/template-structure/
* @author WooThemes
* @package WooCommerce/Templates
* @version 3.0.0
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce/Templates
* @version 3.4.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
defined( 'ABSPATH' ) || exit;
/**
* Hook Woocommerce_before_single_product.
* Hook: woocommerce_before_single_product.
*
* @hooked wc_print_notices - 10
*/
@ -32,7 +29,7 @@ if ( post_password_required() ) {
return;
}
?>
<div id="product-<?php the_ID(); ?>" <?php post_class(); ?>>
<div id="product-<?php the_ID(); ?>" <?php wc_product_class(); ?>>
<?php
/**

View File

@ -0,0 +1,36 @@
<?php
/**
* The template for displaying product widget entries.
*
* This template can be overridden by copying it to yourtheme/woocommerce/content-widget-reviews.php
* HOWEVER, on occasion WooCommerce will need to update template files and you
* (the theme developer) will need to copy the new files to your theme to
* maintain compatibility. We try to do this as little as possible, but it does
* happen. When this occurs the version of the template file will be bumped and
* the readme will list any important changes.
*
* @see https://docs.woocommerce.com/document/template-structure/
* @author WooThemes
* @package WooCommerce/Templates
* @version 3.3.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<li>
<?php do_action( 'woocommerce_widget_product_review_item_start', $args ); ?>
<a href="<?php echo esc_url( get_comment_link( $comment->comment_ID ) ); ?>">
<?php echo $product->get_image(); ?>
<span class="product-title"><?php echo $product->get_name(); ?></span>
</a>
<?php echo wc_get_rating_html( intval( get_comment_meta( $comment->comment_ID, 'rating', true ) ) );?>
<span class="reviewer"><?php echo sprintf( esc_html__( 'by %s', 'woocommerce' ), get_comment_author( $comment->comment_ID ) ); ?></span>
<?php do_action( 'woocommerce_widget_product_review_item_end', $args ); ?>
</li>

View File

@ -10,18 +10,21 @@
* happen. When this occurs the version of the template file will be bumped and
* the readme will list any important changes.
*
* @see https://docs.woocommerce.com/document/template-structure/
* @author WooThemes
* @package WooCommerce/Templates
* @version 2.1.0
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce/Templates
* @version 3.4.0
*/
defined( 'ABSPATH' ) || exit;
do_action( 'woocommerce_before_add_to_cart_button' ); ?>
do_action( 'woocommerce_before_add_to_cart_form' ); ?>
<p class="cart">
<a href="<?php echo esc_url( $product_url ); ?>" rel="nofollow" class="single_add_to_cart_button button alt"><?php echo esc_html( $button_text ); ?></a>
</p>
<form class="cart" action="<?php echo esc_url( $product_url ); ?>" method="post">
<?php do_action( 'woocommerce_before_add_to_cart_button' ); ?>
<?php do_action( 'woocommerce_after_add_to_cart_button' ); ?>
<button type="submit" name="add-to-cart" class="single_add_to_cart_button button alt"><?php echo esc_html( $button_text ); ?></button>
<?php do_action( 'woocommerce_after_add_to_cart_button' ); ?>
</form>
<?php do_action( 'woocommerce_after_add_to_cart_form' ); ?>

View File

@ -39,7 +39,7 @@ do_action( 'woocommerce_before_add_to_cart_form' ); ?>
$post = $post_object; // WPCS: override ok.
setup_postdata( $post );
echo '<tr id="product-' . esc_attr( $grouped_product->get_id() ) . '" class="woocommerce-grouped-product-list-item ' . esc_attr( implode( ' ', get_post_class( '', $grouped_product->get_id() ) ) ) . '">';
echo '<tr id="product-' . esc_attr( $grouped_product->get_id() ) . '" class="woocommerce-grouped-product-list-item ' . esc_attr( implode( ' ', wc_get_product_class( '', $grouped_product->get_id() ) ) ) . '">';
// Output columns for each product.
foreach ( $grouped_product_columns as $column_id ) {

View File

@ -10,9 +10,9 @@
* happen. When this occurs the version of the template file will be bumped and
* the readme will list any important changes.
*
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce/Templates
* @version 3.4.0
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce/Templates
* @version 3.4.0
*/
defined( 'ABSPATH' ) || exit;
@ -23,44 +23,30 @@ if ( ! $product->is_purchasable() ) {
return;
}
echo wc_get_stock_html( $product );
echo wc_get_stock_html( $product ); // WPCS: XSS ok.
if ( $product->is_in_stock() ) : ?>
<?php do_action( 'woocommerce_before_add_to_cart_form' ); ?>
<form class="cart" action="<?php echo esc_url( apply_filters( 'woocommerce_add_to_cart_form_action', $product->get_permalink() ) ); ?>" method="post" enctype='multipart/form-data'>
<?php do_action( 'woocommerce_before_add_to_cart_button' ); ?>
<?php
/**
* @since 2.1.0.
*/
do_action( 'woocommerce_before_add_to_cart_button' );
do_action( 'woocommerce_before_add_to_cart_quantity' );
/**
* @since 3.0.0.
*/
do_action( 'woocommerce_before_add_to_cart_quantity' );
woocommerce_quantity_input( array(
'min_value' => apply_filters( 'woocommerce_quantity_input_min', $product->get_min_purchase_quantity(), $product ),
'max_value' => apply_filters( 'woocommerce_quantity_input_max', $product->get_max_purchase_quantity(), $product ),
'input_value' => isset( $_POST['quantity'] ) ? wc_stock_amount( wp_unslash( $_POST['quantity'] ) ) : $product->get_min_purchase_quantity(), // WPCS: CSRF ok, input var ok.
) );
woocommerce_quantity_input( array(
'min_value' => apply_filters( 'woocommerce_quantity_input_min', $product->get_min_purchase_quantity(), $product ),
'max_value' => apply_filters( 'woocommerce_quantity_input_max', $product->get_max_purchase_quantity(), $product ),
'input_value' => isset( $_POST['quantity'] ) ? wc_stock_amount( $_POST['quantity'] ) : $product->get_min_purchase_quantity(),
) );
/**
* @since 3.0.0.
*/
do_action( 'woocommerce_after_add_to_cart_quantity' );
do_action( 'woocommerce_after_add_to_cart_quantity' );
?>
<button type="submit" name="add-to-cart" value="<?php echo esc_attr( $product->get_id() ); ?>" class="single_add_to_cart_button button alt"><?php echo esc_html( $product->single_add_to_cart_text() ); ?></button>
<?php
/**
* @since 2.1.0.
*/
do_action( 'woocommerce_after_add_to_cart_button' );
?>
<?php do_action( 'woocommerce_after_add_to_cart_button' ); ?>
</form>
<?php do_action( 'woocommerce_after_add_to_cart_form' ); ?>

View File

@ -10,7 +10,7 @@
* happen. When this occurs the version of the template file will be bumped and
* the readme will list any important changes.
*
* @see https://docs.woocommerce.com/document/template-structure/
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce/Templates
* @version 3.4.0
*/
@ -23,40 +23,46 @@ $attribute_keys = array_keys( $attributes );
do_action( 'woocommerce_before_add_to_cart_form' ); ?>
<form class="variations_form cart" action="<?php echo esc_url( apply_filters( 'woocommerce_add_to_cart_form_action', $product->get_permalink() ) ); ?>" method="post" enctype='multipart/form-data' data-product_id="<?php echo absint( $product->get_id() ); ?>" data-product_variations="<?php echo htmlspecialchars( wp_json_encode( $available_variations ) ) ?>">
<form class="variations_form cart" action="<?php echo esc_url( apply_filters( 'woocommerce_add_to_cart_form_action', $product->get_permalink() ) ); ?>" method="post" enctype='multipart/form-data' data-product_id="<?php echo absint( $product->get_id() ); ?>" data-product_variations="<?php echo htmlspecialchars( wp_json_encode( $available_variations ) ); // WPCS: XSS ok. ?>">
<?php do_action( 'woocommerce_before_variations_form' ); ?>
<?php if ( empty( $available_variations ) && false !== $available_variations ) : ?>
<p class="stock out-of-stock"><?php _e( 'This product is currently out of stock and unavailable.', 'woocommerce' ); ?></p>
<p class="stock out-of-stock"><?php esc_html_e( 'This product is currently out of stock and unavailable.', 'woocommerce' ); ?></p>
<?php else : ?>
<table class="variations" cellspacing="0">
<tbody>
<?php foreach ( $attributes as $attribute_name => $options ) : ?>
<tr>
<td class="label"><label for="<?php echo sanitize_title( $attribute_name ); ?>"><?php echo wc_attribute_label( $attribute_name ); ?></label></td>
<td class="label"><label for="<?php echo esc_attr( sanitize_title( $attribute_name ) ); ?>"><?php echo esc_html( wc_attribute_label( $attribute_name ) ); ?></label></td>
<td class="value">
<?php
$selected = isset( $_REQUEST[ 'attribute_' . $attribute_name ] ) ? wc_clean( stripslashes( urldecode( $_REQUEST[ 'attribute_' . $attribute_name ] ) ) ) : $product->get_variation_default_attribute( $attribute_name );
wc_dropdown_variation_attribute_options( array( 'options' => $options, 'attribute' => $attribute_name, 'product' => $product, 'selected' => $selected ) );
echo end( $attribute_keys ) === $attribute_name ? apply_filters( 'woocommerce_reset_variations_link', '<a class="reset_variations" href="#">' . esc_html__( 'Clear', 'woocommerce' ) . '</a>' ) : '';
$selected = isset( $_REQUEST[ 'attribute_' . $attribute_name ] ) ? wc_clean( urldecode( wp_unslash( $_REQUEST[ 'attribute_' . $attribute_name ] ) ) ) : $product->get_variation_default_attribute( $attribute_name ); // WPCS: input var ok, CSRF ok, sanitization ok.
wc_dropdown_variation_attribute_options( array(
'options' => $options,
'attribute' => $attribute_name,
'product' => $product,
'selected' => $selected,
) );
echo end( $attribute_keys ) === $attribute_name ? wp_kses_post( apply_filters( 'woocommerce_reset_variations_link', '<a class="reset_variations" href="#">' . esc_html__( 'Clear', 'woocommerce' ) . '</a>' ) ) : '';
?>
</td>
</tr>
<?php endforeach;?>
<?php endforeach; ?>
</tbody>
</table>
<?php do_action( 'woocommerce_before_add_to_cart_button' ); ?>
<div class="single_variation_wrap">
<?php
/**
* woocommerce_before_single_variation Hook.
* Hook: woocommerce_before_single_variation.
*/
do_action( 'woocommerce_before_single_variation' );
/**
* woocommerce_single_variation hook. Used to output the cart button and placeholder for variation data.
* Hook: woocommerce_single_variation. Used to output the cart button and placeholder for variation data.
*
* @since 2.4.0
* @hooked woocommerce_single_variation - 10 Empty div for variation data.
* @hooked woocommerce_single_variation_add_to_cart_button - 20 Qty and cart button.
@ -64,13 +70,11 @@ do_action( 'woocommerce_before_add_to_cart_form' ); ?>
do_action( 'woocommerce_single_variation' );
/**
* woocommerce_after_single_variation Hook.
* Hook: woocommerce_after_single_variation.
*/
do_action( 'woocommerce_after_single_variation' );
?>
</div>
<?php do_action( 'woocommerce_after_add_to_cart_button' ); ?>
<?php endif; ?>
<?php do_action( 'woocommerce_after_variations_form' ); ?>

View File

@ -2,10 +2,9 @@
/**
* Single variation cart button
*
* @see https://docs.woocommerce.com/document/template-structure/
* @author WooThemes
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce/Templates
* @version 3.0.0
* @version 3.4.0
*/
defined( 'ABSPATH' ) || exit;
@ -13,24 +12,24 @@ defined( 'ABSPATH' ) || exit;
global $product;
?>
<div class="woocommerce-variation-add-to-cart variations_button">
<?php do_action( 'woocommerce_before_add_to_cart_button' ); ?>
<?php
/**
* @since 3.0.0.
*/
do_action( 'woocommerce_before_add_to_cart_quantity' );
do_action( 'woocommerce_before_add_to_cart_quantity' );
woocommerce_quantity_input( array(
'min_value' => apply_filters( 'woocommerce_quantity_input_min', $product->get_min_purchase_quantity(), $product ),
'max_value' => apply_filters( 'woocommerce_quantity_input_max', $product->get_max_purchase_quantity(), $product ),
'input_value' => isset( $_POST['quantity'] ) ? wc_stock_amount( $_POST['quantity'] ) : $product->get_min_purchase_quantity(),
) );
woocommerce_quantity_input( array(
'min_value' => apply_filters( 'woocommerce_quantity_input_min', $product->get_min_purchase_quantity(), $product ),
'max_value' => apply_filters( 'woocommerce_quantity_input_max', $product->get_max_purchase_quantity(), $product ),
'input_value' => isset( $_POST['quantity'] ) ? wc_stock_amount( wp_unslash( $_POST['quantity'] ) ) : $product->get_min_purchase_quantity(), // WPCS: CSRF ok, input var ok.
) );
/**
* @since 3.0.0.
*/
do_action( 'woocommerce_after_add_to_cart_quantity' );
do_action( 'woocommerce_after_add_to_cart_quantity' );
?>
<button type="submit" class="single_add_to_cart_button button alt"><?php echo esc_html( $product->single_add_to_cart_text() ); ?></button>
<?php do_action( 'woocommerce_after_add_to_cart_button' ); ?>
<input type="hidden" name="add-to-cart" value="<?php echo absint( $product->get_id() ); ?>" />
<input type="hidden" name="product_id" value="<?php echo absint( $product->get_id() ); ?>" />
<input type="hidden" name="variation_id" class="variation_id" value="0" />

View File

@ -11,7 +11,16 @@ class WC_Tests_Customer_Download_Log extends WC_Unit_Test_Case {
* Test: get_id
*/
function test_get_id() {
$customer_id = wc_create_new_customer( 'test@example.com', 'testuser', 'testpassword' );
$download = new WC_Customer_Download();
$download->set_user_id( $customer_id );
$download->set_order_id( 1 );
$download->save();
$object = new WC_Customer_Download_Log();
$object->set_permission_id( $download->get_id() );
$object->set_user_id( $customer_id );
$object->set_user_ip_address( '1.2.3.4' );
$id = $object->save();
$this->assertEquals( $id, $object->get_id() );
}

View File

@ -0,0 +1,157 @@
<?php
/**
* Privacy data exporter.
*
* @package WooCommerce\Tests\Util
*/
class WC_Test_Privacy_Export extends WC_Unit_Test_Case {
/**
* Order tracking for cleanup.
*
* @var array
*/
protected $orders = array();
/**
* Customer tracking for cleanup.
*
* @var array
*/
protected $customers = array();
/**
* Load up the importer classes since they aren't loaded by default.
*/
public function setUp() {
$customer1 = WC_Helper_Customer::create_customer( 'customer1', 'password', 'test1@test.com' );
$customer1->set_billing_email( 'customer1@test.com' );
$customer1->save();
$customer2 = WC_Helper_Customer::create_customer( 'customer2', 'password', 'test2@test.com' );
$customer2->set_billing_email( 'customer2@test.com' );
$customer2->save();
$this->customers[] = $customer1;
$this->customers[] = $customer2;
// Create a bunch of dummy orders for some users.
$this->orders[] = WC_Helper_Order::create_order( $customer1->get_id() );
$this->orders[] = WC_Helper_Order::create_order( $customer1->get_id() );
$this->orders[] = WC_Helper_Order::create_order( $customer1->get_id() );
$this->orders[] = WC_Helper_Order::create_order( $customer1->get_id() );
$this->orders[] = WC_Helper_Order::create_order( $customer1->get_id() );
$this->orders[] = WC_Helper_Order::create_order( $customer1->get_id() );
$this->orders[] = WC_Helper_Order::create_order( $customer1->get_id() );
$this->orders[] = WC_Helper_Order::create_order( $customer1->get_id() );
$this->orders[] = WC_Helper_Order::create_order( $customer1->get_id() );
$this->orders[] = WC_Helper_Order::create_order( $customer1->get_id() );
$this->orders[] = WC_Helper_Order::create_order( $customer1->get_id() );
$this->orders[] = WC_Helper_Order::create_order( $customer2->get_id() );
$this->orders[] = WC_Helper_Order::create_order( $customer2->get_id() );
}
/**
* Clean up after test.
*/
public function tearDown() {
foreach ( $this->orders as $object ) {
$object->delete( true );
}
foreach ( $this->customers as $object ) {
$object->delete( true );
}
}
/**
* Test: Customer data exporter.
*/
public function test_customer_data_exporter() {
// Test a non existing user.
$response = WC_Privacy::customer_data_exporter( 'doesnotexist@test.com', 1 );
$this->assertEquals( array(), $response['data'] );
// Do a test export and check response.
$response = WC_Privacy::customer_data_exporter( 'test1@test.com', 1 );
$this->assertTrue( $response['done'] );
$this->assertEquals( array(
array(
'group_id' => 'woocommerce_customer',
'group_label' => 'Customer Data',
'item_id' => 'user',
'data' => array(
array(
'name' => 'Billing Address 1',
'value' => '123 South Street',
),
array(
'name' => 'Billing Address 2',
'value' => 'Apt 1',
),
array(
'name' => 'Billing City',
'value' => 'Philadelphia',
),
array(
'name' => 'Billing Postal/Zip Code',
'value' => '19123',
),
array(
'name' => 'Billing State',
'value' => 'PA',
),
array(
'name' => 'Billing Country',
'value' => 'US',
),
array(
'name' => 'Email Address',
'value' => 'customer1@test.com',
),
array(
'name' => 'Shipping Address 1',
'value' => '123 South Street',
),
array(
'name' => 'Shipping Address 2',
'value' => 'Apt 1',
),
array(
'name' => 'Shipping City',
'value' => 'Philadelphia',
),
array(
'name' => 'Shipping Postal/Zip Code',
'value' => '19123',
),
array(
'name' => 'Shipping State',
'value' => 'PA',
),
array(
'name' => 'Shipping Country',
'value' => 'US',
),
),
),
), $response['data'] );
}
/**
* Test: Order data exporter.
*/
public function test_order_data_exporter() {
$response = WC_Privacy::order_data_exporter( 'test1@test.com', 1 );
$this->assertEquals( 'woocommerce_orders', $response['data'][0]['group_id'] );
$this->assertEquals( 'Orders', $response['data'][0]['group_label'] );
$this->assertContains( 'order-', $response['data'][0]['item_id'] );
$this->assertArrayHasKey( 'data', $response['data'][0] );
$this->assertTrue( 8 === count( $response['data'][0]['data'] ), count( $response['data'][0]['data'] ) );
// Next page should be orders.
$response = WC_Privacy::order_data_exporter( 'test1@test.com', 2 );
$this->assertTrue( $response['done'] );
$this->assertTrue( 8 === count( $response['data'][0]['data'] ), count( $response['data'][0]['data'] ) );
}
}

View File

@ -0,0 +1,68 @@
<?php
/**
* Test template funcitons.
*
* @package WooCommerce/Tests/Templates
* @since 3.4.0
*/
class WC_Tests_Template_Functions extends WC_Unit_Test_Case {
/**
* Test wc_get_product_class().
*
* @covers wc_product_class()
* @covers wc_product_post_class()
* @covers wc_get_product_taxonomy_class()
* @since 3.4.0
*/
public function test_wc_get_product_class() {
$category = wp_insert_term( 'Some Category', 'product_cat' );
$product = new WC_Product_Simple();
$product->set_virtual( true );
$product->set_regular_price( '10' );
$product->set_sale_price( '5' );
$product->set_category_ids( array( $category['term_id'] ) );
$product->save();
$expected = array(
'foo',
'post-' . $product->get_id(),
'product',
'type-product',
'status-publish',
'product_cat-some-category',
'first',
'instock',
'sale',
'virtual',
'purchasable',
'product-type-simple',
);
$this->assertEquals( $expected, array_values( wc_get_product_class( 'foo', $product ) ) );
// All taxonomies.
add_filter( 'woocommerce_get_product_class_include_taxonomies', '__return_true' );
$expected = array(
'foo',
'post-' . $product->get_id(),
'product',
'type-product',
'status-publish',
'product_cat-some-category',
'instock',
'sale',
'virtual',
'purchasable',
'product-type-simple',
);
$this->assertEquals( $expected, array_values( wc_get_product_class( 'foo', $product ) ) );
add_filter( 'woocommerce_get_product_class_include_taxonomies', '__return_false' );
$product->delete( true );
wp_delete_term( $category['term_id'], 'product_cat' );
}
}