WIP - Product CRUD (#12065)

* Created function to get the catalog visibility options

* First methods for WP_Product crud

* Product set methods

* Fixed several erros while setting data

* First methods for WP_Product crud

* Product set methods

* Fixed several erros while setting data

* Hardcode the get_type per product class

* Initial look through getters and setters and abstract data

* Missing var

* Add related product functions and deprecate those in class.

* No need to exclude ID

* Fixed coding standards and improved the docblocks

* Get cached terms from wc_get_related_terms()

* Fixed wrong variable in wc_get_related_terms

* Use count() instead of sizeof()

* Sanitize ids later

* Remove unneeded comments

* wc_get_product_term_ids instead of related wording and use in other places.

get_the_terms is used here and also handles caching, something
wp_get_post_terms does not.

* Clean up the abstract product class a bit, deprecate two functions we have renamed, make update & create work properly, and add some tests for it.

* Bump template version

* Handle PR feedback: Remove duplicate regular_price update, allow changing of post status for products, remove deprecation for get_title since we might still offer it as a function

* Made abstract function useful

* External Product CRUD

* _virtual meta should be 'no', not taxable, in product unit test helper

* Grouped product class

* Tests

* Move children to meta and update test

* Use get_upsell_ids

* Spacing in query

* Moving and refactoring methods

* Availability html

* Tidy/add todos

* Rename method

* Put back review functions (still todo)

* missing $this

* get_price_including_tax/excluding_tax functions

* wc_get_price_to_display

* Price handling

* [Product CRUD] Variable (#12146)

* [Product CRUD] Variable Products

* Handle PR feedback.

* [Product CRUD] Grouped Handling (#12151)

* Handle grouped product saving

* Update routine

* [Product CRUD] Product crud terms (#12149)

* Category and tag id handling

* Replace template functions

* Remove todo

* Handle default name in save function

* Product crud admin save routine (#12174)

* Initial props

* Work on admin saving

* Set/get attributes

* Atom was moaning about this before but no longer.

* Update get_shipping_class

* WC_Product_Attribute

* Use getter in admin panel

* Fix attribute saving

* Spacing

* Fix comment

* wc_implode_text_attributes helper function

* [Product CRUD] Product crud admin use getters (#12196)

* Initial props

* Work on admin saving

* Set/get attributes

* Atom was moaning about this before but no longer.

* Update get_shipping_class

* WC_Product_Attribute

* Use getter in admin panel

* Fix attribute saving

* Move settings into new files

* Refactor panels and use getters

* Use getters for variation panel

* Revert save variation changes for now

* Add todos

* Fix downloads

* REST API CRUD Updates

* Additional API updates/fixes. Added some todos

* Fix final failing tests and implementing setters/getters and attributes functionality.

* Fix comparison for is_on_sale and remove download_type from WC_Product.

* Add a wc_get_products wrapper.

* Remove the download type input from the product data metabox for downloadable products. (#12221)

* [Product CRUD] Variations - setters, getters and admin. (#12228)

* Started on variation changes

* Stock functions

* Variation class

* Bulk change ->id to get_id() to fix variation form display

* Missing status

* Fix add to cart

* Start on stored data save

* save variation

* Save_variations

* Variation edit panel

* Save variations code works.

* Remove stored data code and fix save

* Improve legacy class

* wc_bool_to_string

* prepare_set_attributes

* Use wc_get_products

* More feedback fixes

* Feedback fixes

* Implement CRUD in the legacy REST API

* Handle PR feedback

* [Product CRUD] Getter setter proxy methods (#12236)

* Started on variation changes

* Stock functions

* Variation class

* Bulk change ->id to get_id() to fix variation form display

* Missing status

* Fix add to cart

* Start on stored data save

* save variation

* Save_variations

* Variation edit panel

* Save variations code works.

* Remove stored data code and fix save

* Improve legacy class

* wc_bool_to_string

* prepare_set_attributes

* Use wc_get_products

* More feedback fixes

* get_prop implementation in abstract and data classes

* Implement set_prop

* Change handling

* Array key exists

* set_object_read

* Use get_the_terms() instead of wp_get_post_terms()

wp_get_post_terms() is a wrapper around wp_get_object_terms() which does not
use the object cache, and generates a database query every time it is used.

get_the_terms() however can use data from the object cache if present.

* Allow WP_Query to preload post data, and meta in wc_get_products()

Allow WP_Query to bulk query for post data and meta if more than
just IDs are requested from wc_get_products(). Reduces query count
significantly.

* [Product CRUD] Variable, variation, notices, and stock handling (#12277)

* No longer needed

* Remove old todos

* Use getters in admin list

* Related and upsells update for CRUD

* Fix notice in gallery

* Variable fixes and todos

* Context

* Price sync

* Revert variation attributes change

* Return parent data in view context

* Defer term counting

* wc_find_matching_product_variation

* Stock manage tweaks

* Stock fixes

* Correct id

* correct id

* Better sync

* Data logic setter fix

* feedback

* First methods for WP_Product crud

* Product set methods

* Fixed several erros while setting data

* Hardcode the get_type per product class

* Initial look through getters and setters and abstract data

* Missing var

* Fixed coding standards and improved the docblocks

* Get cached terms from wc_get_related_terms()

* Fixed wrong variable in wc_get_related_terms

* Use count() instead of sizeof()

* Add related product functions and deprecate those in class.

* No need to exclude ID

* Sanitize ids later

* Clean up the abstract product class a bit, deprecate two functions we have renamed, make update & create work properly, and add some tests for it.

* Remove unneeded comments

* wc_get_product_term_ids instead of related wording and use in other places.

get_the_terms is used here and also handles caching, something
wp_get_post_terms does not.

* Handle PR feedback: Remove duplicate regular_price update, allow changing of post status for products, remove deprecation for get_title since we might still offer it as a function

* External Product CRUD

* _virtual meta should be 'no', not taxable, in product unit test helper

* Bump template version

* Made abstract function useful

* Grouped product class

* Tests

* Move children to meta and update test

* Use get_upsell_ids

* Spacing in query

* Moving and refactoring methods

* Availability html

* Tidy/add todos

* Rename method

* Put back review functions (still todo)

* missing $this

* get_price_including_tax/excluding_tax functions

* wc_get_price_to_display

* Price handling

* [Product CRUD] Variable (#12146)

* [Product CRUD] Variable Products

* Handle PR feedback.

* [Product CRUD] Grouped Handling (#12151)

* Handle grouped product saving

* Update routine

* [Product CRUD] Product crud terms (#12149)

* Category and tag id handling

* Replace template functions

* Remove todo

* Handle default name in save function

* Product crud admin save routine (#12174)

* Initial props

* Work on admin saving

* Set/get attributes

* Atom was moaning about this before but no longer.

* Update get_shipping_class

* WC_Product_Attribute

* Use getter in admin panel

* Fix attribute saving

* Spacing

* Fix comment

* wc_implode_text_attributes helper function

* [Product CRUD] Product crud admin use getters (#12196)

* Initial props

* Work on admin saving

* Set/get attributes

* Atom was moaning about this before but no longer.

* Update get_shipping_class

* WC_Product_Attribute

* Use getter in admin panel

* Fix attribute saving

* Move settings into new files

* Refactor panels and use getters

* Use getters for variation panel

* Revert save variation changes for now

* Add todos

* Fix downloads

* REST API CRUD Updates

* Additional API updates/fixes. Added some todos

* Fix final failing tests and implementing setters/getters and attributes functionality.

* Fix comparison for is_on_sale and remove download_type from WC_Product.

* Add a wc_get_products wrapper.

* Remove the download type input from the product data metabox for downloadable products. (#12221)

* [Product CRUD] Variations - setters, getters and admin. (#12228)

* Started on variation changes

* Stock functions

* Variation class

* Bulk change ->id to get_id() to fix variation form display

* Missing status

* Fix add to cart

* Start on stored data save

* save variation

* Save_variations

* Variation edit panel

* Save variations code works.

* Remove stored data code and fix save

* Improve legacy class

* wc_bool_to_string

* prepare_set_attributes

* Use wc_get_products

* More feedback fixes

* Feedback fixes

* Implement CRUD in the legacy REST API

* Handle PR feedback

* [Product CRUD] Getter setter proxy methods (#12236)

* Started on variation changes

* Stock functions

* Variation class

* Bulk change ->id to get_id() to fix variation form display

* Missing status

* Fix add to cart

* Start on stored data save

* save variation

* Save_variations

* Variation edit panel

* Save variations code works.

* Remove stored data code and fix save

* Improve legacy class

* wc_bool_to_string

* prepare_set_attributes

* Use wc_get_products

* More feedback fixes

* get_prop implementation in abstract and data classes

* Implement set_prop

* Change handling

* Array key exists

* set_object_read

* Use get_the_terms() instead of wp_get_post_terms()

wp_get_post_terms() is a wrapper around wp_get_object_terms() which does not
use the object cache, and generates a database query every time it is used.

get_the_terms() however can use data from the object cache if present.

* [Product CRUD] Variable, variation, notices, and stock handling (#12277)

* No longer needed

* Remove old todos

* Use getters in admin list

* Related and upsells update for CRUD

* Fix notice in gallery

* Variable fixes and todos

* Context

* Price sync

* Revert variation attributes change

* Return parent data in view context

* Defer term counting

* wc_find_matching_product_variation

* Stock manage tweaks

* Stock fixes

* Correct id

* correct id

* Better sync

* Data logic setter fix

* feedback

* Prevent notices

* Handle image_id from parent

* Fix error

* Remove _wc_save_product_price

* Remove todo

* Fixed wrong variation URLs

* Fixed undefined $image_id in WC_Product_Variation::get_image_id()

* Allow wc_rest_prepare_date_response() handle timestamps

* Updated get methods on REST API for variations

* Use variations CRUD to save variations metadata

* [Product CRUD] Abstract todos (#12305)

* Get dimensions and weights, with soft deprecation

* Product attributes

* Ratings

* Fix read method

* Downloads

* Feedback

* Revert "[Product CRUD] Abstract todos (#12305)"

This reverts commit 9a6136fcf8.

* Remove deprecated get_variation_id()

* New default attributes method

* [Product CRUD] Product Datastore (#12317)

* Fix up tests in the product/* folder.

* Handle data store updates for grouped, variable, external, simple, and general data store updates for products.

* Variations & variable changes.

* Update -functions.php calls to use data store.

* Add an interface for the public product data store methods.

* Finished product factory tests

* Correctly delete in the api, fix up some comments, and implement an interface for the public variable methods.

* Fix up delete in all versions of the api

* Handle feedback

* Match protected decloration to parent

* Product crud abstract todos (#12316)

* Get dimensions and weights, with soft deprecation

* Product attributes

* Ratings

* Fix read method

* Downloads

* Feedback

* Fix up store

* Fixed method returning in write context

* Fix error in variation admin

* Check for parent value - fixes tax class

* Remove old/complete todos

* Allow set tax class as "parent"

* Removed duplicated sync

* Fixed wrong variation URLs

* Fixed undefined $image_id in WC_Product_Variation::get_image_id()

* Allow wc_rest_prepare_date_response() handle timestamps

* Updated get methods on REST API for variations

* Use variations CRUD to save variations metadata

* Remove deprecated get_variation_id()

* New default attributes method

* Fixed method returning in write context

* Allow set tax class as "parent"

* Removed duplicated sync

* Fixed coding standards

* TODO is not accurate.

* Should pass WC_Product instancies to WC_Comments methods (#12327)

* Use new method in abstract order class to prevent headers sent issue in tests

* Fixed variable description in REST API

* Updated how create initial product variation

* Fixed a few fatal errors and warnings in Products CRUD (#12329)

* Fixed a few fatal errors and warnings in Products CRUD

* Fixed sync functions

* Add variations CRUD to legacy API (#12331)

* Apply crud to variable products in legacy API v1

* New REST API do not need fallback for default attributes

* Apply variations CRUD to legacy API v2

* Legacy v2 - save default attributes

* Variations in legacy API v2 do not have descriptions

* Fixed legacy API v2 variations params

* Applied variations CRUD to legacy API v3

* Sync before save in legacy apis

* Punc

* Removed API todos

* Removed test

* Products endpoint tweaks (#12354)

* Var type already normalized on CRUD

* Let Product CRUD handle with validation, sanitization and conditional checks

* Set downloads using WC_Product_Download

* Stop try catch exceptions more than one time

* Handle WC_Data_Exception in legacy API

* Complete remove products when fails on creating

* On creating I mean!

* Already have a method to complete delete products

* Fixed standards using WP CodeSniffer

* get_the_terms() returns false when empty

* get_manage_stock returns boolean

@claudiosanches

* Merge conflict

* Variations API endpoint fixes

* Product CRUD improvements (#12359)

* args is not used any more - remove todo

* Added test for attributes

* wc_get_price_excluding_tax usage

* parent usage

* Fix rating counts

* Test fixes

* Cleanup after tests

* Make sure status transition code runs even during API calls, not just in admin.

* Default visibility

* Fix attribute setting in API

* Use get name instead of get title

* variation id usage

* Improved cross sell templates

* variation_data

* Grouped product sync

* Notices

* Sync is not needed in API

* Delete

* Rename interfaces

* Update counts in data store
This commit is contained in:
Mike Jolley 2016-11-16 12:38:24 +00:00 committed by GitHub
parent cb82569ed0
commit 76b32c9aa5
138 changed files with 20356 additions and 13923 deletions

View File

@ -46,7 +46,7 @@ tree: true
# generate documentation for deprecated elements # generate documentation for deprecated elements
deprecated: true deprecated: true
# generate list of tasks with @todo annotation # generate list of tasks with @ todo annotation
todo: true todo: true
# add link to ZIP archive of documentation # add link to ZIP archive of documentation

File diff suppressed because one or more lines are too long

View File

@ -3808,6 +3808,7 @@ img.help_tip {
} }
textarea { textarea {
float: left;
height: 3.5em; height: 3.5em;
line-height: 1.5em; line-height: 1.5em;
vertical-align: top; vertical-align: top;
@ -4220,8 +4221,8 @@ img.help_tip {
.upload_image_button { .upload_image_button {
display: block; display: block;
width: 48px; width: 64px;
height: 48px; height: 64px;
float: left; float: left;
margin-right: 20px; margin-right: 20px;
position: relative; position: relative;
@ -4242,8 +4243,8 @@ img.help_tip {
right: 0; right: 0;
bottom: 0; bottom: 0;
text-align: center; text-align: center;
line-height: 48px; line-height: 64px;
font-size: 48px; font-size: 64px;
font-weight: 400; font-weight: 400;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
} }
@ -4277,49 +4278,67 @@ img.help_tip {
} }
input[type=checkbox] { input[type=checkbox] {
margin-top: 5px; margin: -3px 0 0 .5em;
margin-right: 3px; vertical-align: middle;
} }
} }
} }
.form-row { .form-row {
label { label {
display: block; display: inline-block;
}
.woocommerce-help-tip {
float: right;
} }
input[type=text], input[type=text],
input[type=number], input[type=number],
select { select,
textarea {
width: 100%; width: 100%;
vertical-align: middle;
margin: 2px 0 0;
padding: 6px;
}
select {
height: 30px;
line-height: 30px;
} }
&.dimensions_field { &.dimensions_field {
.wrap {
clear:left;
display: block;
}
input { input {
width: 25%; width: 33%;
float: left; float: left;
margin-right: 1%; vertical-align: middle;
&:last-of-type { &:last-of-type {
margin-right: 0; margin-right: 0;
width: 34%;
} }
} }
} }
}
.form-row-first, &.form-row-first,
.form-row-last { &.form-row-last {
width: 48%; width: 48%;
float: right; float: right;
} }
.form-row-first { &.form-row-first {
clear: both; clear: both;
float: left; float: left;
} }
.form-row-full { &.form-row-full {
clear: both; clear: both;
}
} }
/** /**

File diff suppressed because one or more lines are too long

View File

@ -1024,8 +1024,8 @@ p.demo_store,
} }
} }
.alt td, tr:nth-child(even) td,
.alt th { tr:nth-child(even) th {
background: rgba(0, 0, 0, 0.025); background: rgba(0, 0, 0, 0.025);
} }
} }

View File

@ -194,15 +194,15 @@
} }
// Display weight // Display weight
if ( variation.weight ) { if ( variation.weight_html ) {
$weight.wc_set_content( variation.weight ); $weight.wc_set_content( variation.weight_html );
} else { } else {
$weight.wc_reset_content(); $weight.wc_reset_content();
} }
// Display dimensions // Display dimensions
if ( variation.dimensions ) { if ( variation.dimensions_html ) {
$dimensions.wc_set_content( variation.dimensions ); $dimensions.wc_set_content( variation.dimensions_html );
} else { } else {
$dimensions.wc_reset_content(); $dimensions.wc_reset_content();
} }
@ -228,11 +228,11 @@
$template_html = $template_html.replace( '/*<![CDATA[*/', '' ); $template_html = $template_html.replace( '/*<![CDATA[*/', '' );
$template_html = $template_html.replace( '/*]]>*/', '' ); $template_html = $template_html.replace( '/*]]>*/', '' );
$single_variation.html( $template_html ); $single_variation.html( $template_html );
$form.find( 'input[name="variation_id"], input.variation_id' ).val( variation.variation_id ).change(); $form.find( 'input[name="variation_id"], input.variation_id' ).val( variation.id ).change();
} }
// Hide or show qty input // Hide or show qty input
if ( variation.is_sold_individually === 'yes' ) { if ( variation.sold_individually ) {
$qty.find( 'input.qty' ).val( '1' ).attr( 'min', '1' ).attr( 'max', '' ); $qty.find( 'input.qty' ).val( '1' ).attr( 'min', '1' ).attr( 'max', '' );
$qty.hide(); $qty.hide();
} else { } else {
@ -470,7 +470,11 @@
if ( undefined === this.attr( 'data-o_' + attr ) ) { if ( undefined === this.attr( 'data-o_' + attr ) ) {
this.attr( 'data-o_' + attr, ( ! this.attr( attr ) ) ? '' : this.attr( attr ) ); this.attr( 'data-o_' + attr, ( ! this.attr( attr ) ) ? '' : this.attr( attr ) );
} }
this.attr( attr, value ); if ( false === value ) {
this.removeAttr( attr );
} else {
this.attr( attr, value );
}
}; };
/** /**
@ -492,24 +496,18 @@
$product_img_wrap = $product.find( '.woocommerce-product-gallery__wrapper .woocommerce-product-gallery__image:eq(0)' ), $product_img_wrap = $product.find( '.woocommerce-product-gallery__wrapper .woocommerce-product-gallery__image:eq(0)' ),
$product_img = $product.find( '.woocommerce-product-gallery__wrapper .woocommerce-product-gallery__image:eq(0) .wp-post-image' ); $product_img = $product.find( '.woocommerce-product-gallery__wrapper .woocommerce-product-gallery__image:eq(0) .wp-post-image' );
if ( variation && variation.image_src && variation.image_src.length > 1 ) { if ( variation && variation.image && variation.image.src.length > 1 ) {
$product_img.wc_set_variation_attr( 'src', variation.image_src ); $product_img.wc_set_variation_attr( 'src', variation.image.src );
$product_img.wc_set_variation_attr( 'height', variation.image_h ); $product_img.wc_set_variation_attr( 'height', variation.image.src_h );
$product_img.wc_set_variation_attr( 'width', variation.image_w ); $product_img.wc_set_variation_attr( 'width', variation.image.src_w );
$product_img.wc_set_variation_attr( 'srcset', variation.image_srcset ); $product_img.wc_set_variation_attr( 'srcset', variation.image.srcset );
$product_img.wc_set_variation_attr( 'sizes', variation.image_sizes ); $product_img.wc_set_variation_attr( 'sizes', variation.image.sizes );
$product_img.wc_set_variation_attr( 'title', variation.image_title ); $product_img.wc_set_variation_attr( 'title', variation.image.title );
$product_img.wc_set_variation_attr( 'alt', variation.image_alt ); $product_img.wc_set_variation_attr( 'alt', variation.image.alt );
$gallery_img.wc_set_variation_attr( 'src', variation.image_src ); $product_img.wc_set_variation_attr( 'data-large-image', variation.image.full_src );
$product_img.wc_set_variation_attr( 'data-large-image', variation.image_link ); $product_img.wc_set_variation_attr( 'data-large-image-width', variation.image.full_src_w );
$product_img.wc_set_variation_attr( 'data-large-image-width', variation.image_link_h ); $product_img.wc_set_variation_attr( 'data-large-image-height', variation.image.full_src_h );
$product_img.wc_set_variation_attr( 'data-large-image-height', variation.image_link_w ); $product_img_wrap.wc_set_variation_attr( 'data-thumb', variation.image.src );
$product_img_wrap.wc_set_variation_attr( 'data-thumb', variation.image_thumbnail_src );
window.setTimeout( function() {
$( window ).trigger( 'resize' );
$gallery_img.click();
}, 10 );
} else { } else {
$product_img_wrap.wc_reset_variation_attr( 'data-thumb' ); $product_img_wrap.wc_reset_variation_attr( 'data-thumb' );
$product_img.wc_reset_variation_attr( 'large-image' ); $product_img.wc_reset_variation_attr( 'large-image' );

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -101,6 +101,16 @@ abstract class WC_Data {
$this->default_data = $this->data; $this->default_data = $this->data;
} }
/**
* Get the data store.
*
* @since 2.7.0
* @return object
*/
public function get_data_store() {
return $this->data_store;
}
/** /**
* Returns the unique ID for this object. * Returns the unique ID for this object.
* @return int * @return int
@ -476,7 +486,7 @@ abstract class WC_Data {
* @param array $props Key value pairs to set. Key is the prop and should map to a setter function name. * @param array $props Key value pairs to set. Key is the prop and should map to a setter function name.
* @return WP_Error|bool * @return WP_Error|bool
*/ */
public function set_props( $props ) { public function set_props( $props, $context = 'set' ) {
$errors = new WP_Error(); $errors = new WP_Error();
foreach ( $props as $prop => $value ) { foreach ( $props as $prop => $value ) {
@ -513,7 +523,9 @@ abstract class WC_Data {
protected function set_prop( $prop, $value ) { protected function set_prop( $prop, $value ) {
if ( array_key_exists( $prop, $this->data ) ) { if ( array_key_exists( $prop, $this->data ) ) {
if ( true === $this->object_read ) { if ( true === $this->object_read ) {
$this->changes[ $prop ] = $value; if ( $value !== $this->data[ $prop ] || array_key_exists( $prop, $this->changes ) ) {
$this->changes[ $prop ] = $value;
}
} else { } else {
$this->data[ $prop ] = $value; $this->data[ $prop ] = $value;
} }

View File

@ -151,10 +151,10 @@ abstract class WC_Abstract_Legacy_Order extends WC_Data {
// Handly qty if set // Handly qty if set
if ( isset( $args['qty'] ) ) { if ( isset( $args['qty'] ) ) {
if ( $product->backorders_require_notification() && $product->is_on_backorder( $args['qty'] ) ) { if ( $product->backorders_require_notification() && $product->is_on_backorder( $args['qty'] ) ) {
$item->add_meta_data( apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $args['qty'] - max( 0, $product->get_total_stock() ), true ); $item->add_meta_data( apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $args['qty'] - max( 0, $product->get_stock_quantity() ), true );
} }
$args['subtotal'] = $args['subtotal'] ? $args['subtotal'] : $product->get_price_excluding_tax( $args['qty'] ); $args['subtotal'] = $args['subtotal'] ? $args['subtotal'] : wc_get_price_excluding_tax( $product, array( 'qty' => $args['qty'] ) );
$args['total'] = $args['total'] ? $args['total'] : $product->get_price_excluding_tax( $args['qty'] ); $args['total'] = $args['total'] ? $args['total'] : wc_get_price_excluding_tax( $product, array( 'qty' => $args['qty'] ) );
} }
$item->set_order_id( $this->get_id() ); $item->set_order_id( $this->get_id() );

View File

@ -0,0 +1,735 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Legacy Abstract Product
*
* Legacy and deprecated functions are here to keep the WC_Abstract_Product
* clean.
* This class will be removed in future versions.
*
* @version 2.7.0
* @package WooCommerce/Abstracts
* @category Abstract Class
* @author WooThemes
*/
abstract class WC_Abstract_Legacy_Product extends WC_Data {
/**
* The product's type (simple, variable etc).
* @deprecated 2.7.0 get_type() method should return string instead since this prop should not be changed or be public.
* @var string
*/
public $product_type = 'simple';
/**
* Magic __isset method for backwards compatibility. Legacy properties which could be accessed directly in the past.
*
* @param string $key Key name.
* @return bool
*/
public function __isset( $key ) {
return
in_array( $key, array_merge( array(
'variation_id',
'variation_data',
'variation_has_stock',
'variation_shipping_class_id',
'product_attributes',
'visibility',
'sale_price_dates_from',
'sale_price_dates_to',
'post',
'download_type',
'product_image_gallery',
'variation_shipping_class',
'shipping_class',
'total_stock',
'crosssell_ids',
'parent',
), array_keys( $this->data ) ) ) || metadata_exists( 'post', $this->get_id(), '_' . $key ) || metadata_exists( 'post', $this->get_parent_id(), '_' . $key );
}
/**
* Magic __get method for backwards compatibility. Maps legacy vars to new getters.
*
* @param string $key Key name.
* @return mixed
*/
public function __get( $key ) {
if ( 'post_type' === $key ) {
return $this->post_type;
}
_doing_it_wrong( $key, __( 'Product properties should not be accessed directly.', 'woocommerce' ), '2.7' );
switch ( $key ) {
case 'id' :
$value = $this->is_type( 'variation' ) ? $this->get_parent_id() : $this->get_id();
break;
case 'variation_id' :
$value = $this->get_id();
break;
case 'product_attributes' :
$value = isset( $this->data['attributes'] ) ? $this->data['attributes'] : '';
break;
case 'visibility' :
$value = $this->get_catalog_visibility();
break;
case 'sale_price_dates_from' :
$value = $this->get_date_on_sale_from();
break;
case 'sale_price_dates_to' :
$value = $this->get_date_on_sale_to();
break;
case 'post' :
$value = get_post( $this->get_id() );
break;
case 'download_type' :
return 'standard';
break;
case 'product_image_gallery' :
$value = $this->get_gallery_image_ids();
break;
case 'variation_shipping_class' :
case 'shipping_class' :
$value = $this->get_shipping_class();
break;
case 'total_stock' :
$value = $this->get_total_stock();
break;
case 'downloadable' :
case 'virtual' :
case 'manage_stock' :
case 'featured' :
case 'sold_individually' :
$value = $this->{"get_$key"}() ? 'yes' : 'no';
break;
case 'crosssell_ids' :
$value = $this->get_cross_sell_ids();
break;
case 'upsell_ids' :
$value = $this->get_upsell_ids();
break;
case 'parent' :
$value = wc_get_product( $this->get_parent_id() );
break;
case 'variation_data' :
$value = wc_get_product_variation_attributes( $this->get_id() );
break;
case 'variation_has_stock' :
$value = $this->managing_stock();
break;
case 'variation_shipping_class_id' :
$value = $this->get_shipping_class_id();
break;
default :
if ( in_array( $key, array_keys( $this->data ) ) ) {
$value = $this->{"get_$key"}();
} else {
$value = get_post_meta( $this->id, '_' . $key, true );
}
break;
}
return $value;
}
/**
* If set, get the default attributes for a variable product.
*
* @deprecated 2.7.0
* @return array
*/
public function get_variation_default_attributes() {
_deprecated_function( 'WC_Product_Variable::get_variation_default_attributes', '2.7', 'WC_Product::get_default_attributes' );
return apply_filters( 'woocommerce_product_default_attributes', array_filter( (array) maybe_unserialize( $this->get_default_attributes() ) ), $this );
}
/**
* Returns the gallery attachment ids.
*
* @deprecated 2.7.0
* @return array
*/
public function get_gallery_attachment_ids() {
_deprecated_function( 'WC_Product::get_gallery_attachment_ids', '2.7', 'WC_Product::get_gallery_image_ids' );
return $this->get_gallery_image_ids();
}
/**
* Set stock level of the product.
*
* @deprecated 2.7.0
*/
public function set_stock( $amount = null, $mode = 'set' ) {
_deprecated_function( 'WC_Product::set_stock', '2.7', 'wc_update_product_stock' );
return wc_update_product_stock( $this, $amount, $mode );
}
/**
* Reduce stock level of the product.
*
* @deprecated 2.7.0
* @param int $amount Amount to reduce by. Default: 1
* @return int new stock level
*/
public function reduce_stock( $amount = 1 ) {
_deprecated_function( 'WC_Product::reduce_stock', '2.7', 'wc_update_product_stock' );
wc_update_product_stock( $this, $amount, 'decrease' );
}
/**
* Increase stock level of the product.
*
* @deprecated 2.7.0
* @param int $amount Amount to increase by. Default 1.
* @return int new stock level
*/
public function increase_stock( $amount = 1 ) {
_deprecated_function( 'WC_Product::increase_stock', '2.7', 'wc_update_product_stock' );
wc_update_product_stock( $this, $amount, 'increase' );
}
/**
* Check if the stock status needs changing.
*
* @deprecated 2.7.0 Sync is done automatically on read/save, so calling this should not be needed any more.
*/
public function check_stock_status() {
_deprecated_function( 'WC_Product::check_stock_status', '2.7' );
}
/**
* Returns the availability of the product.
*
* @deprecated 2.7.0
* @return string
*/
public function get_availability() {
_deprecated_function( 'WC_Product::get_availability', '2.7', 'Handled in stock.php template file and wc_format_stock_for_display function.' );
return apply_filters( 'woocommerce_get_availability', array(
'availability' => $this->get_availability_text(),
'class' => $this->get_availability_class(),
), $this );
}
/**
* Get availability text based on stock status.
*
* @deprecated 2.7.0
* @return string
*/
protected function get_availability_text() {
_deprecated_function( 'WC_Product::get_availability_text', '2.7' );
if ( ! $this->is_in_stock() ) {
$availability = __( 'Out of stock', 'woocommerce' );
} elseif ( $this->managing_stock() && $this->is_on_backorder( 1 ) ) {
$availability = $this->backorders_require_notification() ? __( 'Available on backorder', 'woocommerce' ) : __( 'In stock', 'woocommerce' );
} elseif ( $this->managing_stock() ) {
switch ( get_option( 'woocommerce_stock_format' ) ) {
case 'no_amount' :
$availability = __( 'In stock', 'woocommerce' );
break;
case 'low_amount' :
if ( $this->get_total_stock() <= get_option( 'woocommerce_notify_low_stock_amount' ) ) {
$availability = sprintf( __( 'Only %s left in stock', 'woocommerce' ), $this->get_total_stock() );
if ( $this->backorders_allowed() && $this->backorders_require_notification() ) {
$availability .= ' ' . __( '(also available on backorder)', 'woocommerce' );
}
} else {
$availability = __( 'In stock', 'woocommerce' );
}
break;
default :
$availability = sprintf( __( '%s in stock', 'woocommerce' ), $this->get_total_stock() );
if ( $this->backorders_allowed() && $this->backorders_require_notification() ) {
$availability .= ' ' . __( '(also available on backorder)', 'woocommerce' );
}
break;
}
} else {
$availability = '';
}
return apply_filters( 'woocommerce_get_availability_text', $availability, $this );
}
/**
* Get availability classname based on stock status.
*
* @deprecated 2.7.0
* @return string
*/
protected function get_availability_class() {
_deprecated_function( 'WC_Product::get_availability_class', '2.7' );
if ( ! $this->is_in_stock() ) {
$class = 'out-of-stock';
} elseif ( $this->managing_stock() && $this->is_on_backorder( 1 ) && $this->backorders_require_notification() ) {
$class = 'available-on-backorder';
} else {
$class = 'in-stock';
}
return apply_filters( 'woocommerce_get_availability_class', $class, $this );
}
/**
* Get and return related products.
* @deprecated 2.7.0 Use wc_get_related_products instead.
*/
public function get_related( $limit = 5 ) {
_deprecated_function( 'WC_Product::get_related', '2.7', 'wc_get_related_products' );
return wc_get_related_products( $this->get_id(), $limit );
}
/**
* Retrieves related product terms.
* @deprecated 2.7.0 Use wc_get_product_term_ids instead.
*/
protected function get_related_terms( $term ) {
_deprecated_function( 'WC_Product::get_related_terms', '2.7', 'wc_get_product_term_ids' );
return array_merge( array( 0 ), wc_get_product_term_ids( $this->get_id(), $term ) );
}
/**
* Builds the related posts query.
* @deprecated 2.7.0 Use Product Data Store get_related_products_query instead.
*/
protected function build_related_query( $cats_array, $tags_array, $exclude_ids, $limit ) {
_deprecated_function( 'WC_Product::build_related_query', '2.7', 'Product Data Store get_related_products_query' );
$data_store = WC_Data_Store::load( 'product' );
return $data_store->get_related_products_query( $cats_array, $tags_array, $exclude_ids, $limit );
}
/**
* Returns the child product.
* @deprecated 2.7.0 Use wc_get_product instead.
* @param mixed $child_id
* @return WC_Product|WC_Product|WC_Product_variation
*/
public function get_child( $child_id ) {
_deprecated_function( 'WC_Product::get_child', '2.7', 'wc_get_product' );
return wc_get_product( $child_id );
}
/**
* Functions for getting parts of a price, in html, used by get_price_html.
*
* @deprecated 2.7.0
* @return string
*/
public function get_price_html_from_text() {
_deprecated_function( 'WC_Product::get_price_html_from_text', '2.7' );
$from = '<span class="from">' . _x( 'From:', 'min_price', 'woocommerce' ) . ' </span>';
return apply_filters( 'woocommerce_get_price_html_from_text', $from, $this );
}
/**
* Functions for getting parts of a price, in html, used by get_price_html.
*
* @deprecated 2.7.0 Use wc_format_sale_price instead.
* @param string $from String or float to wrap with 'from' text
* @param mixed $to String or float to wrap with 'to' text
* @return string
*/
public function get_price_html_from_to( $from, $to ) {
_deprecated_function( 'WC_Product::get_price_html_from_to', '2.7', 'wc_format_sale_price' );
return apply_filters( 'woocommerce_get_price_html_from_to', wc_format_sale_price( $from, $to ), $from, $to, $this );
}
/**
* Get the suffix to display after prices > 0.
*
* @deprecated 2.7.0 Use wc_get_price_suffix instead.
* @param string $price to calculate, left blank to just use get_price()
* @param integer $qty passed on to get_price_including_tax() or get_price_excluding_tax()
* @return string
*/
public function get_price_suffix( $price = '', $qty = 1 ) {
_deprecated_function( 'WC_Product::get_price_suffix', '2.7', 'wc_get_price_suffix' );
return wc_get_price_suffix( $this, $price, $qty );
}
/**
* Lists a table of attributes for the product page.
* @deprecated 2.7.0 Use wc_display_product_attributes instead.
*/
public function list_attributes() {
_deprecated_function( 'WC_Product::list_attributes', '2.7', 'wc_display_product_attributes' );
wc_display_product_attributes( $this );
}
/**
* Returns the price (including tax). Uses customer tax rates. Can work for a specific $qty for more accurate taxes.
*
* @deprecated 2.7.0 Use wc_get_price_including_tax instead.
* @param int $qty
* @param string $price to calculate, left blank to just use get_price()
* @return string
*/
public function get_price_including_tax( $qty = 1, $price = '' ) {
_deprecated_function( 'WC_Product::get_price_including_tax', '2.7', 'wc_get_price_including_tax' );
return wc_get_price_including_tax( $this, array( 'qty' => $qty, 'price' => $price ) );
}
/**
* Returns the price including or excluding tax, based on the 'woocommerce_tax_display_shop' setting.
*
* @deprecated 2.7.0 Use wc_get_price_to_display instead.
* @param string $price to calculate, left blank to just use get_price()
* @param integer $qty passed on to get_price_including_tax() or get_price_excluding_tax()
* @return string
*/
public function get_display_price( $price = '', $qty = 1 ) {
_deprecated_function( 'WC_Product::get_display_price', '2.7', 'wc_get_price_to_display' );
return wc_get_price_to_display( $this, array( 'qty' => $qty, 'price' => $price ) );
}
/**
* Returns the price (excluding tax) - ignores tax_class filters since the price may *include* tax and thus needs subtracting.
* Uses store base tax rates. Can work for a specific $qty for more accurate taxes.
*
* @deprecated 2.7.0 Use wc_get_price_excluding_tax instead.
* @param int $qty
* @param string $price to calculate, left blank to just use get_price()
* @return string
*/
public function get_price_excluding_tax( $qty = 1, $price = '' ) {
_deprecated_function( 'WC_Product::get_price_excluding_tax', '2.7', 'wc_get_price_excluding_tax' );
return wc_get_price_excluding_tax( $this, array( 'qty' => $qty, 'price' => $price ) );
}
/**
* Adjust a products price dynamically.
*
* @deprecated 2.7.0
* @param mixed $price
*/
public function adjust_price( $price ) {
_deprecated_function( 'WC_Product::adjust_price', '2.7', 'WC_Product::set_price / WC_Product::get_price' );
$this->data['price'] = $this->data['price'] + $price;
}
/**
* Returns the product categories.
*
* @deprecated 2.7.0
* @param string $sep (default: ', ').
* @param string $before (default: '').
* @param string $after (default: '').
* @return string
*/
public function get_categories( $sep = ', ', $before = '', $after = '' ) {
_deprecated_function( 'WC_Product::get_categories', '2.7', 'wc_get_product_category_list' );
return wc_get_product_category_list( $this->get_id(), $sep, $before, $after );
}
/**
* Returns the product tags.
*
* @deprecated 2.7.0
* @param string $sep (default: ', ').
* @param string $before (default: '').
* @param string $after (default: '').
* @return array
*/
public function get_tags( $sep = ', ', $before = '', $after = '' ) {
_deprecated_function( 'WC_Product::get_tags', '2.7', 'wc_get_product_tag_list' );
return wc_get_product_tag_list( $this->get_id(), $sep, $before, $after );
}
/**
* Get the product's post data.
*
* @deprecated 2.7.0
* @return WP_Post
*/
public function get_post_data() {
_deprecated_function( 'WC_Product::get_post_data', '2.7', 'get_post' );
return get_post( $this->get_id() );
}
/**
* Get the title of the post.
*
* @deprecated 2.7.0
* @return string
*/
public function get_title() {
return apply_filters( 'woocommerce_product_title', $this->get_post_data() ? $this->get_post_data()->post_title : '', $this );
}
/**
* Get the parent of the post.
*
* @deprecated 2.7.0
* @return int
*/
public function get_parent() {
_deprecated_function( 'WC_Product::get_parent', '2.7', 'WC_Product::get_parent_id' );
return apply_filters( 'woocommerce_product_parent', absint( $this->get_post_data()->post_parent ), $this );
}
/**
* Returns the upsell product ids.
*
* @deprecated 2.7.0
* @return array
*/
public function get_upsells() {
_deprecated_function( 'WC_Product::get_upsells', '2.7', 'WC_Product::get_upsell_ids' );
return apply_filters( 'woocommerce_product_upsell_ids', $this->get_upsell_ids(), $this );
}
/**
* Returns the cross sell product ids.
*
* @deprecated 2.7.0
* @return array
*/
public function get_cross_sells() {
_deprecated_function( 'WC_Product::get_cross_sells', '2.7', 'WC_Product::get_cross_sell_ids' );
return apply_filters( 'woocommerce_product_crosssell_ids', $this->get_cross_sell_ids(), $this );
}
/**
* Check if variable product has default attributes set.
*
* @deprecated 2.7.0
* @return bool
*/
public function has_default_attributes() {
_deprecated_function( 'WC_Product_Variable::has_default_attributes', '2.7', 'Check WC_Product::get_default_attributes directly' );
if ( ! $this->get_default_attributes() ) {
return true;
}
return false;
}
/**
* Get variation ID.
*
* @deprecated 2.7.0
* @return int
*/
public function get_variation_id() {
_deprecated_function( 'WC_Product::get_variation_id', '2.7', 'WC_Product::get_id() will always be the variation ID if this is a variation.' );
return $this->get_id();
}
/**
* Get product variation description.
*
* @deprecated 2.7.0
* @return string
*/
public function get_variation_description() {
_deprecated_function( 'WC_Product::get_variation_description', '2.7', 'WC_Product::get_description()' );
return $this->get_description();
}
/**
* Check if all variation's attributes are set.
*
* @deprecated 2.7.0
* @return boolean
*/
public function has_all_attributes_set() {
_deprecated_function( 'WC_Product::has_all_attributes_set', '2.7' );
$set = true;
// undefined attributes have null strings as array values
foreach ( $this->get_variation_attributes() as $att ) {
if ( ! $att ) {
$set = false;
break;
}
}
return $set;
}
/**
* Returns whether or not the variations parent is visible.
*
* @deprecated 2.7.0
* @return bool
*/
public function parent_is_visible() {
_deprecated_function( 'WC_Product::parent_is_visible', '2.7' );
return $this->is_visible();
}
/**
* Get total stock - This is the stock of parent and children combined.
*
* @deprecated 2.7.0
* @return int
*/
public function get_total_stock() {
_deprecated_function( 'WC_Product::get_total_stock', '2.7', 'Use get_stock_quantity on each child. Beware of performance issues in doing so.' );
if ( sizeof( $this->get_children() ) > 0 ) {
$total_stock = max( 0, $this->get_stock_quantity() );
foreach ( $this->get_children() as $child_id ) {
if ( 'yes' === get_post_meta( $child_id, '_manage_stock', true ) ) {
$stock = get_post_meta( $child_id, '_stock', true );
$total_stock += max( 0, wc_stock_amount( $stock ) );
}
}
} else {
$total_stock = $this->get_stock_quantity();
}
return wc_stock_amount( $total_stock );
}
/**
* Get formatted variation data with WC < 2.4 back compat and proper formatting of text-based attribute names.
*
* @deprecated 2.7.0
* @return string
*/
public function get_formatted_variation_attributes( $flat = false ) {
_deprecated_function( 'WC_Product::get_formatted_variation_attributes', '2.7', 'wc_get_formatted_variation' );
return wc_get_formatted_variation( $this->get_variation_attributes(), $flat );
}
/**
* Sync variable product prices with the children lowest/highest prices.
*
* @deprecated 2.7.0 not used in core.
*/
public function variable_product_sync( $product_id = '' ) {
_deprecated_function( 'WC_Product::variable_product_sync', '2.7' );
if ( empty( $product_id ) ) {
$product_id = $this->get_id();
}
// Sync prices with children
self::sync( $product_id );
// Re-load prices
$this->read_product_data();
}
/**
* Sync the variable product's attributes with the variations.
*/
public static function sync_attributes( $product, $children = false ) {
if ( ! is_a( $product, 'WC_Product' ) ) {
$product = wc_get_product( $product );
}
/**
* Pre 2.4 handling where 'slugs' were saved instead of the full text attribute.
* Attempt to get full version of the text attribute from the parent and UPDATE meta.
*/
if ( version_compare( get_post_meta( $product->get_id(), '_product_version', true ), '2.4.0', '<' ) ) {
$parent_attributes = array_filter( (array) get_post_meta( $product->get_id(), '_product_attributes', true ) );
if ( ! $children ) {
$children = $product->get_children( 'edit' );
}
foreach ( $children as $child_id ) {
$all_meta = get_post_meta( $child_id );
foreach ( $all_meta as $name => $value ) {
if ( 0 !== strpos( $name, 'attribute_' ) ) {
continue;
}
if ( sanitize_title( $value[0] ) === $value[0] ) {
foreach ( $parent_attributes as $attribute ) {
if ( 'attribute_' . sanitize_title( $attribute['name'] ) !== $name ) {
continue;
}
$text_attributes = wc_get_text_attributes( $attribute['value'] );
foreach ( $text_attributes as $text_attribute ) {
if ( sanitize_title( $text_attribute ) === $value[0] ) {
update_post_meta( $child_id, $name, $text_attribute );
break;
}
}
}
}
}
}
}
}
/**
* Match a variation to a given set of attributes using a WP_Query.
* @deprecated 2.7.0 in favour of Product data store's find_matching_product_variation.
*/
public function get_matching_variation( $match_attributes = array() ) {
_deprecated_function( 'WC_Product::get_matching_variation', '2.7', 'Product data store find_matching_product_variation' );
$data_store = WC_Data_Store::load( 'product' );
return $data_store->find_matching_product_variation( $this, $match_attributes );
}
/**
* Returns whether or not we are showing dimensions on the product page.
* @deprecated 2.7.0 Unused.
* @return bool
*/
public function enable_dimensions_display() {
_deprecated_function( 'WC_Product::enable_dimensions_display', '2.7' );
return apply_filters( 'wc_product_enable_dimensions_display', true ) && ( $this->has_dimensions() || $this->has_weight() || $this->child_has_weight() || $this->child_has_dimensions() );
}
/**
* Returns the product rating in html format.
*
* @deprecated 2.7.0
* @param string $rating (default: '')
* @return string
*/
public function get_rating_html( $rating = null ) {
_deprecated_function( 'WC_Product::get_rating_html', '2.7', 'wc_get_rating_html' );
return wc_get_rating_html( $rating );
}
/**
* Sync product rating. Can be called statically.
*
* @deprecated 2.7.0
* @param int $post_id
*/
public static function sync_average_rating( $post_id ) {
_deprecated_function( 'WC_Product::sync_average_rating', '2.7', 'WC_Comments::get_average_rating_for_product or leave to CRUD.' );
$average = WC_Comments::get_average_rating_for_product( wc_get_product( $post_id ) );
update_post_meta( $post_id, '_wc_average_rating', $average );
}
/**
* Sync product rating count. Can be called statically.
*
* @deprecated 2.7.0
* @param int $post_id
*/
public static function sync_rating_count( $post_id ) {
_deprecated_function( 'WC_Product::sync_rating_count', '2.7', 'WC_Comments::get_rating_counts_for_product or leave to CRUD.' );
$counts = WC_Comments::get_rating_counts_for_product( wc_get_product( $post_id ) );
update_post_meta( $post_id, '_wc_rating_count', $counts );
}
/**
* Same as get_downloads in CRUD.
*
* @deprecated 2.7.0
* @return array
*/
public function get_files() {
wc_soft_deprecated_function( 'WC_Product::get_files', '2.7', '2.8', 'WC_Product::get_downloads' );
return $this->get_downloads();
}
/**
* @deprected 2.7.0 Sync is taken care of during save - no need to call this directly.
*/
public function grouped_product_sync() {
_deprecated_function( 'WC_Product::grouped_product_sync', '2.7' );
}
}

View File

@ -978,13 +978,13 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
public function add_product( $product, $qty = 1, $args = array() ) { public function add_product( $product, $qty = 1, $args = array() ) {
if ( $product ) { if ( $product ) {
$default_args = array( $default_args = array(
'name' => $product->get_title(), 'name' => $product->get_name(),
'tax_class' => $product->get_tax_class(), 'tax_class' => $product->get_tax_class(),
'product_id' => $product->get_id(), 'product_id' => $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id(),
'variation_id' => isset( $product->variation_id ) ? $product->variation_id : 0, 'variation_id' => $product->is_type( 'variation' ) ? $product->get_id() : 0,
'variation' => isset( $product->variation_id ) ? $product->get_variation_attributes() : array(), 'variation' => $product->is_type( 'variation' ) ? $product->get_attributes() : array(),
'subtotal' => $product->get_price_excluding_tax( $qty ), 'subtotal' => wc_get_price_excluding_tax( $product, array( 'qty' => $qty ) ),
'total' => $product->get_price_excluding_tax( $qty ), 'total' => wc_get_price_excluding_tax( $product, array( 'qty' => $qty ) ),
'quantity' => $qty, 'quantity' => $qty,
); );
} else { } else {
@ -1164,7 +1164,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
$shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' ); $shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
// Inherit tax class from items // Inherit tax class from items
if ( '' === $shipping_tax_class ) { if ( 'inherit' === $shipping_tax_class ) {
$tax_rates = array(); $tax_rates = array();
$tax_classes = array_merge( array( '' ), WC_Tax::get_tax_classes() ); $tax_classes = array_merge( array( '' ), WC_Tax::get_tax_classes() );
$found_tax_classes = $this->get_items_tax_classes(); $found_tax_classes = $this->get_items_tax_classes();
@ -1188,7 +1188,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
'state' => $args['state'], 'state' => $args['state'],
'postcode' => $args['postcode'], 'postcode' => $args['postcode'],
'city' => $args['city'], 'city' => $args['city'],
'tax_class' => 'standard' === $shipping_tax_class ? '' : $shipping_tax_class, 'tax_class' => $shipping_tax_class,
) ); ) );
} }

File diff suppressed because it is too large Load Diff

View File

@ -274,7 +274,7 @@ abstract class WC_Shipping_Method extends WC_Settings_API {
$items_in_package = array(); $items_in_package = array();
foreach ( $args['package']['contents'] as $item ) { foreach ( $args['package']['contents'] as $item ) {
$product = $item['data']; $product = $item['data'];
$items_in_package[] = $product->get_title() . ' &times; ' . $item['quantity']; $items_in_package[] = $product->get_name() . ' &times; ' . $item['quantity'];
} }
$rate->add_meta_data( __( 'Items', 'woocommerce' ), implode( ', ', $items_in_package ) ); $rate->add_meta_data( __( 'Items', 'woocommerce' ), implode( ', ', $items_in_package ) );
} }

View File

@ -218,7 +218,6 @@ class WC_Admin_Duplicate_Product {
* *
* @param mixed $id * @param mixed $id
* @return WP_Post|bool * @return WP_Post|bool
* @todo Returning false? Need to check for it in...
* @see duplicate_product * @see duplicate_product
*/ */
private function get_product_to_duplicate( $id ) { private function get_product_to_duplicate( $id ) {

View File

@ -71,13 +71,6 @@ class WC_Admin_Post_Types {
add_filter( 'parse_query', array( $this, 'product_filters_query' ) ); add_filter( 'parse_query', array( $this, 'product_filters_query' ) );
add_filter( 'posts_search', array( $this, 'product_search' ) ); add_filter( 'posts_search', array( $this, 'product_search' ) );
// Status transitions
add_action( 'delete_post', array( $this, 'delete_post' ) );
add_action( 'wp_trash_post', array( $this, 'trash_post' ) );
add_action( 'untrash_post', array( $this, 'untrash_post' ) );
add_action( 'before_delete_post', array( $this, 'delete_order_items' ) );
add_action( 'before_delete_post', array( $this, 'delete_order_downloadable_permissions' ) );
// Edit post screens // Edit post screens
add_filter( 'enter_title_here', array( $this, 'enter_title_here' ), 1, 2 ); add_filter( 'enter_title_here', array( $this, 'enter_title_here' ), 1, 2 );
add_action( 'edit_form_after_title', array( $this, 'edit_form_after_title' ) ); add_action( 'edit_form_after_title', array( $this, 'edit_form_after_title' ) );
@ -319,7 +312,7 @@ class WC_Admin_Post_Types {
public function render_product_columns( $column ) { public function render_product_columns( $column ) {
global $post, $the_product; global $post, $the_product;
if ( empty( $the_product ) || $the_product->id != $post->ID ) { if ( empty( $the_product ) || $the_product->get_id() != $post->ID ) {
$the_product = wc_get_product( $post ); $the_product = wc_get_product( $post );
} }
@ -353,25 +346,25 @@ class WC_Admin_Post_Types {
/* Custom inline data for woocommerce. */ /* Custom inline data for woocommerce. */
echo ' echo '
<div class="hidden" id="woocommerce_inline_' . $post->ID . '"> <div class="hidden" id="woocommerce_inline_' . $post->ID . '">
<div class="menu_order">' . $post->menu_order . '</div> <div class="menu_order">' . $the_product->get_menu_order() . '</div>
<div class="sku">' . $the_product->sku . '</div> <div class="sku">' . $the_product->get_sku() . '</div>
<div class="regular_price">' . $the_product->regular_price . '</div> <div class="regular_price">' . $the_product->get_regular_price() . '</div>
<div class="sale_price">' . $the_product->sale_price . '</div> <div class="sale_price">' . $the_product->get_sale_price() . '</div>
<div class="weight">' . $the_product->weight . '</div> <div class="weight">' . $the_product->get_weight() . '</div>
<div class="length">' . $the_product->length . '</div> <div class="length">' . $the_product->get_length() . '</div>
<div class="width">' . $the_product->width . '</div> <div class="width">' . $the_product->get_width() . '</div>
<div class="height">' . $the_product->height . '</div> <div class="height">' . $the_product->get_height() . '</div>
<div class="shipping_class">' . $the_product->get_shipping_class() . '</div> <div class="shipping_class">' . $the_product->get_shipping_class() . '</div>
<div class="visibility">' . $the_product->visibility . '</div> <div class="visibility">' . $the_product->get_catalog_visibility() . '</div>
<div class="stock_status">' . $the_product->stock_status . '</div> <div class="stock_status">' . $the_product->get_stock_status() . '</div>
<div class="stock">' . $the_product->stock . '</div> <div class="stock">' . $the_product->get_stock_quantity() . '</div>
<div class="manage_stock">' . $the_product->manage_stock . '</div> <div class="manage_stock">' . $the_product->get_manage_stock() . '</div>
<div class="featured">' . $the_product->featured . '</div> <div class="featured">' . $the_product->get_featured() . '</div>
<div class="product_type">' . $the_product->product_type . '</div> <div class="product_type">' . $the_product->get_type() . '</div>
<div class="product_is_virtual">' . $the_product->virtual . '</div> <div class="product_is_virtual">' . $the_product->get_virtual() . '</div>
<div class="tax_status">' . $the_product->tax_status . '</div> <div class="tax_status">' . $the_product->get_tax_status() . '</div>
<div class="tax_class">' . $the_product->tax_class . '</div> <div class="tax_class">' . $the_product->get_tax_class() . '</div>
<div class="backorders">' . $the_product->backorders . '</div> <div class="backorders">' . $the_product->get_backorders() . '</div>
</div> </div>
'; ';
@ -380,11 +373,11 @@ class WC_Admin_Post_Types {
echo $the_product->get_sku() ? $the_product->get_sku() : '<span class="na">&ndash;</span>'; echo $the_product->get_sku() ? $the_product->get_sku() : '<span class="na">&ndash;</span>';
break; break;
case 'product_type' : case 'product_type' :
if ( 'grouped' == $the_product->product_type ) { if ( $the_product->is_type( 'grouped' ) ) {
echo '<span class="product-type tips grouped" data-tip="' . esc_attr__( 'Grouped', 'woocommerce' ) . '"></span>'; echo '<span class="product-type tips grouped" data-tip="' . esc_attr__( 'Grouped', 'woocommerce' ) . '"></span>';
} elseif ( 'external' == $the_product->product_type ) { } elseif ( $the_product->is_type( 'external' ) ) {
echo '<span class="product-type tips external" data-tip="' . esc_attr__( 'External/Affiliate product', 'woocommerce' ) . '"></span>'; echo '<span class="product-type tips external" data-tip="' . esc_attr__( 'External/Affiliate', 'woocommerce' ) . '"></span>';
} elseif ( 'simple' == $the_product->product_type ) { } elseif ( $the_product->is_type( 'simple' ) ) {
if ( $the_product->is_virtual() ) { if ( $the_product->is_virtual() ) {
echo '<span class="product-type tips virtual" data-tip="' . esc_attr__( 'Virtual', 'woocommerce' ) . '"></span>'; echo '<span class="product-type tips virtual" data-tip="' . esc_attr__( 'Virtual', 'woocommerce' ) . '"></span>';
@ -393,11 +386,11 @@ class WC_Admin_Post_Types {
} else { } else {
echo '<span class="product-type tips simple" data-tip="' . esc_attr__( 'Simple', 'woocommerce' ) . '"></span>'; echo '<span class="product-type tips simple" data-tip="' . esc_attr__( 'Simple', 'woocommerce' ) . '"></span>';
} }
} elseif ( 'variable' == $the_product->product_type ) { } elseif ( $the_product->is_type( 'variable' ) ) {
echo '<span class="product-type tips variable" data-tip="' . esc_attr__( 'Variable', 'woocommerce' ) . '"></span>'; echo '<span class="product-type tips variable" data-tip="' . esc_attr__( 'Variable', 'woocommerce' ) . '"></span>';
} else { } else {
// Assuming that we have other types in future // Assuming that we have other types in future
echo '<span class="product-type tips ' . $the_product->product_type . '" data-tip="' . ucfirst( $the_product->product_type ) . '"></span>'; echo '<span class="product-type tips ' . $the_product->get_type() . '" data-tip="' . ucfirst( $the_product->get_type() ) . '"></span>';
} }
break; break;
case 'price' : case 'price' :
@ -434,9 +427,8 @@ class WC_Admin_Post_Types {
$stock_html = '<mark class="outofstock">' . __( 'Out of stock', 'woocommerce' ) . '</mark>'; $stock_html = '<mark class="outofstock">' . __( 'Out of stock', 'woocommerce' ) . '</mark>';
} }
// If the product has children, a single stock level would be misleading as some could be -ve and some +ve, some managed/some unmanaged etc so hide stock level in this case. if ( $the_product->managing_stock() ) {
if ( $the_product->managing_stock() && ! sizeof( $the_product->get_children() ) ) { $stock_html .= ' (' . $the_product->get_stock_quantity() . ')';
$stock_html .= ' (' . $the_product->get_total_stock() . ')';
} }
echo apply_filters( 'woocommerce_admin_stock_html', $stock_html, $the_product ); echo apply_filters( 'woocommerce_admin_stock_html', $stock_html, $the_product );
@ -690,7 +682,7 @@ class WC_Admin_Post_Types {
<td class="qty"><?php echo esc_html( $item->get_quantity() ); ?></td> <td class="qty"><?php echo esc_html( $item->get_quantity() ); ?></td>
<td class="name"> <td class="name">
<?php if ( $product ) : ?> <?php if ( $product ) : ?>
<?php echo ( wc_product_sku_enabled() && $product->get_sku() ) ? $product->get_sku() . ' - ' : ''; ?><a href="<?php echo get_edit_post_link( $product->id ); ?>"><?php echo apply_filters( 'woocommerce_order_item_name', $item->get_name(), $item, false ); ?></a> <?php echo ( wc_product_sku_enabled() && $product->get_sku() ) ? $product->get_sku() . ' - ' : ''; ?><a href="<?php echo get_edit_post_link( $product->get_id() ); ?>"><?php echo apply_filters( 'woocommerce_order_item_name', $item->get_name(), $item, false ); ?></a>
<?php else : ?> <?php else : ?>
<?php echo apply_filters( 'woocommerce_order_item_name', $item->get_name(), $item, false ); ?> <?php echo apply_filters( 'woocommerce_order_item_name', $item->get_name(), $item, false ); ?>
<?php endif; ?> <?php endif; ?>
@ -1080,7 +1072,7 @@ class WC_Admin_Post_Types {
} }
/** /**
* Quick edit. * Quick edit. @todo CRUDIFY
* *
* @param integer $post_id * @param integer $post_id
* @param WC_Product $product * @param WC_Product $product
@ -1923,180 +1915,6 @@ class WC_Admin_Post_Types {
} }
} }
/**
* Removes variations etc belonging to a deleted post, and clears transients.
*
* @param mixed $id ID of post being deleted
*/
public function delete_post( $id ) {
global $woocommerce, $wpdb;
if ( ! current_user_can( 'delete_posts' ) ) {
return;
}
if ( $id > 0 ) {
$post_type = get_post_type( $id );
switch ( $post_type ) {
case 'product' :
$child_product_variations = get_children( 'post_parent=' . $id . '&post_type=product_variation' );
if ( ! empty( $child_product_variations ) ) {
foreach ( $child_product_variations as $child ) {
wp_delete_post( $child->ID, true );
}
}
$child_products = get_children( 'post_parent=' . $id . '&post_type=product' );
if ( ! empty( $child_products ) ) {
foreach ( $child_products as $child ) {
$child_post = array();
$child_post['ID'] = $child->ID;
$child_post['post_parent'] = 0;
wp_update_post( $child_post );
}
}
if ( $parent_id = wp_get_post_parent_id( $id ) ) {
wc_delete_product_transients( $parent_id );
}
break;
case 'product_variation' :
wc_delete_product_transients( wp_get_post_parent_id( $id ) );
break;
case 'shop_order' :
$refunds = $wpdb->get_results( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type = 'shop_order_refund' AND post_parent = %d", $id ) );
if ( ! is_null( $refunds ) ) {
foreach ( $refunds as $refund ) {
wp_delete_post( $refund->ID, true );
}
}
break;
}
}
}
/**
* woocommerce_trash_post function.
*
* @param mixed $id
*/
public function trash_post( $id ) {
global $wpdb;
if ( $id > 0 ) {
$post_type = get_post_type( $id );
if ( in_array( $post_type, wc_get_order_types( 'order-count' ) ) ) {
// Delete count - meta doesn't work on trashed posts
$user_id = get_post_meta( $id, '_customer_user', true );
if ( $user_id > 0 ) {
delete_user_meta( $user_id, '_money_spent' );
delete_user_meta( $user_id, '_order_count' );
}
$refunds = $wpdb->get_results( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type = 'shop_order_refund' AND post_parent = %d", $id ) );
foreach ( $refunds as $refund ) {
$wpdb->update( $wpdb->posts, array( 'post_status' => 'trash' ), array( 'ID' => $refund->ID ) );
}
delete_transient( 'woocommerce_processing_order_count' );
wc_delete_shop_order_transients( $id );
}
}
}
/**
* woocommerce_untrash_post function.
*
* @param mixed $id
*/
public function untrash_post( $id ) {
global $wpdb;
if ( $id > 0 ) {
$post_type = get_post_type( $id );
if ( in_array( $post_type, wc_get_order_types( 'order-count' ) ) ) {
// Delete count - meta doesn't work on trashed posts
$user_id = get_post_meta( $id, '_customer_user', true );
if ( $user_id > 0 ) {
delete_user_meta( $user_id, '_money_spent' );
delete_user_meta( $user_id, '_order_count' );
}
$refunds = $wpdb->get_results( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type = 'shop_order_refund' AND post_parent = %d", $id ) );
foreach ( $refunds as $refund ) {
$wpdb->update( $wpdb->posts, array( 'post_status' => 'wc-completed' ), array( 'ID' => $refund->ID ) );
}
delete_transient( 'woocommerce_processing_order_count' );
wc_delete_shop_order_transients( $id );
} elseif ( 'product' === $post_type ) {
// Check if SKU is valid before untrash the product.
$sku = get_post_meta( $id, '_sku', true );
if ( ! empty( $sku ) ) {
if ( ! wc_product_has_unique_sku( $id, $sku ) ) {
update_post_meta( $id, '_sku', '' );
}
}
}
}
}
/**
* Remove item meta on permanent deletion.
*/
public function delete_order_items( $postid ) {
global $wpdb;
if ( in_array( get_post_type( $postid ), wc_get_order_types() ) ) {
do_action( 'woocommerce_delete_order_items', $postid );
$wpdb->query( "
DELETE {$wpdb->prefix}woocommerce_order_items, {$wpdb->prefix}woocommerce_order_itemmeta
FROM {$wpdb->prefix}woocommerce_order_items
JOIN {$wpdb->prefix}woocommerce_order_itemmeta ON {$wpdb->prefix}woocommerce_order_items.order_item_id = {$wpdb->prefix}woocommerce_order_itemmeta.order_item_id
WHERE {$wpdb->prefix}woocommerce_order_items.order_id = '{$postid}';
" );
do_action( 'woocommerce_deleted_order_items', $postid );
}
}
/**
* Remove downloadable permissions on permanent order deletion.
*/
public function delete_order_downloadable_permissions( $postid ) {
global $wpdb;
if ( in_array( get_post_type( $postid ), wc_get_order_types() ) ) {
do_action( 'woocommerce_delete_order_downloadable_permissions', $postid );
$wpdb->query( $wpdb->prepare( "
DELETE FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions
WHERE order_id = %d
", $postid ) );
do_action( 'woocommerce_deleted_order_downloadable_permissions', $postid );
}
}
/** /**
* Change title boxes in admin. * Change title boxes in admin.
* @param string $text * @param string $text
@ -2248,7 +2066,7 @@ class WC_Admin_Post_Types {
} }
$product = wc_get_product( $product_id ); $product = wc_get_product( $product_id );
$existing_download_ids = array_keys( (array) $product->get_files() ); $existing_download_ids = array_keys( (array) $product->get_downloads() );
$updated_download_ids = array_keys( (array) $downloadable_files ); $updated_download_ids = array_keys( (array) $downloadable_files );
$new_download_ids = array_filter( array_diff( $updated_download_ids, $existing_download_ids ) ); $new_download_ids = array_filter( array_diff( $updated_download_ids, $existing_download_ids ) );

View File

@ -40,7 +40,7 @@ class WC_Meta_Box_Order_Downloads {
if ( $download_permissions && sizeof( $download_permissions ) > 0 ) foreach ( $download_permissions as $download ) { if ( $download_permissions && sizeof( $download_permissions ) > 0 ) foreach ( $download_permissions as $download ) {
if ( ! $product || $product->id != $download->product_id ) { if ( ! $product || $product->get_id() != $download->product_id ) {
$product = wc_get_product( absint( $download->product_id ) ); $product = wc_get_product( absint( $download->product_id ) );
$file_counter = 1; $file_counter = 1;
} }

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@ if ( ! defined( 'ABSPATH' ) ) {
<button type="button" data-permission_id="<?php echo absint( $download->permission_id ); ?>" rel="<?php echo absint( $download->product_id ) . ',' . esc_attr( $download->download_id ); ?>" class="revoke_access button"><?php _e( 'Revoke access', 'woocommerce' ); ?></button> <button type="button" data-permission_id="<?php echo absint( $download->permission_id ); ?>" rel="<?php echo absint( $download->product_id ) . ',' . esc_attr( $download->download_id ); ?>" class="revoke_access button"><?php _e( 'Revoke access', 'woocommerce' ); ?></button>
<div class="handlediv" aria-label="<?php esc_attr_e( 'Click to toggle', 'woocommerce' ); ?>"></div> <div class="handlediv" aria-label="<?php esc_attr_e( 'Click to toggle', 'woocommerce' ); ?>"></div>
<strong> <strong>
<?php echo '#' . absint( $product->id ) . ' &mdash; ' . apply_filters( 'woocommerce_admin_download_permissions_title', $product->get_title(), $download->product_id, $download->order_id, $download->order_key, $download->download_id ) . ' &mdash; ' . esc_html( $file_count ) . ': ' . wc_get_filename_from_url( $product->get_file_download_path( $download->download_id ) ) . ' &mdash; ' . sprintf( _n( 'Downloaded %s time', 'Downloaded %s times', absint( $download->download_count ), 'woocommerce' ), absint( $download->download_count ) ); ?> <?php echo '#' . absint( $product->get_id() ) . ' &mdash; ' . apply_filters( 'woocommerce_admin_download_permissions_title', $product->get_name(), $download->product_id, $download->order_id, $download->order_key, $download->download_id ) . ' &mdash; ' . esc_html( $file_count ) . ': ' . wc_get_filename_from_url( $product->get_file_download_path( $download->download_id ) ) . ' &mdash; ' . sprintf( _n( 'Downloaded %s time', 'Downloaded %s times', absint( $download->download_count ), 'woocommerce' ), absint( $download->download_count ) ); ?>
</strong> </strong>
</h3> </h3>
<table cellpadding="0" cellspacing="0" class="wc-metabox-content"> <table cellpadding="0" cellspacing="0" class="wc-metabox-content">

View File

@ -15,17 +15,9 @@ $line_items_fee = $order->get_items( 'fee' );
$line_items_shipping = $order->get_items( 'shipping' ); $line_items_shipping = $order->get_items( 'shipping' );
if ( wc_tax_enabled() ) { if ( wc_tax_enabled() ) {
$order_taxes = $order->get_taxes(); $order_taxes = $order->get_taxes();
$tax_classes = WC_Tax::get_tax_classes(); $tax_classes = WC_Tax::get_tax_classes();
$classes_options = array(); $classes_options = wc_get_product_tax_class_options();
$classes_options[''] = __( 'Standard', 'woocommerce' );
if ( ! empty( $tax_classes ) ) {
foreach ( $tax_classes as $class ) {
$classes_options[ sanitize_title( $class ) ] = $class;
}
}
$show_tax_columns = sizeof( $order_taxes ) === 1; $show_tax_columns = sizeof( $order_taxes ) === 1;
} }
?> ?>

View File

@ -1,8 +1,8 @@
<div data-taxonomy="<?php echo esc_attr( $taxonomy ); ?>" class="woocommerce_attribute wc-metabox closed <?php echo esc_attr( implode( ' ', $metabox_class ) ); ?>" rel="<?php echo $position; ?>"> <div data-taxonomy="<?php echo esc_attr( $attribute->get_taxonomy() ); ?>" class="woocommerce_attribute wc-metabox closed <?php echo esc_attr( implode( ' ', $metabox_class ) ); ?>" rel="<?php echo esc_attr( $attribute->get_position() ); ?>">
<h3> <h3>
<a href="#" class="remove_row delete"><?php _e( 'Remove', 'woocommerce' ); ?></a> <a href="#" class="remove_row delete"><?php _e( 'Remove', 'woocommerce' ); ?></a>
<div class="handlediv" aria-label="<?php esc_attr_e( 'Click to toggle', 'woocommerce' ); ?>"></div> <div class="handlediv" title="<?php esc_attr_e( 'Click to toggle', 'woocommerce' ); ?>"></div>
<strong class="attribute_name"><?php echo esc_html( $attribute_label ); ?></strong> <strong class="attribute_name"><?php echo esc_html( wc_attribute_label( $attribute->get_name() ) ); ?></strong>
</h3> </h3>
<div class="woocommerce_attribute_data wc-metabox-content"> <div class="woocommerce_attribute_data wc-metabox-content">
<table cellpadding="0" cellspacing="0"> <table cellpadding="0" cellspacing="0">
@ -11,20 +11,19 @@
<td class="attribute_name"> <td class="attribute_name">
<label><?php _e( 'Name', 'woocommerce' ); ?>:</label> <label><?php _e( 'Name', 'woocommerce' ); ?>:</label>
<?php if ( $attribute['is_taxonomy'] ) : ?> <?php if ( $attribute->is_taxonomy() ) : ?>
<strong><?php echo esc_html( $attribute_label ); ?></strong> <strong><?php echo esc_html( wc_attribute_label( $attribute->get_name() ) ); ?></strong>
<input type="hidden" name="attribute_names[<?php echo $i; ?>]" value="<?php echo esc_attr( $taxonomy ); ?>" /> <input type="hidden" name="attribute_names[<?php echo $i; ?>]" value="<?php echo esc_attr( $attribute->get_name() ); ?>" />
<?php else : ?> <?php else : ?>
<input type="text" class="attribute_name" name="attribute_names[<?php echo $i; ?>]" value="<?php echo esc_attr( $attribute['name'] ); ?>" /> <input type="text" class="attribute_name" name="attribute_names[<?php echo $i; ?>]" value="<?php echo esc_attr( $attribute->get_name() ); ?>" />
<?php endif; ?> <?php endif; ?>
<input type="hidden" name="attribute_position[<?php echo $i; ?>]" class="attribute_position" value="<?php echo esc_attr( $position ); ?>" /> <input type="hidden" name="attribute_position[<?php echo $i; ?>]" class="attribute_position" value="<?php echo esc_attr( $attribute->get_position() ); ?>" />
<input type="hidden" name="attribute_is_taxonomy[<?php echo $i; ?>]" value="<?php echo $attribute['is_taxonomy'] ? 1 : 0; ?>" />
</td> </td>
<td rowspan="3"> <td rowspan="3">
<label><?php _e( 'Value(s)', 'woocommerce' ); ?>:</label> <label><?php _e( 'Value(s)', 'woocommerce' ); ?>:</label>
<?php if ( $attribute['is_taxonomy'] ) : ?> <?php if ( $attribute->is_taxonomy() && ( $attribute_taxonomy = $attribute->get_taxonomy_object() ) ) : ?>
<?php if ( 'select' === $attribute_taxonomy->attribute_type ) : ?> <?php if ( 'select' === $attribute_taxonomy->attribute_type ) : ?>
<select multiple="multiple" data-placeholder="<?php esc_attr_e( 'Select terms', 'woocommerce' ); ?>" class="multiselect attribute_values wc-enhanced-select" name="attribute_values[<?php echo $i; ?>][]"> <select multiple="multiple" data-placeholder="<?php esc_attr_e( 'Select terms', 'woocommerce' ); ?>" class="multiselect attribute_values wc-enhanced-select" name="attribute_values[<?php echo $i; ?>][]">
@ -33,10 +32,10 @@
'orderby' => 'name', 'orderby' => 'name',
'hide_empty' => 0, 'hide_empty' => 0,
); );
$all_terms = get_terms( $taxonomy, apply_filters( 'woocommerce_product_attribute_terms', $args ) ); $all_terms = get_terms( $attribute->get_taxonomy(), apply_filters( 'woocommerce_product_attribute_terms', $args ) );
if ( $all_terms ) { if ( $all_terms ) {
foreach ( $all_terms as $term ) { foreach ( $all_terms as $term ) {
echo '<option value="' . esc_attr( $term->slug ) . '" ' . selected( has_term( absint( $term->term_id ), $taxonomy, $thepostid ), true, false ) . '>' . esc_attr( apply_filters( 'woocommerce_product_attribute_term_name', $term->name, $term ) ) . '</option>'; echo '<option value="' . esc_attr( $term->term_id ) . '" ' . selected( in_array( $term->term_id, $attribute->get_options() ), true, false ) . '>' . esc_attr( apply_filters( 'woocommerce_product_attribute_term_name', $term->name, $term ) ) . '</option>';
} }
} }
?> ?>
@ -50,7 +49,7 @@
<input type="text" name="attribute_values[<?php echo $i; ?>]" value="<?php <input type="text" name="attribute_values[<?php echo $i; ?>]" value="<?php
// Text attributes should list terms pipe separated // Text attributes should list terms pipe separated
echo esc_attr( implode( ' ' . WC_DELIMITER . ' ', wp_get_post_terms( $thepostid, $taxonomy, array( 'fields' => 'names' ) ) ) ); echo esc_attr( wc_implode_text_attributes( wp_list_pluck( $attribute->get_terms(), 'name' ) ) );
?>" placeholder="<?php ?>" placeholder="<?php
@ -65,20 +64,20 @@
<?php else : ?> <?php else : ?>
<textarea name="attribute_values[<?php echo $i; ?>]" cols="5" rows="5" placeholder="<?php echo esc_attr( sprintf( __( 'Enter some text, or some attributes by "%s" separating values.', 'woocommerce' ), WC_DELIMITER ) ); ?>"><?php echo esc_textarea( $attribute['value'] ); ?></textarea> <textarea name="attribute_values[<?php echo $i; ?>]" cols="5" rows="5" placeholder="<?php echo esc_attr( sprintf( __( 'Enter some text, or some attributes by "%s" separating values.', 'woocommerce' ), WC_DELIMITER ) ); ?>"><?php echo esc_textarea( wc_implode_text_attributes( $attribute->get_options() ) ); ?></textarea>
<?php endif; ?> <?php endif; ?>
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>
<label><input type="checkbox" class="checkbox" <?php checked( $attribute['is_visible'], 1 ); ?> name="attribute_visibility[<?php echo $i; ?>]" value="1" /> <?php _e( 'Visible on the product page', 'woocommerce' ); ?></label> <label><input type="checkbox" class="checkbox" <?php checked( $attribute->get_visible(), true ); ?> name="attribute_visibility[<?php echo $i; ?>]" value="1" /> <?php _e( 'Visible on the product page', 'woocommerce' ); ?></label>
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>
<div class="enable_variation show_if_variable"> <div class="enable_variation show_if_variable">
<label><input type="checkbox" class="checkbox" <?php checked( $attribute['is_variation'], 1 ); ?> name="attribute_variation[<?php echo $i; ?>]" value="1" /> <?php _e( 'Used for variations', 'woocommerce' ); ?></label> <label><input type="checkbox" class="checkbox" <?php checked( $attribute->get_variation(), true ); ?> name="attribute_variation[<?php echo $i; ?>]" value="1" /> <?php _e( 'Used for variations', 'woocommerce' ); ?></label>
</div> </div>
</td> </td>
</tr> </tr>

View File

@ -0,0 +1,44 @@
<div id="advanced_product_data" class="panel woocommerce_options_panel hidden">
<div class="options_group hide_if_external">
<?php
woocommerce_wp_textarea_input( array(
'id' => '_purchase_note',
'value' => $product_object->get_purchase_note( 'edit' ),
'label' => __( 'Purchase note', 'woocommerce' ),
'desc_tip' => true,
'description' => __( 'Enter an optional note to send the customer after purchase.', 'woocommerce' ),
) );
?>
</div>
<div class="options_group">
<?php
woocommerce_wp_text_input( array(
'id' => 'menu_order',
'value' => $product_object->get_menu_order( 'edit' ),
'label' => __( 'Menu order', 'woocommerce' ),
'desc_tip' => true,
'description' => __( 'Custom ordering position.', 'woocommerce' ),
'type' => 'number',
'custom_attributes' => array(
'step' => '1',
),
) );
?>
</div>
<div class="options_group reviews">
<?php
woocommerce_wp_checkbox( array(
'id' => '_reviews_allowed',
'value' => $product_object->get_reviews_allowed( 'edit' ) ? 'open' : 'closed',
'label' => __( 'Enable reviews', 'woocommerce' ),
'cbvalue' => 'open',
) );
do_action( 'woocommerce_product_options_reviews' );
?>
</div>
<?php do_action( 'woocommerce_product_options_advanced' ); ?>
</div>

View File

@ -0,0 +1,51 @@
<div id="product_attributes" class="panel wc-metaboxes-wrapper hidden">
<div class="toolbar toolbar-top">
<span class="expand-close">
<a href="#" class="expand_all"><?php _e( 'Expand', 'woocommerce' ); ?></a> / <a href="#" class="close_all"><?php _e( 'Close', 'woocommerce' ); ?></a>
</span>
<select name="attribute_taxonomy" class="attribute_taxonomy">
<option value=""><?php _e( 'Custom product attribute', 'woocommerce' ); ?></option>
<?php
global $wc_product_attributes;
// Array of defined attribute taxonomies
$attribute_taxonomies = wc_get_attribute_taxonomies();
if ( ! empty( $attribute_taxonomies ) ) {
foreach ( $attribute_taxonomies as $tax ) {
$attribute_taxonomy_name = wc_attribute_taxonomy_name( $tax->attribute_name );
$label = $tax->attribute_label ? $tax->attribute_label : $tax->attribute_name;
echo '<option value="' . esc_attr( $attribute_taxonomy_name ) . '">' . esc_html( $label ) . '</option>';
}
}
?>
</select>
<button type="button" class="button add_attribute"><?php _e( 'Add', 'woocommerce' ); ?></button>
</div>
<div class="product_attributes wc-metaboxes">
<?php
// Product attributes - taxonomies and custom, ordered, with visibility and variation attributes set
$attributes = $product_object->get_attributes( 'edit' );
$i = -1;
foreach ( $attributes as $attribute ) {
$i++;
$metabox_class = array();
if ( $attribute->is_taxonomy() ) {
$metabox_class[] = 'taxonomy';
$metabox_class[] = $attribute->get_name();
}
include( 'html-product-attribute.php' );
}
?>
</div>
<div class="toolbar">
<span class="expand-close">
<a href="#" class="expand_all"><?php _e( 'Expand', 'woocommerce' ); ?></a> / <a href="#" class="close_all"><?php _e( 'Close', 'woocommerce' ); ?></a>
</span>
<button type="button" class="button save_attributes button-primary"><?php _e( 'Save attributes', 'woocommerce' ); ?></button>
</div>
<?php do_action( 'woocommerce_product_options_attributes' ); ?>
</div>

View File

@ -0,0 +1,154 @@
<div id="general_product_data" class="panel woocommerce_options_panel">
<div class="options_group show_if_external">
<?php
woocommerce_wp_text_input( array(
'id' => '_product_url',
'value' => is_callable( array( $product_object, 'get_product_url' ) ) ? $product_object->get_product_url( 'edit' ) : '',
'label' => __( 'Product URL', 'woocommerce' ),
'placeholder' => 'http://',
'description' => __( 'Enter the external URL to the product.', 'woocommerce' ),
) );
woocommerce_wp_text_input( array(
'id' => '_button_text',
'value' => is_callable( array( $product_object, 'get_button_text' ) ) ? $product_object->get_button_text( 'edit' ) : '',
'label' => __( 'Button text', 'woocommerce' ),
'placeholder' => _x( 'Buy product', 'placeholder', 'woocommerce' ),
'description' => __( 'This text will be shown on the button linking to the external product.', 'woocommerce' ),
) );
?>
</div>
<div class="options_group pricing show_if_simple show_if_external hidden">
<?php
woocommerce_wp_text_input( array(
'id' => '_regular_price',
'value' => $product_object->get_regular_price( 'edit' ),
'label' => __( 'Regular price', 'woocommerce' ) . ' (' . get_woocommerce_currency_symbol() . ')',
'data_type' => 'price',
) );
woocommerce_wp_text_input( array(
'id' => '_sale_price',
'value' => $product_object->get_sale_price( 'edit' ),
'data_type' => 'price',
'label' => __( 'Sale price', 'woocommerce' ) . ' (' . get_woocommerce_currency_symbol() . ')',
'description' => '<a href="#" class="sale_schedule">' . __( 'Schedule', 'woocommerce' ) . '</a>',
) );
$sale_price_dates_from = ( $date = $product_object->get_date_on_sale_from( 'edit' ) ) ? date_i18n( 'Y-m-d', $date ) : '';
$sale_price_dates_to = ( $date = $product_object->get_date_on_sale_to( 'edit' ) ) ? date_i18n( 'Y-m-d', $date ) : '';
echo '<p class="form-field sale_price_dates_fields">
<label for="_sale_price_dates_from">' . __( 'Sale price dates', 'woocommerce' ) . '</label>
<input type="text" class="short" name="_sale_price_dates_from" id="_sale_price_dates_from" value="' . esc_attr( $sale_price_dates_from ) . '" placeholder="' . _x( 'From&hellip;', 'placeholder', 'woocommerce' ) . ' YYYY-MM-DD" maxlength="10" pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" />
<input type="text" class="short" name="_sale_price_dates_to" id="_sale_price_dates_to" value="' . esc_attr( $sale_price_dates_to ) . '" placeholder="' . _x( 'To&hellip;', 'placeholder', 'woocommerce' ) . ' YYYY-MM-DD" maxlength="10" pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" />
<a href="#" class="cancel_sale_schedule">' . __( 'Cancel', 'woocommerce' ) . '</a>' . wc_help_tip( __( 'The sale will end at the beginning of the set date.', 'woocommerce' ) ) . '
</p>';
do_action( 'woocommerce_product_options_pricing' );
?>
</div>
<div class="options_group show_if_downloadable hidden">
<div class="form-field downloadable_files">
<label><?php _e( 'Downloadable files', 'woocommerce' ); ?></label>
<table class="widefat">
<thead>
<tr>
<th class="sort">&nbsp;</th>
<th><?php _e( 'Name', 'woocommerce' ); ?> <?php echo wc_help_tip( __( 'This is the name of the download shown to the customer.', 'woocommerce' ) ); ?></th>
<th colspan="2"><?php _e( 'File URL', 'woocommerce' ); ?> <?php echo wc_help_tip( __( 'This is the URL or absolute path to the file which customers will get access to. URLs entered here should already be encoded.', 'woocommerce' ) ); ?></th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<?php
if ( $downloadable_files = $product_object->get_downloads( 'edit' ) ) {
foreach ( $downloadable_files as $key => $file ) {
include( 'html-product-download.php' );
}
}
?>
</tbody>
<tfoot>
<tr>
<th colspan="5">
<a href="#" class="button insert" data-row="<?php
$file = array(
'file' => '',
'name' => '',
);
ob_start();
include( 'html-product-download.php' );
echo esc_attr( ob_get_clean() );
?>"><?php _e( 'Add File', 'woocommerce' ); ?></a>
</th>
</tr>
</tfoot>
</table>
</div>
<?php
woocommerce_wp_text_input( array(
'id' => '_download_limit',
'value' => -1 === $product_object->get_download_limit( 'edit' ) ? '' : $product_object->get_download_limit( 'edit' ),
'label' => __( 'Download limit', 'woocommerce' ),
'placeholder' => __( 'Unlimited', 'woocommerce' ),
'description' => __( 'Leave blank for unlimited re-downloads.', 'woocommerce' ),
'type' => 'number',
'custom_attributes' => array(
'step' => '1',
'min' => '0',
),
) );
woocommerce_wp_text_input( array(
'id' => '_download_expiry',
'value' => -1 === $product_object->get_download_expiry( 'edit' ) ? '' : $product_object->get_download_expiry( 'edit' ),
'label' => __( 'Download expiry', 'woocommerce' ),
'placeholder' => __( 'Never', 'woocommerce' ),
'description' => __( 'Enter the number of days before a download link expires, or leave blank.', 'woocommerce' ),
'type' => 'number',
'custom_attributes' => array(
'step' => '1',
'min' => '0',
),
) );
do_action( 'woocommerce_product_options_downloads' );
?>
</div>
<?php if ( wc_tax_enabled() ) : ?>
<div class="options_group show_if_simple show_if_external show_if_variable">
<?php
woocommerce_wp_select( array(
'id' => '_tax_status',
'value' => $product_object->get_tax_status( 'edit' ),
'label' => __( 'Tax status', 'woocommerce' ),
'options' => array(
'taxable' => __( 'Taxable', 'woocommerce' ),
'shipping' => __( 'Shipping only', 'woocommerce' ),
'none' => _x( 'None', 'Tax status', 'woocommerce' ),
),
'desc_tip' => 'true',
'description' => __( 'Define whether or not the entire product is taxable, or just the cost of shipping it.', 'woocommerce' ),
) );
woocommerce_wp_select( array(
'id' => '_tax_class',
'value' => $product_object->get_tax_class( 'edit' ),
'label' => __( 'Tax class', 'woocommerce' ),
'options' => wc_get_product_tax_class_options(),
'desc_tip' => 'true',
'description' => __( 'Choose a tax class for this product. Tax classes are used to apply different tax rates specific to certain types of product.', 'woocommerce' ),
) );
do_action( 'woocommerce_product_options_tax' );
?>
</div>
<?php endif; ?>
<?php do_action( 'woocommerce_product_options_general_product_data' ); ?>
</div>

View File

@ -0,0 +1,87 @@
<div id="inventory_product_data" class="panel woocommerce_options_panel hidden">
<div class="options_group">
<?php
if ( wc_product_sku_enabled() ) {
woocommerce_wp_text_input( array(
'id' => '_sku',
'value' => $product_object->get_sku( 'edit' ),
'label' => '<abbr title="' . __( 'Stock Keeping Unit', 'woocommerce' ) . '">' . __( 'SKU', 'woocommerce' ) . '</abbr>',
'desc_tip' => true,
'description' => __( 'SKU refers to a Stock-keeping unit, a unique identifier for each distinct product and service that can be purchased.', 'woocommerce' ),
) );
}
do_action( 'woocommerce_product_options_sku' );
if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
woocommerce_wp_checkbox( array(
'id' => '_manage_stock',
'value' => $product_object->get_manage_stock( 'edit' ) ? 'yes' : 'no',
'wrapper_class' => 'show_if_simple show_if_variable',
'label' => __( 'Manage stock?', 'woocommerce' ),
'description' => __( 'Enable stock management at product level', 'woocommerce' ),
) );
do_action( 'woocommerce_product_options_stock' );
echo '<div class="stock_fields show_if_simple show_if_variable">';
woocommerce_wp_text_input( array(
'id' => '_stock',
'value' => $product_object->get_stock_quantity( 'edit' ),
'label' => __( 'Stock quantity', 'woocommerce' ),
'desc_tip' => true,
'description' => __( 'Stock quantity. If this is a variable product this value will be used to control stock for all variations, unless you define stock at variation level.', 'woocommerce' ),
'type' => 'number',
'custom_attributes' => array(
'step' => 'any',
),
'data_type' => 'stock',
) );
woocommerce_wp_select( array(
'id' => '_backorders',
'value' => $product_object->get_backorders( 'edit' ),
'label' => __( 'Allow backorders?', 'woocommerce' ),
'options' => wc_get_product_backorder_options(),
'desc_tip' => true,
'description' => __( 'If managing stock, this controls whether or not backorders are allowed. If enabled, stock quantity can go below 0.', 'woocommerce' ),
) );
do_action( 'woocommerce_product_options_stock_fields' );
echo '</div>';
}
woocommerce_wp_select( array(
'id' => '_stock_status',
'value' => $product_object->get_stock_status( 'edit' ),
'wrapper_class' => 'hide_if_variable hide_if_external',
'label' => __( 'Stock status', 'woocommerce' ),
'options' => wc_get_product_stock_status_options(),
'desc_tip' => true,
'description' => __( 'Controls whether or not the product is listed as "in stock" or "out of stock" on the frontend.', 'woocommerce' ),
) );
do_action( 'woocommerce_product_options_stock_status' );
?>
</div>
<div class="options_group show_if_simple show_if_variable">
<?php
woocommerce_wp_checkbox( array(
'id' => '_sold_individually',
'value' => $product_object->get_sold_individually( 'edit' ) ? 'yes' : 'no',
'wrapper_class' => 'show_if_simple show_if_variable',
'label' => __( 'Sold individually', 'woocommerce' ),
'description' => __( 'Enable this to only allow one of this item to be bought in a single order', 'woocommerce' ),
) );
do_action( 'woocommerce_product_options_sold_individually' );
?>
</div>
<?php do_action( 'woocommerce_product_options_inventory_product_data' ); ?>
</div>

View File

@ -0,0 +1,57 @@
<div id="linked_product_data" class="panel woocommerce_options_panel hidden">
<div class="options_group">
<p class="form-field">
<label for="upsell_ids"><?php _e( 'Up-sells', 'woocommerce' ); ?></label>
<input type="hidden" class="wc-product-search" style="width: 50%;" id="upsell_ids" name="upsell_ids" data-placeholder="<?php esc_attr_e( 'Search for a product&hellip;', 'woocommerce' ); ?>" data-action="woocommerce_json_search_products" data-multiple="true" data-exclude="<?php echo intval( $post->ID ); ?>" data-selected="<?php
$product_ids = $product_object->get_upsell_ids( 'edit' );
$json_ids = array();
foreach ( $product_ids as $product_id ) {
$product = wc_get_product( $product_id );
if ( is_object( $product ) ) {
$json_ids[ $product_id ] = wp_kses_post( html_entity_decode( $product->get_formatted_name(), ENT_QUOTES, get_bloginfo( 'charset' ) ) );
}
}
echo esc_attr( json_encode( $json_ids ) );
?>" value="<?php echo implode( ',', array_keys( $json_ids ) ); ?>" /> <?php echo wc_help_tip( __( 'Up-sells are products which you recommend instead of the currently viewed product, for example, products that are more profitable or better quality or more expensive.', 'woocommerce' ) ); ?>
</p>
<p class="form-field">
<label for="crosssell_ids"><?php _e( 'Cross-sells', 'woocommerce' ); ?></label>
<input type="hidden" class="wc-product-search" style="width: 50%;" id="crosssell_ids" name="crosssell_ids" data-placeholder="<?php esc_attr_e( 'Search for a product&hellip;', 'woocommerce' ); ?>" data-action="woocommerce_json_search_products" data-multiple="true" data-exclude="<?php echo intval( $post->ID ); ?>" data-selected="<?php
$product_ids = $product_object->get_cross_sell_ids( 'edit' );
$json_ids = array();
foreach ( $product_ids as $product_id ) {
$product = wc_get_product( $product_id );
if ( is_object( $product ) ) {
$json_ids[ $product_id ] = wp_kses_post( html_entity_decode( $product->get_formatted_name(), ENT_QUOTES, get_bloginfo( 'charset' ) ) );
}
}
echo esc_attr( json_encode( $json_ids ) );
?>" value="<?php echo implode( ',', array_keys( $json_ids ) ); ?>" /> <?php echo wc_help_tip( __( 'Cross-sells are products which you promote in the cart, based on the current product.', 'woocommerce' ) ); ?>
</p>
<p class="form-field show_if_grouped">
<label for="grouped_products"><?php _e( 'Grouped products', 'woocommerce' ); ?></label>
<input type="hidden" class="wc-product-search" style="width: 50%;" id="grouped_products" name="grouped_products" data-placeholder="<?php esc_attr_e( 'Search for a product&hellip;', 'woocommerce' ); ?>" data-action="woocommerce_json_search_products" data-multiple="true" data-exclude="<?php echo intval( $post->ID ); ?>" data-selected="<?php
$product_ids = $product_object->get_children( 'edit' );
$json_ids = array();
foreach ( $product_ids as $product_id ) {
$product = wc_get_product( $product_id );
if ( is_object( $product ) ) {
$json_ids[ $product_id ] = wp_kses_post( html_entity_decode( $product->get_formatted_name(), ENT_QUOTES, get_bloginfo( 'charset' ) ) );
}
}
echo esc_attr( json_encode( $json_ids ) );
?>" value="<?php echo implode( ',', array_keys( $json_ids ) ); ?>" /> <?php echo wc_help_tip( __( 'This lets you choose which products are part of this group.', 'woocommerce' ) ); ?>
</p>
</div>
<?php do_action( 'woocommerce_product_options_related' ); ?>
</div>

View File

@ -0,0 +1,45 @@
<div class="panel-wrap product_data">
<span class="type_box hidden"> &mdash;
<label for="product-type">
<select id="product-type" name="product-type">
<optgroup label="<?php esc_attr_e( 'Product Type', 'woocommerce' ); ?>">
<?php foreach ( wc_get_product_types() as $value => $label ) : ?>
<option value="<?php echo esc_attr( $value ); ?>" <?php echo selected( $product_object->get_type(), $value, false ); ?>><?php echo esc_html( $label ); ?></option>
<?php endforeach; ?>
</optgroup>
</select>
</label>
<?php foreach ( self::get_product_type_options() as $key => $option ) :
if ( $thepostid ) {
$selected_value = is_callable( $product_object, "is_$key" ) ? $product_object->{"is_$key"}() : get_post_meta( $post->ID, '_' . $key, true );
} else {
$selected_value = isset( $option['default'] ) ? $option['default'] : 'no';
}
?>
<label for="<?php echo esc_attr( $option['id'] ); ?>" class="<?php echo esc_attr( $option['wrapper_class'] ); ?> tips" data-tip="<?php echo esc_attr( $option['description'] ); ?>">
<?php echo esc_html( $option['label'] ); ?>:
<input type="checkbox" name="<?php echo esc_attr( $option['id'] ); ?>" id="<?php echo esc_attr( $option['id'] ); ?>" <?php echo checked( $selected_value, 'yes', false ); ?> />
</label>
<?php endforeach; ?>
</span>
<ul class="product_data_tabs wc-tabs">
<?php foreach ( self::get_product_data_tabs() as $key => $tab ) : ?>
<li class="<?php echo $key; ?>_options <?php echo $key; ?>_tab <?php echo implode( ' ' , (array) $tab['class'] ); ?>">
<a href="#<?php echo $tab['target']; ?>"><?php echo esc_html( $tab['label'] ); ?></a>
</li>
<?php endforeach; ?>
<?php do_action( 'woocommerce_product_write_panel_tabs' ); ?>
</ul>
<?php
self::output_tabs();
self::output_variations();
do_action( 'woocommerce_product_data_panels' );
wc_do_deprecated_action( 'woocommerce_product_write_panels', array(), '2.6', 'Use woocommerce_product_data_panels action instead.' );
wp_nonce_field( 'woocommerce_save_data', 'woocommerce_meta_nonce' );
?>
<div class="clear"></div>
</div>

View File

@ -0,0 +1,53 @@
<div id="shipping_product_data" class="panel woocommerce_options_panel hidden">
<div class="options_group">
<?php
if ( wc_product_weight_enabled() ) {
woocommerce_wp_text_input( array(
'id' => '_weight',
'value' => $product_object->get_weight( 'edit' ),
'label' => __( 'Weight', 'woocommerce' ) . ' (' . get_option( 'woocommerce_weight_unit' ) . ')',
'placeholder' => wc_format_localized_decimal( 0 ),
'desc_tip' => true,
'description' => __( 'Weight in decimal form', 'woocommerce' ),
'type' => 'text',
'data_type' => 'decimal',
) );
}
if ( wc_product_dimensions_enabled() ) {
?><p class="form-field dimensions_field">
<label for="product_length"><?php echo __( 'Dimensions', 'woocommerce' ) . ' (' . get_option( 'woocommerce_dimension_unit' ) . ')'; ?></label>
<span class="wrap">
<input id="product_length" placeholder="<?php esc_attr_e( 'Length', 'woocommerce' ); ?>" class="input-text wc_input_decimal" size="6" type="text" name="_length" value="<?php echo esc_attr( wc_format_localized_decimal( $product_object->get_length( 'edit' ) ) ); ?>" />
<input placeholder="<?php esc_attr_e( 'Width', 'woocommerce' ); ?>" class="input-text wc_input_decimal" size="6" type="text" name="_width" value="<?php echo esc_attr( wc_format_localized_decimal( $product_object->get_width( 'edit' ) ) ); ?>" />
<input placeholder="<?php esc_attr_e( 'Height', 'woocommerce' ); ?>" class="input-text wc_input_decimal last" size="6" type="text" name="_height" value="<?php echo esc_attr( wc_format_localized_decimal( $product_object->get_height( 'edit' ) ) ); ?>" />
</span>
<?php echo wc_help_tip( __( 'LxWxH in decimal form', 'woocommerce' ) ); ?>
</p><?php
}
do_action( 'woocommerce_product_options_dimensions' );
?>
</div>
<div class="options_group">
<?php
$args = array(
'taxonomy' => 'product_shipping_class',
'hide_empty' => 0,
'show_option_none' => __( 'No shipping class', 'woocommerce' ),
'name' => 'product_shipping_class',
'id' => 'product_shipping_class',
'selected' => $product_object->get_shipping_class_id( 'edit' ),
'class' => 'select short',
);
?><p class="form-field dimensions_field">
<label for="product_shipping_class"><?php _e( 'Shipping class', 'woocommerce' ); ?></label>
<?php wp_dropdown_categories( $args ); ?>
<?php echo wc_help_tip( __( 'Shipping classes are used by certain shipping methods to group similar products.', 'woocommerce' ) ); ?>
</p><?php
do_action( 'woocommerce_product_options_shipping' );
?>
</div>
</div>

View File

@ -0,0 +1,136 @@
<div id="variable_product_options" class="panel wc-metaboxes-wrapper hidden">
<div id="variable_product_options_inner">
<?php if ( ! count( $variation_attributes ) ) : ?>
<div id="message" class="inline notice woocommerce-message">
<p><?php _e( 'Before you can add a variation you need to add some variation attributes on the <strong>Attributes</strong> tab.', 'woocommerce' ); ?></p>
<p><a class="button-primary" href="<?php echo esc_url( apply_filters( 'woocommerce_docs_url', 'https://docs.woocommerce.com/document/variable-product/', 'product-variations' ) ); ?>" target="_blank"><?php _e( 'Learn more', 'woocommerce' ); ?></a></p>
</div>
<?php else : ?>
<div class="toolbar toolbar-variations-defaults">
<div class="variations-defaults">
<strong><?php _e( 'Default Form Values', 'woocommerce' ); ?>: <?php echo wc_help_tip( __( 'These are the attributes that will be pre-selected on the frontend.', 'woocommerce' ) ); ?></strong>
<?php
foreach ( $variation_attributes as $attribute ) {
$selected_value = isset( $default_attributes[ sanitize_title( $attribute->get_name() ) ] ) ? $default_attributes[ sanitize_title( $attribute->get_name() ) ] : '';
?>
<select name="default_attribute_<?php echo sanitize_title( $attribute->get_name() ); ?>" data-current="<?php echo esc_attr( $selected_value ); ?>">
<option value=""><?php echo esc_html( sprintf( __( 'No default %s&hellip;', 'woocommerce' ), wc_attribute_label( $attribute->get_name() ) ) ); ?></option>
<?php if ( $attribute->is_taxonomy() ) : ?>
<?php foreach ( $attribute->get_terms() as $option ) : ?>
<option <?php selected( $selected_value, $option->slug ); ?> value="<?php echo esc_attr( $option->slug ); ?>"><?php echo esc_html( apply_filters( 'woocommerce_variation_option_name', $option->name ) ); ?></option>
<?php endforeach; ?>
<?php else : ?>
<?php foreach ( $attribute->get_options() as $option ) : ?>
<option <?php selected( $selected_value, $option ); ?> value="<?php echo esc_attr( $option ); ?>"><?php echo esc_html( apply_filters( 'woocommerce_variation_option_name', $option ) ); ?></option>
<?php endforeach; ?>
<?php endif; ?>
</select>
<?php
}
?>
</div>
<div class="clear"></div>
</div>
<div class="toolbar toolbar-top">
<select id="field_to_edit" class="variation_actions">
<option data-global="true" value="add_variation"><?php _e( 'Add variation', 'woocommerce' ); ?></option>
<option data-global="true" value="link_all_variations"><?php _e( 'Create variations from all attributes', 'woocommerce' ); ?></option>
<option value="delete_all"><?php _e( 'Delete all variations', 'woocommerce' ); ?></option>
<optgroup label="<?php esc_attr_e( 'Status', 'woocommerce' ); ?>">
<option value="toggle_enabled"><?php _e( 'Toggle &quot;Enabled&quot;', 'woocommerce' ); ?></option>
<option value="toggle_downloadable"><?php _e( 'Toggle &quot;Downloadable&quot;', 'woocommerce' ); ?></option>
<option value="toggle_virtual"><?php _e( 'Toggle &quot;Virtual&quot;', 'woocommerce' ); ?></option>
</optgroup>
<optgroup label="<?php esc_attr_e( 'Pricing', 'woocommerce' ); ?>">
<option value="variable_regular_price"><?php _e( 'Set regular prices', 'woocommerce' ); ?></option>
<option value="variable_regular_price_increase"><?php _e( 'Increase regular prices (fixed amount or percentage)', 'woocommerce' ); ?></option>
<option value="variable_regular_price_decrease"><?php _e( 'Decrease regular prices (fixed amount or percentage)', 'woocommerce' ); ?></option>
<option value="variable_sale_price"><?php _e( 'Set sale prices', 'woocommerce' ); ?></option>
<option value="variable_sale_price_increase"><?php _e( 'Increase sale prices (fixed amount or percentage)', 'woocommerce' ); ?></option>
<option value="variable_sale_price_decrease"><?php _e( 'Decrease sale prices (fixed amount or percentage)', 'woocommerce' ); ?></option>
<option value="variable_sale_schedule"><?php _e( 'Set scheduled sale dates', 'woocommerce' ); ?></option>
</optgroup>
<optgroup label="<?php esc_attr_e( 'Inventory', 'woocommerce' ); ?>">
<option value="toggle_manage_stock"><?php _e( 'Toggle &quot;Manage stock&quot;', 'woocommerce' ); ?></option>
<option value="variable_stock"><?php _e( 'Stock', 'woocommerce' ); ?></option>
</optgroup>
<optgroup label="<?php esc_attr_e( 'Shipping', 'woocommerce' ); ?>">
<option value="variable_length"><?php _e( 'Length', 'woocommerce' ); ?></option>
<option value="variable_width"><?php _e( 'Width', 'woocommerce' ); ?></option>
<option value="variable_height"><?php _e( 'Height', 'woocommerce' ); ?></option>
<option value="variable_weight"><?php _e( 'Weight', 'woocommerce' ); ?></option>
</optgroup>
<optgroup label="<?php esc_attr_e( 'Downloadable products', 'woocommerce' ); ?>">
<option value="variable_download_limit"><?php _e( 'Download limit', 'woocommerce' ); ?></option>
<option value="variable_download_expiry"><?php _e( 'Download expiry', 'woocommerce' ); ?></option>
</optgroup>
<?php do_action( 'woocommerce_variable_product_bulk_edit_actions' ); ?>
</select>
<a class="button bulk_edit do_variation_action"><?php _e( 'Go', 'woocommerce' ); ?></a>
<div class="variations-pagenav">
<span class="displaying-num"><?php printf( _n( '%s item', '%s items', $variations_count, 'woocommerce' ), $variations_count ); ?></span>
<span class="expand-close">
(<a href="#" class="expand_all"><?php _e( 'Expand', 'woocommerce' ); ?></a> / <a href="#" class="close_all"><?php _e( 'Close', 'woocommerce' ); ?></a>)
</span>
<span class="pagination-links">
<a class="first-page disabled" title="<?php esc_attr_e( 'Go to the first page', 'woocommerce' ); ?>" href="#">&laquo;</a>
<a class="prev-page disabled" title="<?php esc_attr_e( 'Go to the previous page', 'woocommerce' ); ?>" href="#">&lsaquo;</a>
<span class="paging-select">
<label for="current-page-selector-1" class="screen-reader-text"><?php _e( 'Select Page', 'woocommerce' ); ?></label>
<select class="page-selector" id="current-page-selector-1" title="<?php esc_attr_e( 'Current page', 'woocommerce' ); ?>">
<?php for ( $i = 1; $i <= $variations_total_pages; $i++ ) : ?>
<option value="<?php echo $i; ?>"><?php echo $i; ?></option>
<?php endfor; ?>
</select>
<?php _ex( 'of', 'number of pages', 'woocommerce' ); ?> <span class="total-pages"><?php echo $variations_total_pages; ?></span>
</span>
<a class="next-page" title="<?php esc_attr_e( 'Go to the next page', 'woocommerce' ); ?>" href="#">&rsaquo;</a>
<a class="last-page" title="<?php esc_attr_e( 'Go to the last page', 'woocommerce' ); ?>" href="#">&raquo;</a>
</span>
</div>
<div class="clear"></div>
</div>
<div class="woocommerce_variations wc-metaboxes" data-attributes="<?php
// esc_attr does not double encode - htmlspecialchars does
echo htmlspecialchars( json_encode( wc_list_pluck( $variation_attributes, 'get_data' ) ) );
?>" data-total="<?php echo $variations_count; ?>" data-total_pages="<?php echo $variations_total_pages; ?>" data-page="1" data-edited="false">
</div>
<div class="toolbar">
<button type="button" class="button-primary save-variation-changes" disabled="disabled"><?php _e( 'Save changes', 'woocommerce' ); ?></button>
<button type="button" class="button cancel-variation-changes" disabled="disabled"><?php _e( 'Cancel', 'woocommerce' ); ?></button>
<div class="variations-pagenav">
<span class="displaying-num"><?php printf( _n( '%s item', '%s items', $variations_count, 'woocommerce' ), $variations_count ); ?></span>
<span class="expand-close">
(<a href="#" class="expand_all"><?php _e( 'Expand', 'woocommerce' ); ?></a> / <a href="#" class="close_all"><?php _e( 'Close', 'woocommerce' ); ?></a>)
</span>
<span class="pagination-links">
<a class="first-page disabled" title="<?php esc_attr_e( 'Go to the first page', 'woocommerce' ); ?>" href="#">&laquo;</a>
<a class="prev-page disabled" title="<?php esc_attr_e( 'Go to the previous page', 'woocommerce' ); ?>" href="#">&lsaquo;</a>
<span class="paging-select">
<label for="current-page-selector-1" class="screen-reader-text"><?php _e( 'Select Page', 'woocommerce' ); ?></label>
<select class="page-selector" id="current-page-selector-1" title="<?php esc_attr_e( 'Current page', 'woocommerce' ); ?>">
<?php for ( $i = 1; $i <= $variations_total_pages; $i++ ) : ?>
<option value="<?php echo $i; ?>"><?php echo $i; ?></option>
<?php endfor; ?>
</select>
<?php _ex( 'of', 'number of pages', 'woocommerce' ); ?> <span class="total-pages"><?php echo $variations_total_pages; ?></span>
</span>
<a class="next-page" title="<?php esc_attr_e( 'Go to the next page', 'woocommerce' ); ?>" href="#">&rsaquo;</a>
<a class="last-page" title="<?php esc_attr_e( 'Go to the last page', 'woocommerce' ); ?>" href="#">&raquo;</a>
</span>
</div>
<div class="clear"></div>
</div>
<?php endif; ?>
</div>
</div>

View File

@ -1,16 +1,15 @@
<?php <?php
/** /**
* Outputs a variation * Outputs a variation for editing.
* *
* @var int $variation_id * @var int $variation_id
* @var WP_POST $variation * @var WP_POST $variation
* @var array $variation_data array of variation data * @var WP_Product_Variation $variation_object
* @var array $variation_data array of variation data @deprecated.
*/ */
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
extract( $variation_data );
?> ?>
<div class="woocommerce_variation wc-metabox closed"> <div class="woocommerce_variation wc-metabox closed">
<h3> <h3>
@ -19,108 +18,135 @@ extract( $variation_data );
<div class="tips sort" data-tip="<?php esc_attr_e( 'Drag and drop, or click to set admin variation order', 'woocommerce' ); ?>"></div> <div class="tips sort" data-tip="<?php esc_attr_e( 'Drag and drop, or click to set admin variation order', 'woocommerce' ); ?>"></div>
<strong>#<?php echo esc_html( $variation_id ); ?> </strong> <strong>#<?php echo esc_html( $variation_id ); ?> </strong>
<?php <?php
foreach ( $parent_data['attributes'] as $attribute ) { $attribute_values = $variation_object->get_attributes( 'edit' );
// Only deal with attributes that are variations foreach ( $product_object->get_attributes( 'edit' ) as $attribute ) {
if ( ! $attribute['is_variation'] || 'false' === $attribute['is_variation'] ) { if ( ! $attribute->get_variation() ) {
continue; continue;
} }
$selected_value = isset( $attribute_values[ 'attribute_' . sanitize_title( $attribute->get_name() ) ] ) ? $attribute_values[ 'attribute_' . sanitize_title( $attribute->get_name() ) ] : '';
// Get current value for variation (if set) ?>
$variation_selected_value = isset( $variation_data[ 'attribute_' . sanitize_title( $attribute['name'] ) ] ) ? $variation_data[ 'attribute_' . sanitize_title( $attribute['name'] ) ] : ''; <select name="attribute_<?php echo sanitize_title( $attribute->get_name() ) . "[{$loop}]"; ?>">
<option value=""><?php
// Name will be something like attribute_pa_color /* translators: %s: attribute label */
/* translators: %s: attribute label */ echo sprintf( __( 'Any %s&hellip;', 'woocommerce' ), esc_html( wc_attribute_label( $attribute->get_name() ) ) );
echo '<select name="attribute_' . sanitize_title( $attribute['name'] ) . '[' . $loop . ']"><option value="">' . sprintf( __( 'Any %s&hellip;', 'woocommerce' ), esc_html( wc_attribute_label( $attribute['name'] ) ) ) . '</option>'; ?>&hellip;</option>
<?php if ( $attribute->is_taxonomy() ) : ?>
// Get terms for attribute taxonomy or value if its a custom attribute <?php foreach ( $attribute->get_terms() as $option ) : ?>
if ( $attribute['is_taxonomy'] ) { <option <?php selected( $selected_value, $option->slug ); ?> value="<?php echo esc_attr( $option->slug ); ?>"><?php echo esc_html( apply_filters( 'woocommerce_variation_option_name', $option->name ) ); ?></option>
<?php endforeach; ?>
$post_terms = wp_get_post_terms( $parent_data['id'], $attribute['name'] ); <?php else : ?>
<?php foreach ( $attribute->get_options() as $option ) : ?>
foreach ( $post_terms as $term ) { <option <?php selected( $selected_value, $option ); ?> value="<?php echo esc_attr( $option ); ?>"><?php echo esc_html( apply_filters( 'woocommerce_variation_option_name', $option ) ); ?></option>
echo '<option ' . selected( $variation_selected_value, $term->slug, false ) . ' value="' . esc_attr( $term->slug ) . '">' . esc_html( apply_filters( 'woocommerce_variation_option_name', $term->name ) ) . '</option>'; <?php endforeach; ?>
} <?php endif; ?>
} else { </select>
<?php
$options = wc_get_text_attributes( $attribute['value'] );
foreach ( $options as $option ) {
$selected = sanitize_title( $variation_selected_value ) === $variation_selected_value ? selected( $variation_selected_value, sanitize_title( $option ), false ) : selected( $variation_selected_value, $option, false );
echo '<option ' . $selected . ' value="' . esc_attr( $option ) . '">' . esc_html( apply_filters( 'woocommerce_variation_option_name', $option ) ) . '</option>';
}
}
echo '</select>';
} }
?> ?>
<input type="hidden" name="variable_post_id[<?php echo $loop; ?>]" value="<?php echo esc_attr( $variation_id ); ?>" /> <input type="hidden" name="variable_post_id[<?php echo $loop; ?>]" value="<?php echo esc_attr( $variation_id ); ?>" />
<input type="hidden" class="variation_menu_order" name="variation_menu_order[<?php echo $loop; ?>]" value="<?php echo isset( $menu_order ) ? absint( $menu_order ) : 0; ?>" /> <input type="hidden" class="variation_menu_order" name="variation_menu_order[<?php echo $loop; ?>]" value="<?php echo esc_attr( $variation_object->get_menu_order( 'edit' ) ); ?>" />
</h3> </h3>
<div class="woocommerce_variable_attributes wc-metabox-content" style="display: none;"> <div class="woocommerce_variable_attributes wc-metabox-content" style="display: none;">
<div class="data"> <div class="data">
<p class="form-row form-row-first upload_image"> <p class="form-row form-row-first upload_image">
<a href="#" class="upload_image_button tips <?php echo ( $_thumbnail_id > 0 ) ? 'remove' : ''; ?>" data-tip="<?php echo ( $_thumbnail_id > 0 ) ? __( 'Remove this image', 'woocommerce' ) : __( 'Upload an image', 'woocommerce' ); ?>" rel="<?php echo esc_attr( $variation_id ); ?>"><img src="<?php echo ( ! empty( $image ) ) ? esc_attr( $image ) : esc_attr( wc_placeholder_img_src() ); ?>" /><input type="hidden" name="upload_image_id[<?php echo $loop; ?>]" class="upload_image_id" value="<?php echo esc_attr( $_thumbnail_id ); ?>" /></a> <a href="#" class="upload_image_button tips <?php echo $variation_object->get_image_id( 'edit' ) ? 'remove' : ''; ?>" data-tip="<?php echo $variation_object->get_image_id( 'edit' ) ? __( 'Remove this image', 'woocommerce' ) : __( 'Upload an image', 'woocommerce' ); ?>" rel="<?php echo esc_attr( $variation_id ); ?>">
<img src="<?php echo $variation_object->get_image_id( 'edit' ) ? esc_url( wp_get_attachment_thumb_url( $variation_object->get_image_id( 'edit' ) ) ) : esc_url( wc_placeholder_img_src() ); ?>" /><input type="hidden" name="upload_image_id[<?php echo $loop; ?>]" class="upload_image_id" value="<?php echo esc_attr( $variation_object->get_image_id( 'edit' ) ); ?>" />
</a>
</p> </p>
<?php if ( wc_product_sku_enabled() ) : ?> <?php
<p class="sku form-row form-row-last"> if ( wc_product_sku_enabled() ) {
<label><?php _e( 'SKU', 'woocommerce' ); ?> <?php echo wc_help_tip( __( 'Enter a SKU for this variation or leave blank to use the parent product SKU.', 'woocommerce' ) ); ?></label> woocommerce_wp_text_input( array(
<input type="text" size="5" name="variable_sku[<?php echo $loop; ?>]" value="<?php if ( isset( $_sku ) ) echo esc_attr( $_sku ); ?>" placeholder="<?php echo esc_attr( $parent_data['sku'] ); ?>" /> 'id' => "variable_sku{$loop}",
</p> 'name' => "variable_sku[{$loop}]",
<?php else : ?> 'value' => $variation_object->get_sku( 'edit' ),
<input type="hidden" name="variable_sku[<?php echo $loop; ?>]" value="<?php if ( isset( $_sku ) ) echo esc_attr( $_sku ); ?>" /> 'placeholder' => $variation_object->get_sku(),
<?php endif; ?> 'label' => '<abbr title="' . __( 'Stock Keeping Unit', 'woocommerce' ) . '">' . __( 'SKU', 'woocommerce' ) . '</abbr>',
'desc_tip' => true,
'description' => __( 'SKU refers to a Stock-keeping unit, a unique identifier for each distinct product and service that can be purchased.', 'woocommerce' ),
'wrapper_class' => 'form-row form-row-last',
) );
}
?>
<p class="form-row form-row-full options"> <p class="form-row form-row-full options">
<label><input type="checkbox" class="checkbox" name="variable_enabled[<?php echo $loop; ?>]" <?php checked( $variation->post_status, 'publish' ); ?> /> <?php _e( 'Enabled', 'woocommerce' ); ?></label> <label>
<?php _e( 'Enabled', 'woocommerce' ); ?>:
<label><input type="checkbox" class="checkbox variable_is_downloadable" name="variable_is_downloadable[<?php echo $loop; ?>]" <?php checked( isset( $_downloadable ) ? $_downloadable : '', 'yes' ); ?> /> <?php _e( 'Downloadable', 'woocommerce' ); ?> <?php echo wc_help_tip( __( 'Enable this option if access is given to a downloadable file upon purchase of a product', 'woocommerce' ) ); ?></label> <input type="checkbox" class="checkbox" name="variable_enabled[<?php echo $loop; ?>]" <?php checked( $variation_object->get_status( 'edit' ), 'publish' ); ?> />
</label>
<label><input type="checkbox" class="checkbox variable_is_virtual" name="variable_is_virtual[<?php echo $loop; ?>]" <?php checked( isset( $_virtual ) ? $_virtual : '', 'yes' ); ?> /> <?php _e( 'Virtual', 'woocommerce' ); ?> <?php echo wc_help_tip( __( 'Enable this option if a product is not shipped or there is no shipping cost', 'woocommerce' ) ); ?></label> <label class="tips" data-tip="<?php _e( 'Enable this option if access is given to a downloadable file upon purchase of a product', 'woocommerce' ); ?>">
<?php _e( 'Downloadable', 'woocommerce' ); ?>:
<?php if ( get_option( 'woocommerce_manage_stock' ) == 'yes' ) : ?> <input type="checkbox" class="checkbox variable_is_downloadable" name="variable_is_downloadable[<?php echo $loop; ?>]" <?php checked( $variation_object->get_downloadable( 'edit' ), true ); ?> />
</label>
<label><input type="checkbox" class="checkbox variable_manage_stock" name="variable_manage_stock[<?php echo $loop; ?>]" <?php checked( isset( $_manage_stock ) ? $_manage_stock : '', 'yes' ); ?> /> <?php _e( 'Manage stock?', 'woocommerce' ); ?> <?php echo wc_help_tip( __( 'Enable this option to enable stock management at variation level', 'woocommerce' ) ); ?></label> <label class="tips" data-tip="<?php _e( 'Enable this option if a product is not shipped or there is no shipping cost', 'woocommerce' ); ?>">
<?php _e( 'Virtual', 'woocommerce' ); ?>:
<input type="checkbox" class="checkbox variable_is_virtual" name="variable_is_virtual[<?php echo $loop; ?>]" <?php checked( $variation_object->get_virtual( 'edit' ), true ); ?> />
</label>
<?php if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) : ?>
<label class="tips" data-tip="<?php _e( 'Enable this option to enable stock management at variation level', 'woocommerce' ); ?>">
<?php _e( 'Manage stock?', 'woocommerce' ); ?>
<input type="checkbox" class="checkbox variable_manage_stock" name="variable_manage_stock[<?php echo $loop; ?>]" <?php checked( $variation_object->get_manage_stock( 'edit' ), true ); ?> />
</label>
<?php endif; ?> <?php endif; ?>
<?php do_action( 'woocommerce_variation_options', $loop, $variation_data, $variation ); ?> <?php do_action( 'woocommerce_variation_options', $loop, $variation_data, $variation ); ?>
</p> </p>
<div class="variable_pricing"> <div class="variable_pricing">
<p class="form-row form-row-first">
<label><?php
/* translators: %s: currency symbol */
printf(
__( 'Regular price (%s)', 'woocommerce' ),
get_woocommerce_currency_symbol()
);
?></label>
<input type="text" size="5" name="variable_regular_price[<?php echo $loop; ?>]" value="<?php if ( isset( $_regular_price ) ) echo esc_attr( $_regular_price ); ?>" class="wc_input_price" placeholder="<?php esc_attr_e( 'Variation price (required)', 'woocommerce' ); ?>" />
</p>
<p class="form-row form-row-last">
<label><?php
/* translators: %s: currency symbol */
printf(
__( 'Sale price (%s)', 'woocommerce' ),
get_woocommerce_currency_symbol()
);
?> <a href="#" class="sale_schedule"><?php _e( 'Schedule', 'woocommerce' ); ?></a><a href="#" class="cancel_sale_schedule" style="display:none"><?php _e( 'Cancel schedule', 'woocommerce' ); ?></a></label>
<input type="text" size="5" name="variable_sale_price[<?php echo $loop; ?>]" value="<?php if ( isset( $_sale_price ) ) echo esc_attr( $_sale_price ); ?>" class="wc_input_price" />
</p>
<div class="sale_price_dates_fields" style="display: none">
<p class="form-row form-row-first">
<label><?php _e( 'Sale start date', 'woocommerce' ); ?></label>
<input type="text" class="sale_price_dates_from" name="variable_sale_price_dates_from[<?php echo $loop; ?>]" value="<?php echo ! empty( $_sale_price_dates_from ) ? date_i18n( 'Y-m-d', $_sale_price_dates_from ) : ''; ?>" placeholder="<?php echo esc_attr__( 'From&hellip;', 'woocommerce' ) ?> YYYY-MM-DD" maxlength="10" pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" />
</p>
<p class="form-row form-row-last">
<label><?php _e( 'Sale end date', 'woocommerce' ); ?></label>
<input type="text" class="sale_price_dates_to" name="variable_sale_price_dates_to[<?php echo $loop; ?>]" value="<?php echo ! empty( $_sale_price_dates_to ) ? date_i18n( 'Y-m-d', $_sale_price_dates_to ) : ''; ?>" placeholder="<?php echo esc_attr__( 'To&hellip;', 'woocommerce' ) ?> YYYY-MM-DD" maxlength="10" pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" />
</p>
</div>
<?php <?php
/* translators: %s: currency symbol */
$label = sprintf(
__( 'Regular price (%s)', 'woocommerce' ),
get_woocommerce_currency_symbol()
);
woocommerce_wp_text_input( array(
'id' => "variable_regular_price_{$loop}",
'name' => "variable_regular_price[{$loop}]",
'value' => wc_format_localized_price( $variation_object->get_regular_price( 'edit' ) ),
'label' => $label,
'data_type' => 'price',
'wrapper_class' => 'form-row form-row-first',
) );
/* translators: %s: currency symbol */
$label = sprintf(
__( 'Sale price (%s)', 'woocommerce' ),
get_woocommerce_currency_symbol()
);
woocommerce_wp_text_input( array(
'id' => "variable_regular_price_{$loop}",
'name' => "variable_regular_price[{$loop}]",
'value' => wc_format_localized_price( $variation_object->get_regular_price( 'edit' ) ),
'label' => __( 'Regular price', 'woocommerce' ) . ' (' . get_woocommerce_currency_symbol() . ')',
'data_type' => 'price',
'wrapper_class' => 'form-row form-row-first',
) );
woocommerce_wp_text_input( array(
'id' => "variable_sale_price{$loop}",
'name' => "variable_sale_price[{$loop}]",
'value' => wc_format_localized_price( $variation_object->get_sale_price( 'edit' ) ),
'data_type' => 'price',
'label' => $label . ' <a href="#" class="sale_schedule">' . __( 'Schedule', 'woocommerce' ) . '</a><a href="#" class="cancel_sale_schedule hidden">' . __( 'Cancel schedule', 'woocommerce' ) . '</a>',
'wrapper_class' => 'form-row form-row-last',
) );
$sale_price_dates_from = ( $date = $variation_object->get_date_on_sale_from( 'edit' ) ) ? date_i18n( 'Y-m-d', $date ) : '';
$sale_price_dates_to = ( $date = $variation_object->get_date_on_sale_to( 'edit' ) ) ? date_i18n( 'Y-m-d', $date ) : '';
echo '<div class="form-field sale_price_dates_fields hidden">
<p class="form-row form-row-first">
<label>' . __( 'Sale start date', 'woocommerce' ) . '</label>
<input type="text" class="sale_price_dates_from" name="variable_sale_price_dates_from[' . $loop . ']" value="' . esc_attr( $sale_price_dates_from ) . '" placeholder="' . _x( 'From&hellip;', 'placeholder', 'woocommerce' ) . ' YYYY-MM-DD" maxlength="10" pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" />
</p>
<p class="form-row form-row-last">
<label>' . __( 'Sale end date', 'woocommerce' ) . '</label>
<input type="text" class="sale_price_dates_to" name="variable_sale_price_dates_to[' . $loop . ']" id="_sale_price_dates_to" value="' . esc_attr( $sale_price_dates_to ) . '" placeholder="' . _x( 'To&hellip;', 'placeholder', 'woocommerce' ) . ' YYYY-MM-DD" maxlength="10" pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" />
</p>
</div>';
/** /**
* woocommerce_variation_options_pricing action. * woocommerce_variation_options_pricing action.
* *
@ -134,24 +160,35 @@ extract( $variation_data );
?> ?>
</div> </div>
<?php if ( 'yes' == get_option( 'woocommerce_manage_stock' ) ) : ?> <?php if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) : ?>
<div class="show_if_variation_manage_stock" style="display: none;"> <div class="show_if_variation_manage_stock" style="display: none;">
<p class="form-row form-row-first">
<label><?php _e( 'Stock quantity', 'woocommerce' ); ?> <?php echo wc_help_tip( __( 'Enter a quantity to enable stock management at variation level, or leave blank to use the parent product\'s options.', 'woocommerce' ) ); ?></label>
<input type="number" size="5" name="variable_stock[<?php echo $loop; ?>]" value="<?php if ( isset( $_stock ) ) echo esc_attr( wc_stock_amount( $_stock ) ); ?>" step="any" />
</p>
<p class="form-row form-row-last">
<label><?php _e( 'Allow backorders?', 'woocommerce' ); ?></label>
<select name="variable_backorders[<?php echo $loop; ?>]">
<?php
foreach ( $parent_data['backorder_options'] as $key => $value ) {
echo '<option value="' . esc_attr( $key ) . '" ' . selected( $key === $_backorders, true, false ) . '>' . esc_html( $value ) . '</option>';
}
?>
</select>
</p>
<?php <?php
woocommerce_wp_text_input( array(
'id' => "variable_stock{$loop}",
'name' => "variable_stock[{$loop}]",
'value' => $variation_object->get_stock_quantity( 'edit' ),
'label' => __( 'Stock quantity', 'woocommerce' ),
'desc_tip' => true,
'description' => __( 'Enter a quantity to enable stock management at variation level, or leave blank to use the parent product\'s options.', 'woocommerce' ),
'type' => 'number',
'custom_attributes' => array(
'step' => 'any',
),
'data_type' => 'stock',
'wrapper_class' => 'form-row form-row-first',
) );
woocommerce_wp_select( array(
'id' => "variable_backorders{$loop}",
'name' => "variable_backorders[{$loop}]",
'value' => $variation_object->get_backorders( 'edit' ),
'label' => __( 'Allow backorders?', 'woocommerce' ),
'options' => wc_get_product_backorder_options(),
'desc_tip' => true,
'description' => __( 'If managing stock, this controls whether or not backorders are allowed. If enabled, stock quantity can go below 0.', 'woocommerce' ),
'wrapper_class' => 'form-row form-row-last',
) );
/** /**
* woocommerce_variation_options_inventory action. * woocommerce_variation_options_inventory action.
* *
@ -166,37 +203,42 @@ extract( $variation_data );
</div> </div>
<?php endif; ?> <?php endif; ?>
<div class=""> <div>
<p class="form-row form-row-full"> <?php
<label><?php _e( 'Stock status', 'woocommerce' ); ?> <?php echo wc_help_tip( __( 'Controls whether or not the product is listed as "in stock" or "out of stock" on the frontend.', 'woocommerce' ) ); ?></label> woocommerce_wp_select( array(
<select name="variable_stock_status[<?php echo $loop; ?>]"> 'id' => "variable_stock_status{$loop}",
<?php 'name' => "variable_stock_status[{$loop}]",
foreach ( $parent_data['stock_status_options'] as $key => $value ) { 'value' => $variation_object->get_stock_status( 'edit' ),
echo '<option value="' . esc_attr( $key === $_stock_status ? '' : $key ) . '" ' . selected( $key === $_stock_status, true, false ) . '>' . esc_html( $value ) . '</option>'; 'label' => __( 'Stock status', 'woocommerce' ),
} 'options' => wc_get_product_stock_status_options(),
?> 'desc_tip' => true,
</select> 'description' => __( 'Controls whether or not the product is listed as "in stock" or "out of stock" on the frontend.', 'woocommerce' ),
</p> 'wrapper_class' => 'form-row form-row-full',
</div> ) );
<?php if ( wc_product_weight_enabled() || wc_product_dimensions_enabled() ) : ?> if ( wc_product_weight_enabled() ) {
<div> /* translators: %s: weight unit */
<?php if ( wc_product_weight_enabled() ) : ?> $label = sprintf(
<p class="form-row hide_if_variation_virtual form-row-first"> __( 'Weight (%s)', 'woocommerce' ),
<label><?php esc_html( get_option( 'woocommerce_weight_unit' ) )
/* translators: %s: weight unit */ );
printf(
__( 'Weight (%s)', 'woocommerce' ), woocommerce_wp_text_input( array(
esc_html( get_option( 'woocommerce_weight_unit' ) ) 'id' => "variable_weight{$loop}",
); 'name' => "variable_weight[{$loop}]",
?> <?php echo wc_help_tip( __( 'Enter a weight for this variation or leave blank to use the parent product weight.', 'woocommerce' ) ); ?></label> 'value' => wc_format_localized_decimal( $variation_object->get_weight( 'edit' ) ),
<input type="text" size="5" name="variable_weight[<?php echo $loop; ?>]" value="<?php if ( isset( $_weight ) ) echo esc_attr( $_weight ); ?>" placeholder="<?php echo esc_attr( $parent_data['weight'] ); ?>" class="wc_input_decimal" /> 'placeholder' => wc_format_localized_decimal( $product_object->get_weight() ),
</p> 'label' => $label,
<?php else : ?> 'desc_tip' => true,
<p>&nbsp;</p> 'description' => __( 'Weight in decimal form', 'woocommerce' ),
<?php endif; ?> 'type' => 'text',
<?php if ( wc_product_dimensions_enabled() ) : ?> 'data_type' => 'decimal',
<p class="form-row dimensions_field hide_if_variation_virtual form-row-last"> 'wrapper_class' => 'form-row form-row-first hide_if_variation_virtual',
) );
}
if ( wc_product_dimensions_enabled() ) {
?><p class="form-field form-row dimensions_field hide_if_variation_virtual form-row-last">
<label for="product_length"><?php <label for="product_length"><?php
/* translators: %s: dimension unit */ /* translators: %s: dimension unit */
printf( printf(
@ -204,73 +246,78 @@ extract( $variation_data );
get_option( 'woocommerce_dimension_unit' ) get_option( 'woocommerce_dimension_unit' )
); );
?></label> ?></label>
<input id="product_length" class="input-text wc_input_decimal" size="6" type="text" name="variable_length[<?php echo $loop; ?>]" value="<?php if ( isset( $_length ) ) echo esc_attr( $_length ); ?>" placeholder="<?php echo esc_attr( $parent_data['length'] ); ?>" /> <?php echo wc_help_tip( __( 'Length x width x height in decimal form', 'woocommerce' ) ); ?>
<input class="input-text wc_input_decimal" size="6" type="text" name="variable_width[<?php echo $loop; ?>]" value="<?php if ( isset( $_width ) ) echo esc_attr( $_width ); ?>" placeholder="<?php echo esc_attr( $parent_data['width'] ); ?>" /> <span class="wrap">
<input class="input-text wc_input_decimal last" size="6" type="text" name="variable_height[<?php echo $loop; ?>]" value="<?php if ( isset( $_height ) ) echo esc_attr( $_height ); ?>" placeholder="<?php echo esc_attr( $parent_data['height'] ); ?>" /> <input id="product_length" placeholder="<?php esc_attr_e( 'Length', 'woocommerce' ); ?>" class="input-text wc_input_decimal" size="6" type="text" name="variable_length[<?php echo $loop; ?>]" value="<?php echo esc_attr( wc_format_localized_decimal( $variation_object->get_length( 'edit' ) ) ); ?>" />
</p> <input placeholder="<?php esc_attr_e( 'Width', 'woocommerce' ); ?>" class="input-text wc_input_decimal" size="6" type="text" name="variable_width[<?php echo $loop; ?>]" value="<?php echo esc_attr( wc_format_localized_decimal( $variation_object->get_width( 'edit' ) ) ); ?>" />
<?php else : ?> <input placeholder="<?php esc_attr_e( 'Height', 'woocommerce' ); ?>" class="input-text wc_input_decimal last" size="6" type="text" name="variable_height[<?php echo $loop; ?>]" value="<?php echo esc_attr( wc_format_localized_decimal( $variation_object->get_height( 'edit' ) ) ); ?>" />
<p>&nbsp;</p> </span>
<?php endif; ?> </p><?php
}
/**
* woocommerce_variation_options_dimensions action.
*
* @since 2.5.0
*
* @param int $loop
* @param array $variation_data
* @param WP_Post $variation
*/
do_action( 'woocommerce_variation_options_dimensions', $loop, $variation_data, $variation );
?>
</div>
<?php
/**
* woocommerce_variation_options_dimensions action.
*
* @since 2.5.0
*
* @param int $loop
* @param array $variation_data
* @param WP_Post $variation
*/
do_action( 'woocommerce_variation_options_dimensions', $loop, $variation_data, $variation );
?>
</div>
<?php endif; ?>
<div> <div>
<p class="form-row hide_if_variation_virtual form-row-full"><label><?php _e( 'Shipping class', 'woocommerce' ); ?></label> <?php <p class="form-row hide_if_variation_virtual form-row-full"><label><?php _e( 'Shipping class', 'woocommerce' ); ?></label> <?php
$args = array( wp_dropdown_categories( array(
'taxonomy' => 'product_shipping_class', 'taxonomy' => 'product_shipping_class',
'hide_empty' => 0, 'hide_empty' => 0,
'show_option_none' => __( 'Same as parent', 'woocommerce' ), 'show_option_none' => __( 'Same as parent', 'woocommerce' ),
'name' => 'variable_shipping_class[' . $loop . ']', 'name' => 'variable_shipping_class[' . $loop . ']',
'id' => '', 'id' => '',
'selected' => isset( $shipping_class ) ? esc_attr( $shipping_class ) : '', 'selected' => $variation_object->get_shipping_class_id( 'edit' ),
'echo' => 0, ) );
);
echo wp_dropdown_categories( $args );
?></p> ?></p>
<?php if ( wc_tax_enabled() ) : ?> <?php
<p class="form-row form-row-full"> if ( wc_tax_enabled() ) {
<label><?php _e( 'Tax class', 'woocommerce' ); ?></label> woocommerce_wp_select( array(
<select name="variable_tax_class[<?php echo $loop; ?>]"> 'id' => "variable_tax_class{$loop}",
<option value="parent" <?php selected( is_null( $_tax_class ), true ); ?>><?php _e( 'Same as parent', 'woocommerce' ); ?></option> 'name' => "variable_tax_class[{$loop}]",
<?php 'value' => $variation_object->get_tax_class( 'edit' ),
foreach ( $parent_data['tax_class_options'] as $key => $value ) 'label' => __( 'Tax class', 'woocommerce' ),
echo '<option value="' . esc_attr( $key ) . '" ' . selected( $key === $_tax_class, true, false ) . '>' . esc_html( $value ) . '</option>'; 'options' => array_merge( array( 'parent' => __( 'Same as parent', 'woocommerce' ) ), wc_get_product_tax_class_options() ),
?></select> 'desc_tip' => 'true',
</p> 'description' => __( 'Choose a tax class for this product. Tax classes are used to apply different tax rates specific to certain types of product.', 'woocommerce' ),
'wrapper_class' => 'form-row form-row-full',
) );
<?php /**
/** * woocommerce_variation_options_tax action.
* woocommerce_variation_options_tax action. *
* * @since 2.5.0
* @since 2.5.0 *
* * @param int $loop
* @param int $loop * @param array $variation_data
* @param array $variation_data * @param WP_Post $variation
* @param WP_Post $variation */
*/ do_action( 'woocommerce_variation_options_tax', $loop, $variation_data, $variation );
do_action( 'woocommerce_variation_options_tax', $loop, $variation_data, $variation ); }
?> ?>
<?php endif; ?>
</div> </div>
<div> <div>
<p class="form-row form-row-full"> <?php
<label><?php _e( 'Variation description', 'woocommerce' ); ?></label> woocommerce_wp_textarea_input( array(
<textarea name="variable_description[<?php echo $loop; ?>]" rows="3" style="width:100%;"><?php echo isset( $variation_data['_variation_description'] ) ? esc_textarea( $variation_data['_variation_description'] ) : ''; ?></textarea> 'id' => "variable_description{$loop}",
</p> 'name' => "variable_description[{$loop}]",
'value' => $variation_object->get_description( 'edit' ),
'label' => __( 'Description', 'woocommerce' ),
'desc_tip' => true,
'description' => __( 'Enter an optional description for this variation.', 'woocommerce' ),
'wrapper_class' => 'form-row form-row-full',
) );
?>
</div> </div>
<div class="show_if_variation_downloadable" style="display: none;"> <div class="show_if_variation_downloadable" style="display: none;">
<div class="form-row form-row-full downloadable_files"> <div class="form-row form-row-full downloadable_files">
@ -285,14 +332,8 @@ extract( $variation_data );
</thead> </thead>
<tbody> <tbody>
<?php <?php
if ( $_downloadable_files ) { if ( $downloads = $variation_object->get_downloads( 'edit' ) ) {
foreach ( $_downloadable_files as $key => $file ) { foreach ( $downloads as $key => $file ) {
if ( ! is_array( $file ) ) {
$file = array(
'file' => $file,
'name' => '',
);
}
include( 'html-product-variation-download.php' ); include( 'html-product-variation-download.php' );
} }
} }
@ -317,16 +358,39 @@ extract( $variation_data );
</div> </div>
</div> </div>
<div class="show_if_variation_downloadable" style="display: none;"> <div class="show_if_variation_downloadable" style="display: none;">
<p class="form-row form-row-first">
<label><?php _e( 'Download limit', 'woocommerce' ); ?> <?php echo wc_help_tip( __( 'Leave blank for unlimited re-downloads.', 'woocommerce' ) ); ?></label>
<input type="number" size="5" name="variable_download_limit[<?php echo $loop; ?>]" value="<?php if ( isset( $_download_limit ) ) echo esc_attr( $_download_limit ); ?>" placeholder="<?php esc_attr_e( 'Unlimited', 'woocommerce' ); ?>" step="1" min="0" />
</p>
<p class="form-row form-row-last">
<label><?php _e( 'Download expiry', 'woocommerce' ); ?> <?php echo wc_help_tip( __( 'Enter the number of days before a download link expires, or leave blank.', 'woocommerce' ) ); ?></label>
<input type="number" size="5" name="variable_download_expiry[<?php echo $loop; ?>]" value="<?php if ( isset( $_download_expiry ) ) echo esc_attr( $_download_expiry ); ?>" placeholder="<?php esc_attr_e( 'Unlimited', 'woocommerce' ); ?>" step="1" min="0" />
</p>
<?php <?php
woocommerce_wp_text_input( array(
'id' => "variable_download_limit{$loop}",
'name' => "variable_download_limit[{$loop}]",
'value' => $variation_object->get_download_limit( 'edit' ) < 0 ? '' : $variation_object->get_download_limit( 'edit' ),
'label' => __( 'Download limit', 'woocommerce' ),
'placeholder' => __( 'Unlimited', 'woocommerce' ),
'description' => __( 'Leave blank for unlimited re-downloads.', 'woocommerce' ),
'type' => 'number',
'desc_tip' => true,
'custom_attributes' => array(
'step' => '1',
'min' => '0',
),
'wrapper_class' => 'form-row form-row-first',
) );
woocommerce_wp_text_input( array(
'id' => "variable_download_expiry{$loop}",
'name' => "variable_download_expiry[{$loop}]",
'value' => $variation_object->get_download_expiry( 'edit' ) < 0 ? '' : $variation_object->get_download_expiry( 'edit' ),
'label' => __( 'Download expiry', 'woocommerce' ),
'placeholder' => __( 'Never', 'woocommerce' ),
'description' => __( 'Enter the number of days before a download link expires, or leave blank.', 'woocommerce' ),
'type' => 'number',
'desc_tip' => true,
'custom_attributes' => array(
'step' => '1',
'min' => '0',
),
'wrapper_class' => 'form-row form-row-last',
) );
/** /**
* woocommerce_variation_options_download action. * woocommerce_variation_options_download action.
* *

View File

@ -76,7 +76,7 @@ class WC_Report_Stock extends WP_List_Table {
public function column_default( $item, $column_name ) { public function column_default( $item, $column_name ) {
global $product; global $product;
if ( ! $product || $product->id !== $item->id ) { if ( ! $product || $product->get_id() !== $item->id ) {
$product = wc_get_product( $item->id ); $product = wc_get_product( $item->id );
} }
@ -87,7 +87,7 @@ class WC_Report_Stock extends WP_List_Table {
echo $sku . ' - '; echo $sku . ' - ';
} }
echo $product->get_title(); echo $product->get_name();
// Get variation data // Get variation data
if ( $product->is_type( 'variation' ) ) { if ( $product->is_type( 'variation' ) ) {

View File

@ -72,13 +72,6 @@ class WC_Settings_Tax extends WC_Settings_Page {
* @return array * @return array
*/ */
public function get_settings() { public function get_settings() {
$tax_classes = WC_Tax::get_tax_classes();
$classes_options = array();
foreach ( $tax_classes as $class ) {
$classes_options[ sanitize_title( $class ) ] = esc_html( $class );
}
return apply_filters( 'woocommerce_get_settings_' . $this->id, include( 'views/settings-tax.php' ) ); return apply_filters( 'woocommerce_get_settings_' . $this->id, include( 'views/settings-tax.php' ) );
} }

View File

@ -39,10 +39,10 @@ $settings = array(
'desc' => __( 'Optionally control which tax class shipping gets, or leave it so shipping tax is based on the cart items themselves.', 'woocommerce' ), 'desc' => __( 'Optionally control which tax class shipping gets, or leave it so shipping tax is based on the cart items themselves.', 'woocommerce' ),
'id' => 'woocommerce_shipping_tax_class', 'id' => 'woocommerce_shipping_tax_class',
'css' => 'min-width:150px;', 'css' => 'min-width:150px;',
'default' => '', 'default' => 'inherit',
'type' => 'select', 'type' => 'select',
'class' => 'wc-enhanced-select', 'class' => 'wc-enhanced-select',
'options' => array( '' => __( 'Shipping tax class based on cart items', 'woocommerce' ), 'standard' => __( 'Standard', 'woocommerce' ) ) + $classes_options, 'options' => array_merge( array( 'inherit' => __( 'Shipping tax class based on cart items', 'woocommerce' ) ), wc_get_product_tax_class_options() ),
'desc_tip' => true, 'desc_tip' => true,
), ),

View File

@ -218,12 +218,9 @@ if ( ! defined( 'ABSPATH' ) ) {
<span class="input-text-wrap"> <span class="input-text-wrap">
<select class="stock_status" name="_stock_status"> <select class="stock_status" name="_stock_status">
<?php <?php
$options = array( echo '<option value="">' . __( '— No Change —', 'woocommerce' ) . '</option>';
'' => __( '— No change —', 'woocommerce' ),
'instock' => __( 'In stock', 'woocommerce' ), foreach ( wc_get_product_stock_status_options() as $key => $value ) {
'outofstock' => __( 'Out of stock', 'woocommerce' ),
);
foreach ( $options as $key => $value ) {
echo '<option value="' . esc_attr( $key ) . '">' . $value . '</option>'; echo '<option value="' . esc_attr( $key ) . '">' . $value . '</option>';
} }
?> ?>
@ -277,13 +274,9 @@ if ( ! defined( 'ABSPATH' ) ) {
<span class="input-text-wrap"> <span class="input-text-wrap">
<select class="backorders" name="_backorders"> <select class="backorders" name="_backorders">
<?php <?php
$options = array( echo '<option value="">' . __( '— No Change —', 'woocommerce' ) . '</option>';
'' => __( '— No change —', 'woocommerce' ),
'no' => __( 'Do not allow', 'woocommerce' ), foreach ( wc_get_product_backorder_options() as $key => $value ) {
'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
'yes' => __( 'Allow', 'woocommerce' ),
);
foreach ( $options as $key => $value ) {
echo '<option value="' . esc_attr( $key ) . '">' . $value . '</option>'; echo '<option value="' . esc_attr( $key ) . '">' . $value . '</option>';
} }
?> ?>

View File

@ -162,11 +162,7 @@ if ( ! defined( 'ABSPATH' ) ) {
<span class="input-text-wrap"> <span class="input-text-wrap">
<select class="stock_status" name="_stock_status"> <select class="stock_status" name="_stock_status">
<?php <?php
$options = array( foreach ( wc_get_product_stock_status_options() as $key => $value ) {
'instock' => __( 'In stock', 'woocommerce' ),
'outofstock' => __( 'Out of stock', 'woocommerce' ),
);
foreach ( $options as $key => $value ) {
echo '<option value="' . esc_attr( $key ) . '">' . $value . '</option>'; echo '<option value="' . esc_attr( $key ) . '">' . $value . '</option>';
} }
?> ?>
@ -197,12 +193,7 @@ if ( ! defined( 'ABSPATH' ) ) {
<span class="input-text-wrap"> <span class="input-text-wrap">
<select class="backorders" name="_backorders"> <select class="backorders" name="_backorders">
<?php <?php
$options = array( foreach ( wc_get_product_backorder_options() as $key => $value ) {
'no' => __( 'Do not allow', 'woocommerce' ),
'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
'yes' => __( 'Allow', 'woocommerce' ),
);
foreach ( $options as $key => $value ) {
echo '<option value="' . esc_attr( $key ) . '">' . $value . '</option>'; echo '<option value="' . esc_attr( $key ) . '">' . $value . '</option>';
} }
?> ?>

View File

@ -27,6 +27,7 @@ function woocommerce_wp_text_input( $field ) {
$field['value'] = isset( $field['value'] ) ? $field['value'] : get_post_meta( $thepostid, $field['id'], true ); $field['value'] = isset( $field['value'] ) ? $field['value'] : get_post_meta( $thepostid, $field['id'], true );
$field['name'] = isset( $field['name'] ) ? $field['name'] : $field['id']; $field['name'] = isset( $field['name'] ) ? $field['name'] : $field['id'];
$field['type'] = isset( $field['type'] ) ? $field['type'] : 'text'; $field['type'] = isset( $field['type'] ) ? $field['type'] : 'text';
$field['desc_tip'] = isset( $field['desc_tip'] ) ? $field['desc_tip'] : false;
$data_type = empty( $field['data_type'] ) ? '' : $field['data_type']; $data_type = empty( $field['data_type'] ) ? '' : $field['data_type'];
switch ( $data_type ) { switch ( $data_type ) {
@ -61,16 +62,19 @@ function woocommerce_wp_text_input( $field ) {
} }
} }
echo '<p class="form-field ' . esc_attr( $field['id'] ) . '_field ' . esc_attr( $field['wrapper_class'] ) . '"><label for="' . esc_attr( $field['id'] ) . '">' . wp_kses_post( $field['label'] ) . '</label><input type="' . esc_attr( $field['type'] ) . '" class="' . esc_attr( $field['class'] ) . '" style="' . esc_attr( $field['style'] ) . '" name="' . esc_attr( $field['name'] ) . '" id="' . esc_attr( $field['id'] ) . '" value="' . esc_attr( $field['value'] ) . '" placeholder="' . esc_attr( $field['placeholder'] ) . '" ' . implode( ' ', $custom_attributes ) . ' /> '; echo '<p class="form-field ' . esc_attr( $field['id'] ) . '_field ' . esc_attr( $field['wrapper_class'] ) . '">
<label for="' . esc_attr( $field['id'] ) . '">' . wp_kses_post( $field['label'] ) . '</label>';
if ( ! empty( $field['description'] ) ) { if ( ! empty( $field['description'] ) && false !== $field['desc_tip'] ) {
echo wc_help_tip( $field['description'] );
if ( isset( $field['desc_tip'] ) && false !== $field['desc_tip'] ) {
echo wc_help_tip( $field['description'] );
} else {
echo '<span class="description">' . wp_kses_post( $field['description'] ) . '</span>';
}
} }
echo '<input type="' . esc_attr( $field['type'] ) . '" class="' . esc_attr( $field['class'] ) . '" style="' . esc_attr( $field['style'] ) . '" name="' . esc_attr( $field['name'] ) . '" id="' . esc_attr( $field['id'] ) . '" value="' . esc_attr( $field['value'] ) . '" placeholder="' . esc_attr( $field['placeholder'] ) . '" ' . implode( ' ', $custom_attributes ) . ' /> ';
if ( ! empty( $field['description'] ) && false === $field['desc_tip'] ) {
echo '<span class="description">' . wp_kses_post( $field['description'] ) . '</span>';
}
echo '</p>'; echo '</p>';
} }
@ -103,6 +107,8 @@ function woocommerce_wp_textarea_input( $field ) {
$field['style'] = isset( $field['style'] ) ? $field['style'] : ''; $field['style'] = isset( $field['style'] ) ? $field['style'] : '';
$field['wrapper_class'] = isset( $field['wrapper_class'] ) ? $field['wrapper_class'] : ''; $field['wrapper_class'] = isset( $field['wrapper_class'] ) ? $field['wrapper_class'] : '';
$field['value'] = isset( $field['value'] ) ? $field['value'] : get_post_meta( $thepostid, $field['id'], true ); $field['value'] = isset( $field['value'] ) ? $field['value'] : get_post_meta( $thepostid, $field['id'], true );
$field['desc_tip'] = isset( $field['desc_tip'] ) ? $field['desc_tip'] : false;
$field['name'] = isset( $field['name'] ) ? $field['name'] : $field['id'];
// Custom attribute handling // Custom attribute handling
$custom_attributes = array(); $custom_attributes = array();
@ -114,16 +120,19 @@ function woocommerce_wp_textarea_input( $field ) {
} }
} }
echo '<p class="form-field ' . esc_attr( $field['id'] ) . '_field ' . esc_attr( $field['wrapper_class'] ) . '"><label for="' . esc_attr( $field['id'] ) . '">' . wp_kses_post( $field['label'] ) . '</label><textarea class="' . esc_attr( $field['class'] ) . '" style="' . esc_attr( $field['style'] ) . '" name="' . esc_attr( $field['id'] ) . '" id="' . esc_attr( $field['id'] ) . '" placeholder="' . esc_attr( $field['placeholder'] ) . '" rows="2" cols="20" ' . implode( ' ', $custom_attributes ) . '>' . esc_textarea( $field['value'] ) . '</textarea> '; echo '<p class="form-field ' . esc_attr( $field['id'] ) . '_field ' . esc_attr( $field['wrapper_class'] ) . '">
<label for="' . esc_attr( $field['id'] ) . '">' . wp_kses_post( $field['label'] ) . '</label>';
if ( ! empty( $field['description'] ) ) { if ( ! empty( $field['description'] ) && false !== $field['desc_tip'] ) {
echo wc_help_tip( $field['description'] );
if ( isset( $field['desc_tip'] ) && false !== $field['desc_tip'] ) {
echo wc_help_tip( $field['description'] );
} else {
echo '<span class="description">' . wp_kses_post( $field['description'] ) . '</span>';
}
} }
echo '<textarea class="' . esc_attr( $field['class'] ) . '" style="' . esc_attr( $field['style'] ) . '" name="' . esc_attr( $field['name'] ) . '" id="' . esc_attr( $field['id'] ) . '" placeholder="' . esc_attr( $field['placeholder'] ) . '" rows="2" cols="20" ' . implode( ' ', $custom_attributes ) . '>' . esc_textarea( $field['value'] ) . '</textarea> ';
if ( ! empty( $field['description'] ) && false === $field['desc_tip'] ) {
echo '<span class="description">' . wp_kses_post( $field['description'] ) . '</span>';
}
echo '</p>'; echo '</p>';
} }
@ -142,6 +151,7 @@ function woocommerce_wp_checkbox( $field ) {
$field['value'] = isset( $field['value'] ) ? $field['value'] : get_post_meta( $thepostid, $field['id'], true ); $field['value'] = isset( $field['value'] ) ? $field['value'] : get_post_meta( $thepostid, $field['id'], true );
$field['cbvalue'] = isset( $field['cbvalue'] ) ? $field['cbvalue'] : 'yes'; $field['cbvalue'] = isset( $field['cbvalue'] ) ? $field['cbvalue'] : 'yes';
$field['name'] = isset( $field['name'] ) ? $field['name'] : $field['id']; $field['name'] = isset( $field['name'] ) ? $field['name'] : $field['id'];
$field['desc_tip'] = isset( $field['desc_tip'] ) ? $field['desc_tip'] : false;
// Custom attribute handling // Custom attribute handling
$custom_attributes = array(); $custom_attributes = array();
@ -153,15 +163,17 @@ function woocommerce_wp_checkbox( $field ) {
} }
} }
echo '<p class="form-field ' . esc_attr( $field['id'] ) . '_field ' . esc_attr( $field['wrapper_class'] ) . '"><label for="' . esc_attr( $field['id'] ) . '">' . wp_kses_post( $field['label'] ) . '</label><input type="checkbox" class="' . esc_attr( $field['class'] ) . '" style="' . esc_attr( $field['style'] ) . '" name="' . esc_attr( $field['name'] ) . '" id="' . esc_attr( $field['id'] ) . '" value="' . esc_attr( $field['cbvalue'] ) . '" ' . checked( $field['value'], $field['cbvalue'], false ) . ' ' . implode( ' ', $custom_attributes ) . '/> '; echo '<p class="form-field ' . esc_attr( $field['id'] ) . '_field ' . esc_attr( $field['wrapper_class'] ) . '">
<label for="' . esc_attr( $field['id'] ) . '">' . wp_kses_post( $field['label'] ) . '</label>';
if ( ! empty( $field['description'] ) ) { if ( ! empty( $field['description'] ) && false !== $field['desc_tip'] ) {
echo wc_help_tip( $field['description'] );
}
if ( isset( $field['desc_tip'] ) && false !== $field['desc_tip'] ) { echo '<input type="checkbox" class="' . esc_attr( $field['class'] ) . '" style="' . esc_attr( $field['style'] ) . '" name="' . esc_attr( $field['name'] ) . '" id="' . esc_attr( $field['id'] ) . '" value="' . esc_attr( $field['cbvalue'] ) . '" ' . checked( $field['value'], $field['cbvalue'], false ) . ' ' . implode( ' ', $custom_attributes ) . '/> ';
echo wc_help_tip( $field['description'] );
} else { if ( ! empty( $field['description'] ) && false === $field['desc_tip'] ) {
echo '<span class="description">' . wp_kses_post( $field['description'] ) . '</span>'; echo '<span class="description">' . wp_kses_post( $field['description'] ) . '</span>';
}
} }
echo '</p>'; echo '</p>';
@ -181,6 +193,7 @@ function woocommerce_wp_select( $field ) {
$field['wrapper_class'] = isset( $field['wrapper_class'] ) ? $field['wrapper_class'] : ''; $field['wrapper_class'] = isset( $field['wrapper_class'] ) ? $field['wrapper_class'] : '';
$field['value'] = isset( $field['value'] ) ? $field['value'] : get_post_meta( $thepostid, $field['id'], true ); $field['value'] = isset( $field['value'] ) ? $field['value'] : get_post_meta( $thepostid, $field['id'], true );
$field['name'] = isset( $field['name'] ) ? $field['name'] : $field['id']; $field['name'] = isset( $field['name'] ) ? $field['name'] : $field['id'];
$field['desc_tip'] = isset( $field['desc_tip'] ) ? $field['desc_tip'] : false;
// Custom attribute handling // Custom attribute handling
$custom_attributes = array(); $custom_attributes = array();
@ -192,7 +205,14 @@ function woocommerce_wp_select( $field ) {
} }
} }
echo '<p class="form-field ' . esc_attr( $field['id'] ) . '_field ' . esc_attr( $field['wrapper_class'] ) . '"><label for="' . esc_attr( $field['id'] ) . '">' . wp_kses_post( $field['label'] ) . '</label><select id="' . esc_attr( $field['id'] ) . '" name="' . esc_attr( $field['name'] ) . '" class="' . esc_attr( $field['class'] ) . '" style="' . esc_attr( $field['style'] ) . '" ' . implode( ' ', $custom_attributes ) . '>'; echo '<p class="form-field ' . esc_attr( $field['id'] ) . '_field ' . esc_attr( $field['wrapper_class'] ) . '">
<label for="' . esc_attr( $field['id'] ) . '">' . wp_kses_post( $field['label'] ) . '</label>';
if ( ! empty( $field['description'] ) && false !== $field['desc_tip'] ) {
echo wc_help_tip( $field['description'] );
}
echo '<select id="' . esc_attr( $field['id'] ) . '" name="' . esc_attr( $field['name'] ) . '" class="' . esc_attr( $field['class'] ) . '" style="' . esc_attr( $field['style'] ) . '" ' . implode( ' ', $custom_attributes ) . '>';
foreach ( $field['options'] as $key => $value ) { foreach ( $field['options'] as $key => $value ) {
echo '<option value="' . esc_attr( $key ) . '" ' . selected( esc_attr( $field['value'] ), esc_attr( $key ), false ) . '>' . esc_html( $value ) . '</option>'; echo '<option value="' . esc_attr( $key ) . '" ' . selected( esc_attr( $field['value'] ), esc_attr( $key ), false ) . '>' . esc_html( $value ) . '</option>';
@ -200,14 +220,10 @@ function woocommerce_wp_select( $field ) {
echo '</select> '; echo '</select> ';
if ( ! empty( $field['description'] ) ) { if ( ! empty( $field['description'] ) && false === $field['desc_tip'] ) {
echo '<span class="description">' . wp_kses_post( $field['description'] ) . '</span>';
if ( isset( $field['desc_tip'] ) && false !== $field['desc_tip'] ) {
echo wc_help_tip( $field['description'] );
} else {
echo '<span class="description">' . wp_kses_post( $field['description'] ) . '</span>';
}
} }
echo '</p>'; echo '</p>';
} }
@ -225,8 +241,15 @@ function woocommerce_wp_radio( $field ) {
$field['wrapper_class'] = isset( $field['wrapper_class'] ) ? $field['wrapper_class'] : ''; $field['wrapper_class'] = isset( $field['wrapper_class'] ) ? $field['wrapper_class'] : '';
$field['value'] = isset( $field['value'] ) ? $field['value'] : get_post_meta( $thepostid, $field['id'], true ); $field['value'] = isset( $field['value'] ) ? $field['value'] : get_post_meta( $thepostid, $field['id'], true );
$field['name'] = isset( $field['name'] ) ? $field['name'] : $field['id']; $field['name'] = isset( $field['name'] ) ? $field['name'] : $field['id'];
$field['desc_tip'] = isset( $field['desc_tip'] ) ? $field['desc_tip'] : false;
echo '<fieldset class="form-field ' . esc_attr( $field['id'] ) . '_field ' . esc_attr( $field['wrapper_class'] ) . '"><legend>' . wp_kses_post( $field['label'] ) . '</legend><ul class="wc-radios">'; echo '<fieldset class="form-field ' . esc_attr( $field['id'] ) . '_field ' . esc_attr( $field['wrapper_class'] ) . '"><legend>' . wp_kses_post( $field['label'] ) . '</legend>';
if ( ! empty( $field['description'] ) && false !== $field['desc_tip'] ) {
echo wc_help_tip( $field['description'] );
}
echo '<ul class="wc-radios">';
foreach ( $field['options'] as $key => $value ) { foreach ( $field['options'] as $key => $value ) {
@ -242,13 +265,8 @@ function woocommerce_wp_radio( $field ) {
} }
echo '</ul>'; echo '</ul>';
if ( ! empty( $field['description'] ) ) { if ( ! empty( $field['description'] ) && false === $field['desc_tip'] ) {
echo '<span class="description">' . wp_kses_post( $field['description'] ) . '</span>';
if ( isset( $field['desc_tip'] ) && false !== $field['desc_tip'] ) {
echo wc_help_tip( $field['description'] );
} else {
echo '<span class="description">' . wp_kses_post( $field['description'] ) . '</span>';
}
} }
echo '</fieldset>'; echo '</fieldset>';

View File

@ -131,33 +131,32 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Products_Controller
*/ */
public function prepare_item_for_response( $post, $request ) { public function prepare_item_for_response( $post, $request ) {
$variation = wc_get_product( $post ); $variation = wc_get_product( $post );
$post_data = get_post( $variation->get_variation_id() );
$data = array( $data = array(
'id' => $variation->get_variation_id(), 'id' => $variation->get_id(),
'date_created' => wc_rest_prepare_date_response( $post_data->post_date_gmt ), 'date_created' => wc_rest_prepare_date_response( $variation->get_date_created() ),
'date_modified' => wc_rest_prepare_date_response( $post_data->post_modified_gmt ), 'date_modified' => wc_rest_prepare_date_response( $variation->get_date_modified() ),
'description' => $variation->get_variation_description(), 'description' => $variation->get_description(),
'permalink' => $variation->get_permalink(), 'permalink' => $variation->get_permalink(),
'sku' => $variation->get_sku(), 'sku' => $variation->get_sku(),
'price' => $variation->get_price(), 'price' => $variation->get_price(),
'regular_price' => $variation->get_regular_price(), 'regular_price' => $variation->get_regular_price(),
'sale_price' => $variation->get_sale_price(), 'sale_price' => $variation->get_sale_price(),
'date_on_sale_from' => $variation->sale_price_dates_from ? date( 'Y-m-d', $variation->sale_price_dates_from ) : '', 'date_on_sale_from' => $variation->get_date_on_sale_from() ? date( 'Y-m-d', $variation->get_date_on_sale_from() ) : '',
'date_on_sale_to' => $variation->sale_price_dates_to ? date( 'Y-m-d', $variation->sale_price_dates_to ) : '', 'date_on_sale_to' => $variation->get_date_on_sale_to() ? date( 'Y-m-d', $variation->get_date_on_sale_to() ) : '',
'on_sale' => $variation->is_on_sale(), 'on_sale' => $variation->is_on_sale(),
'visible' => $variation->is_visible(), 'visible' => $variation->is_visible(),
'purchasable' => $variation->is_purchasable(), 'purchasable' => $variation->is_purchasable(),
'virtual' => $variation->is_virtual(), 'virtual' => $variation->is_virtual(),
'downloadable' => $variation->is_downloadable(), 'downloadable' => $variation->is_downloadable(),
'downloads' => $this->get_downloads( $variation ), 'downloads' => $this->get_downloads( $variation ),
'download_limit' => '' !== $variation->download_limit ? (int) $variation->download_limit : -1, 'download_limit' => '' !== $variation->get_download_limit() ? (int) $variation->get_download_limit() : -1,
'download_expiry' => '' !== $variation->download_expiry ? (int) $variation->download_expiry : -1, 'download_expiry' => '' !== $variation->get_download_expiry() ? (int) $variation->get_download_expiry() : -1,
'tax_status' => $variation->get_tax_status(), 'tax_status' => $variation->get_tax_status(),
'tax_class' => $variation->get_tax_class(), 'tax_class' => $variation->get_tax_class(),
'manage_stock' => $variation->managing_stock(), 'manage_stock' => $variation->managing_stock(),
'stock_quantity' => $variation->get_stock_quantity(), 'stock_quantity' => $variation->get_stock_quantity(),
'in_stock' => $variation->is_in_stock(), 'in_stock' => $variation->is_in_stock(),
'backorders' => $variation->backorders, 'backorders' => $variation->get_backorders(),
'backorders_allowed' => $variation->backorders_allowed(), 'backorders_allowed' => $variation->backorders_allowed(),
'backordered' => $variation->is_on_backorder(), 'backordered' => $variation->is_on_backorder(),
'weight' => $variation->get_weight(), 'weight' => $variation->get_weight(),
@ -200,27 +199,13 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Products_Controller
* @return WP_Error|stdClass $data Post object. * @return WP_Error|stdClass $data Post object.
*/ */
protected function prepare_item_for_database( $request ) { protected function prepare_item_for_database( $request ) {
$data = new stdClass;
// ID.
if ( isset( $request['id'] ) ) { if ( isset( $request['id'] ) ) {
$data->ID = absint( $request['id'] ); $variation = wc_get_product( absint( $request['id'] ) );
} else {
$variation = new WC_Product_Variation();
} }
// Post content. $variation->set_parent_id( absint( $request['product_id'] ) );
if ( isset( $request['description'] ) ) {
$data->post_content = wp_filter_post_kses( $request['description'] );
}
$data->post_parent = $request['product_id'];
$data->post_author = get_current_user_id();
$data->post_status = 'publish';
// Only when creating
if ( empty( $request['id'] ) ) {
$data->post_type = $this->post_type;
$data->ping_status = 'closed';
}
/** /**
* Filter the query_vars used in `get_items` for the constructed query. * Filter the query_vars used in `get_items` for the constructed query.
@ -228,11 +213,11 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Products_Controller
* The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being
* prepared for insertion. * prepared for insertion.
* *
* @param stdClass $data An object representing a single item prepared * @param WC_Product_Variation $variation An object representing a single item prepared
* for inserting or updating the database. * for inserting or updating the database.
* @param WP_REST_Request $request Request object. * @param WP_REST_Request $request Request object.
*/ */
return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}", $data, $request ); return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}", $variation, $request );
} }
/** /**
@ -245,7 +230,7 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Products_Controller
protected function update_post_meta_fields( $post, $request ) { protected function update_post_meta_fields( $post, $request ) {
try { try {
$variable_product = wc_get_product( $post ); $variable_product = wc_get_product( $post );
$product = $variable_product->parent; $product = wc_get_product( $variable_product->get_parent_id() );
$this->save_variations_data( $product, $request, true ); $this->save_variations_data( $product, $request, true );
return true; return true;
} catch ( WC_REST_Exception $e ) { } catch ( WC_REST_Exception $e ) {
@ -263,7 +248,7 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Products_Controller
protected function add_post_meta_fields( $post, $request ) { protected function add_post_meta_fields( $post, $request ) {
try { try {
$variable_product = wc_get_product( $post->ID ); $variable_product = wc_get_product( $post->ID );
$product = $variable_product->parent; $product = wc_get_product( $variable_product->get_parent_id() );
$request['id'] = $post->ID; $request['id'] = $post->ID;
$this->save_variations_data( $product, $request, true ); $this->save_variations_data( $product, $request, true );
return true; return true;
@ -395,7 +380,7 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Products_Controller
'visible' => array( 'visible' => array(
'description' => __( "Define if the attribute is visible on the \"Additional information\" tab in the product's page.", 'woocommerce' ), 'description' => __( "Define if the attribute is visible on the \"Additional information\" tab in the product's page.", 'woocommerce' ),
'type' => 'boolean', 'type' => 'boolean',
'default' => false, 'default' => true,
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
'purchasable' => array( 'purchasable' => array(
@ -626,7 +611,7 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Products_Controller
$base = str_replace( '(?P<product_id>[\d]+)', $product_id, $this->rest_base ); $base = str_replace( '(?P<product_id>[\d]+)', $product_id, $this->rest_base );
$links = array( $links = array(
'self' => array( 'self' => array(
'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $base, $variation->get_variation_id() ) ), 'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $base, $variation->get_id() ) ),
), ),
'collection' => array( 'collection' => array(
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $base ) ), 'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $base ) ),

File diff suppressed because it is too large Load Diff

View File

@ -80,7 +80,7 @@ class WC_REST_Report_Top_Sellers_Controller extends WC_REST_Report_Sales_Control
if ( $product ) { if ( $product ) {
$top_sellers[] = array( $top_sellers[] = array(
'name' => $product->get_title(), 'name' => $product->get_name(),
'product_id' => (int) $item->product_id, 'product_id' => (int) $item->product_id,
'quantity' => wc_stock_amount( $item->order_item_qty ), 'quantity' => wc_stock_amount( $item->order_item_qty ),
); );

View File

@ -277,7 +277,6 @@ class WC_REST_Taxes_Controller extends WC_REST_Controller {
/** /**
* Take tax data from the request and return the updated or newly created rate. * Take tax data from the request and return the updated or newly created rate.
* *
* @todo Replace with CRUD in 2.7.0
* @param WP_REST_Request $request Full details about the request. * @param WP_REST_Request $request Full details about the request.
* @param stdClass|null $current Existing tax object. * @param stdClass|null $current Existing tax object.
* @return stdClass * @return stdClass

View File

@ -176,7 +176,6 @@ class WC_API_Coupons extends WC_API_Resource {
/** /**
* Create a coupon * Create a coupon
* *
* @TODO implement in 2.2
* @param array $data * @param array $data
* @return array * @return array
*/ */
@ -188,7 +187,6 @@ class WC_API_Coupons extends WC_API_Resource {
/** /**
* Edit a coupon * Edit a coupon
* *
* @TODO implement in 2.2
* @param int $id the coupon ID * @param int $id the coupon ID
* @param array $data * @param array $data
* @return array * @return array
@ -206,7 +204,6 @@ class WC_API_Coupons extends WC_API_Resource {
/** /**
* Delete a coupon * Delete a coupon
* *
* @TODO enable along with PUT/POST in 2.2
* @param int $id the coupon ID * @param int $id the coupon ID
* @param bool $force true to permanently delete coupon, false to move to trash * @param bool $force true to permanently delete coupon, false to move to trash
* @return array * @return array

View File

@ -201,7 +201,6 @@ class WC_API_Customers extends WC_API_Resource {
/** /**
* Create a customer * Create a customer
* *
* @TODO implement in 2.2 with woocommerce_create_new_customer()
* @param array $data * @param array $data
* @return array * @return array
*/ */
@ -216,7 +215,6 @@ class WC_API_Customers extends WC_API_Resource {
/** /**
* Edit a customer * Edit a customer
* *
* @TODO implement in 2.2
* @param int $id the customer ID * @param int $id the customer ID
* @param array $data * @param array $data
* @return array * @return array
@ -234,7 +232,6 @@ class WC_API_Customers extends WC_API_Resource {
/** /**
* Delete a customer * Delete a customer
* *
* @TODO enable along with PUT/POST in 2.2
* @param int $id the customer ID * @param int $id the customer ID
* @return array * @return array
*/ */

View File

@ -284,7 +284,6 @@ class WC_API_Orders extends WC_API_Resource {
/** /**
* Delete an order * Delete an order
* *
* @TODO enable along with POST in 2.2
* @param int $id the order ID * @param int $id the order ID
* @param bool $force true to permanently delete order, false to move to trash * @param bool $force true to permanently delete order, false to move to trash
* @return array * @return array

View File

@ -8,7 +8,7 @@
* @category API * @category API
* @package WooCommerce/API * @package WooCommerce/API
* @since 2.1 * @since 2.1
* @version 2.1 * @version 2.7
*/ */
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
@ -113,14 +113,12 @@ class WC_API_Products extends WC_API_Resource {
// add variations to variable products // add variations to variable products
if ( $product->is_type( 'variable' ) && $product->has_child() ) { if ( $product->is_type( 'variable' ) && $product->has_child() ) {
$product_data['variations'] = $this->get_variation_data( $product ); $product_data['variations'] = $this->get_variation_data( $product );
} }
// add the parent product data to an individual variation // add the parent product data to an individual variation
if ( $product->is_type( 'variation' ) ) { if ( $product->is_type( 'variation' ) ) {
$product_data['parent'] = $this->get_product_data( $product->get_parent_id() );
$product_data['parent'] = $this->get_product_data( $product->parent );
} }
return array( 'product' => apply_filters( 'woocommerce_api_product_response', $product_data, $product, $fields, $this->server ) ); return array( 'product' => apply_filters( 'woocommerce_api_product_response', $product_data, $product, $fields, $this->server ) );
@ -150,7 +148,6 @@ class WC_API_Products extends WC_API_Resource {
/** /**
* Edit a product * Edit a product
* *
* @TODO implement in 2.2
* @param int $id the product ID * @param int $id the product ID
* @param array $data * @param array $data
* @return array * @return array
@ -168,7 +165,6 @@ class WC_API_Products extends WC_API_Resource {
/** /**
* Delete a product * Delete a product
* *
* @TODO enable along with PUT/POST in 2.2
* @param int $id the product ID * @param int $id the product ID
* @param bool $force true to permanently delete order, false to move to trash * @param bool $force true to permanently delete order, false to move to trash
* @return array * @return array
@ -268,14 +264,13 @@ class WC_API_Products extends WC_API_Resource {
* @return array * @return array
*/ */
private function get_product_data( $product ) { private function get_product_data( $product ) {
return array( return array(
'title' => $product->get_title(), 'title' => $product->get_name(),
'id' => (int) $product->is_type( 'variation' ) ? $product->get_variation_id() : $product->id, 'id' => $product->get_id(),
'created_at' => $this->server->format_datetime( $product->get_post_data()->post_date_gmt ), 'created_at' => $this->server->format_datetime( $product->get_date_created() ),
'updated_at' => $this->server->format_datetime( $product->get_post_data()->post_modified_gmt ), 'updated_at' => $this->server->format_datetime( $product->get_date_modified() ),
'type' => $product->product_type, 'type' => $product->get_type(),
'status' => $product->get_post_data()->post_status, 'status' => $product->get_status(),
'downloadable' => $product->is_downloadable(), 'downloadable' => $product->is_downloadable(),
'virtual' => $product->is_virtual(), 'virtual' => $product->is_virtual(),
'permalink' => $product->get_permalink(), 'permalink' => $product->get_permalink(),
@ -288,7 +283,7 @@ class WC_API_Products extends WC_API_Resource {
'tax_status' => $product->get_tax_status(), 'tax_status' => $product->get_tax_status(),
'tax_class' => $product->get_tax_class(), 'tax_class' => $product->get_tax_class(),
'managing_stock' => $product->managing_stock(), 'managing_stock' => $product->managing_stock(),
'stock_quantity' => (int) $product->get_stock_quantity(), 'stock_quantity' => $product->get_stock_quantity(),
'in_stock' => $product->is_in_stock(), 'in_stock' => $product->is_in_stock(),
'backorders_allowed' => $product->backorders_allowed(), 'backorders_allowed' => $product->backorders_allowed(),
'backordered' => $product->is_on_backorder(), 'backordered' => $product->is_on_backorder(),
@ -296,38 +291,38 @@ class WC_API_Products extends WC_API_Resource {
'purchaseable' => $product->is_purchasable(), 'purchaseable' => $product->is_purchasable(),
'featured' => $product->is_featured(), 'featured' => $product->is_featured(),
'visible' => $product->is_visible(), 'visible' => $product->is_visible(),
'catalog_visibility' => $product->visibility, 'catalog_visibility' => $product->get_catalog_visibility(),
'on_sale' => $product->is_on_sale(), 'on_sale' => $product->is_on_sale(),
'weight' => $product->get_weight() ? wc_format_decimal( $product->get_weight(), 2 ) : null, 'weight' => $product->get_weight() ? wc_format_decimal( $product->get_weight(), 2 ) : null,
'dimensions' => array( 'dimensions' => array(
'length' => $product->length, 'length' => $product->get_length(),
'width' => $product->width, 'width' => $product->get_width(),
'height' => $product->height, 'height' => $product->get_height(),
'unit' => get_option( 'woocommerce_dimension_unit' ), 'unit' => get_option( 'woocommerce_dimension_unit' ),
), ),
'shipping_required' => $product->needs_shipping(), 'shipping_required' => $product->needs_shipping(),
'shipping_taxable' => $product->is_shipping_taxable(), 'shipping_taxable' => $product->is_shipping_taxable(),
'shipping_class' => $product->get_shipping_class(), 'shipping_class' => $product->get_shipping_class(),
'shipping_class_id' => ( 0 !== $product->get_shipping_class_id() ) ? $product->get_shipping_class_id() : null, 'shipping_class_id' => ( 0 !== $product->get_shipping_class_id() ) ? $product->get_shipping_class_id() : null,
'description' => apply_filters( 'the_content', $product->get_post_data()->post_content ), 'description' => apply_filters( 'the_content', $product->get_description() ),
'short_description' => apply_filters( 'woocommerce_short_description', $product->get_post_data()->post_excerpt ), 'short_description' => apply_filters( 'woocommerce_short_description', $product->get_short_description() ),
'reviews_allowed' => ( 'open' === $product->get_post_data()->comment_status ), 'reviews_allowed' => $product->get_reviews_allowed(),
'average_rating' => wc_format_decimal( $product->get_average_rating(), 2 ), 'average_rating' => wc_format_decimal( $product->get_average_rating(), 2 ),
'rating_count' => (int) $product->get_rating_count(), 'rating_count' => $product->get_rating_count(),
'related_ids' => array_map( 'absint', array_values( $product->get_related() ) ), 'related_ids' => array_map( 'absint', array_values( wc_get_related_products( $product->get_id() ) ) ),
'upsell_ids' => array_map( 'absint', $product->get_upsells() ), 'upsell_ids' => array_map( 'absint', $product->get_upsell_ids() ),
'cross_sell_ids' => array_map( 'absint', $product->get_cross_sells() ), 'cross_sell_ids' => array_map( 'absint', $product->get_cross_sell_ids() ),
'categories' => wp_get_post_terms( $product->id, 'product_cat', array( 'fields' => 'names' ) ), 'categories' => wp_get_post_terms( $product->get_id(), 'product_cat', array( 'fields' => 'names' ) ),
'tags' => wp_get_post_terms( $product->id, 'product_tag', array( 'fields' => 'names' ) ), 'tags' => wp_get_post_terms( $product->get_id(), 'product_tag', array( 'fields' => 'names' ) ),
'images' => $this->get_images( $product ), 'images' => $this->get_images( $product ),
'featured_src' => wp_get_attachment_url( get_post_thumbnail_id( $product->is_type( 'variation' ) ? $product->variation_id : $product->id ) ), 'featured_src' => wp_get_attachment_url( get_post_thumbnail_id( $product->get_id() ) ),
'attributes' => $this->get_attributes( $product ), 'attributes' => $this->get_attributes( $product ),
'downloads' => $this->get_downloads( $product ), 'downloads' => $this->get_downloads( $product ),
'download_limit' => (int) $product->download_limit, 'download_limit' => $product->get_download_limit(),
'download_expiry' => (int) $product->download_expiry, 'download_expiry' => $product->get_download_expiry(),
'download_type' => $product->download_type, 'download_type' => 'standard',
'purchase_note' => apply_filters( 'the_content', $product->purchase_note ), 'purchase_note' => apply_filters( 'the_content', $product->get_purchase_note() ),
'total_sales' => metadata_exists( 'post', $product->id, 'total_sales' ) ? (int) get_post_meta( $product->id, 'total_sales', true ) : 0, 'total_sales' => $product->get_total_sales(),
'variations' => array(), 'variations' => array(),
'parent' => array(), 'parent' => array(),
); );
@ -341,23 +336,19 @@ class WC_API_Products extends WC_API_Resource {
* @return array * @return array
*/ */
private function get_variation_data( $product ) { private function get_variation_data( $product ) {
$variations = array(); $variations = array();
foreach ( $product->get_children() as $child_id ) { foreach ( $product->get_children() as $child_id ) {
$variation = wc_get_product( $child_id );
$variation = $product->get_child( $child_id );
if ( ! $variation->exists() ) { if ( ! $variation->exists() ) {
continue; continue;
} }
$post_data = get_post( $variation->get_variation_id() );
$variations[] = array( $variations[] = array(
'id' => $variation->get_variation_id(), 'id' => $variation->get_id(),
'created_at' => $this->server->format_datetime( $post_data->post_date_gmt ), 'created_at' => $this->server->format_datetime( $variation->get_date_created() ),
'updated_at' => $this->server->format_datetime( $post_data->post_modified_gmt ), 'updated_at' => $this->server->format_datetime( $variation->get_date_modified() ),
'downloadable' => $variation->is_downloadable(), 'downloadable' => $variation->is_downloadable(),
'virtual' => $variation->is_virtual(), 'virtual' => $variation->is_virtual(),
'permalink' => $variation->get_permalink(), 'permalink' => $variation->get_permalink(),
@ -376,9 +367,9 @@ class WC_API_Products extends WC_API_Resource {
'on_sale' => $variation->is_on_sale(), 'on_sale' => $variation->is_on_sale(),
'weight' => $variation->get_weight() ? wc_format_decimal( $variation->get_weight(), 2 ) : null, 'weight' => $variation->get_weight() ? wc_format_decimal( $variation->get_weight(), 2 ) : null,
'dimensions' => array( 'dimensions' => array(
'length' => $variation->length, 'length' => $variation->get_length(),
'width' => $variation->width, 'width' => $variation->get_width(),
'height' => $variation->height, 'height' => $variation->get_height(),
'unit' => get_option( 'woocommerce_dimension_unit' ), 'unit' => get_option( 'woocommerce_dimension_unit' ),
), ),
'shipping_class' => $variation->get_shipping_class(), 'shipping_class' => $variation->get_shipping_class(),
@ -386,8 +377,8 @@ class WC_API_Products extends WC_API_Resource {
'image' => $this->get_images( $variation ), 'image' => $this->get_images( $variation ),
'attributes' => $this->get_attributes( $variation ), 'attributes' => $this->get_attributes( $variation ),
'downloads' => $this->get_downloads( $variation ), 'downloads' => $this->get_downloads( $variation ),
'download_limit' => (int) $product->download_limit, 'download_limit' => (int) $product->get_download_limit(),
'download_expiry' => (int) $product->download_expiry, 'download_expiry' => (int) $product->get_download_expiry(),
); );
} }
@ -402,44 +393,31 @@ class WC_API_Products extends WC_API_Resource {
* @return array * @return array
*/ */
private function get_images( $product ) { private function get_images( $product ) {
$images = $attachment_ids = array();
$product_image = $product->get_image_id();
$images = $attachment_ids = array(); // Add featured image.
if ( ! empty( $product_image ) ) {
if ( $product->is_type( 'variation' ) ) { $attachment_ids[] = $product_image;
if ( has_post_thumbnail( $product->get_variation_id() ) ) {
// add variation image if set
$attachment_ids[] = get_post_thumbnail_id( $product->get_variation_id() );
} elseif ( has_post_thumbnail( $product->id ) ) {
// otherwise use the parent product featured image if set
$attachment_ids[] = get_post_thumbnail_id( $product->id );
}
} else {
// add featured image
if ( has_post_thumbnail( $product->id ) ) {
$attachment_ids[] = get_post_thumbnail_id( $product->id );
}
// add gallery images
$attachment_ids = array_merge( $attachment_ids, $product->get_gallery_attachment_ids() );
} }
// build image data // add gallery images.
$attachment_ids = array_merge( $attachment_ids, $product->get_gallery_image_ids() );
// Build image data.
foreach ( $attachment_ids as $position => $attachment_id ) { foreach ( $attachment_ids as $position => $attachment_id ) {
$attachment_post = get_post( $attachment_id ); $attachment_post = get_post( $attachment_id );
if ( is_null( $attachment_post ) ) if ( is_null( $attachment_post ) ) {
continue; continue;
}
$attachment = wp_get_attachment_image_src( $attachment_id, 'full' ); $attachment = wp_get_attachment_image_src( $attachment_id, 'full' );
if ( ! is_array( $attachment ) ) if ( ! is_array( $attachment ) ) {
continue; continue;
}
$images[] = array( $images[] = array(
'id' => (int) $attachment_id, 'id' => (int) $attachment_id,
@ -452,7 +430,7 @@ class WC_API_Products extends WC_API_Resource {
); );
} }
// set a placeholder image if the product has no images set // Set a placeholder image if the product has no images set.
if ( empty( $images ) ) { if ( empty( $images ) ) {
$images[] = array( $images[] = array(
@ -516,7 +494,7 @@ class WC_API_Products extends WC_API_Resource {
'position' => $attribute['position'], 'position' => $attribute['position'],
'visible' => (bool) $attribute['is_visible'], 'visible' => (bool) $attribute['is_visible'],
'variation' => (bool) $attribute['is_variation'], 'variation' => (bool) $attribute['is_variation'],
'options' => $this->get_attribute_options( $product->id, $attribute ), 'options' => $this->get_attribute_options( $product->get_id(), $attribute ),
); );
} }
} }
@ -537,7 +515,7 @@ class WC_API_Products extends WC_API_Resource {
if ( $product->is_downloadable() ) { if ( $product->is_downloadable() ) {
foreach ( $product->get_files() as $file_id => $file ) { foreach ( $product->get_downloads() as $file_id => $file ) {
$downloads[] = array( $downloads[] = array(
'id' => $file_id, // do not cast as int as this is a hash 'id' => $file_id, // do not cast as int as this is a hash

View File

@ -399,7 +399,7 @@ class WC_API_Reports extends WC_API_Resource {
$product = wc_get_product( $top_seller->product_id ); $product = wc_get_product( $top_seller->product_id );
$top_sellers_data[] = array( $top_sellers_data[] = array(
'title' => $product->get_title(), 'title' => $product->get_name(),
'product_id' => $top_seller->product_id, 'product_id' => $top_seller->product_id,
'quantity' => $top_seller->order_item_qty, 'quantity' => $top_seller->order_item_qty,
); );

File diff suppressed because it is too large Load Diff

View File

@ -247,7 +247,7 @@ class WC_API_Reports extends WC_API_Resource {
if ( $product ) { if ( $product ) {
$top_sellers_data[] = array( $top_sellers_data[] = array(
'title' => $product->get_title(), 'title' => $product->get_name(),
'product_id' => $top_seller->product_id, 'product_id' => $top_seller->product_id,
'quantity' => $top_seller->order_item_qty, 'quantity' => $top_seller->order_item_qty,
); );

File diff suppressed because it is too large Load Diff

View File

@ -247,7 +247,7 @@ class WC_API_Reports extends WC_API_Resource {
if ( $product ) { if ( $product ) {
$top_sellers_data[] = array( $top_sellers_data[] = array(
'title' => $product->get_title(), 'title' => $product->get_name(),
'product_id' => $top_seller->product_id, 'product_id' => $top_seller->product_id,
'quantity' => $top_seller->order_item_qty, 'quantity' => $top_seller->order_item_qty,
); );

View File

@ -128,7 +128,6 @@ class WC_AJAX {
'delete_order_note' => false, 'delete_order_note' => false,
'json_search_products' => false, 'json_search_products' => false,
'json_search_products_and_variations' => false, 'json_search_products_and_variations' => false,
'json_search_grouped_products' => false,
'json_search_downloadable_products_and_variations' => false, 'json_search_downloadable_products_and_variations' => false,
'json_search_customers' => false, 'json_search_customers' => false,
'term_ordering' => false, 'term_ordering' => false,
@ -441,7 +440,8 @@ class WC_AJAX {
die(); die();
} }
$variation_id = $variable_product->get_matching_variation( wp_unslash( $_POST ) ); $data_store = WC_Data_Store::load( 'product' );
$variation_id = $data_store->find_matching_product_variation( $variable_product, wp_unslash( $_POST ) );
if ( $variation_id ) { if ( $variation_id ) {
$variation = $variable_product->get_available_variation( $variation_id ); $variation = $variable_product->get_available_variation( $variation_id );
@ -503,28 +503,18 @@ class WC_AJAX {
die( -1 ); die( -1 );
} }
global $wc_product_attributes;
$thepostid = 0;
$taxonomy = sanitize_text_field( $_POST['taxonomy'] );
$i = absint( $_POST['i'] ); $i = absint( $_POST['i'] );
$position = 0;
$metabox_class = array(); $metabox_class = array();
$attribute = array( $attribute = new WC_Product_Attribute();
'name' => $taxonomy,
'value' => '',
'is_visible' => apply_filters( 'woocommerce_attribute_default_visibility', 1 ),
'is_variation' => apply_filters( 'woocommerce_attribute_default_is_variation', 0 ),
'is_taxonomy' => $taxonomy ? 1 : 0,
);
if ( $taxonomy ) { $attribute->set_id( wc_attribute_taxonomy_id_by_name( sanitize_text_field( $_POST['taxonomy'] ) ) );
$attribute_taxonomy = $wc_product_attributes[ $taxonomy ]; $attribute->set_name( sanitize_text_field( $_POST['taxonomy'] ) );
$metabox_class[] = 'taxonomy'; $attribute->set_visible( apply_filters( 'woocommerce_attribute_default_visibility', 1 ) );
$metabox_class[] = $taxonomy; $attribute->set_variation( apply_filters( 'woocommerce_attribute_default_is_variation', 0 ) );
$attribute_label = wc_attribute_label( $taxonomy );
} else { if ( $attribute->is_taxonomy() ) {
$attribute_label = ''; $metabox_class[] = 'taxonomy';
$metabox_class[] = $attribute->get_name();
} }
include( 'admin/meta-boxes/views/html-product-attribute.php' ); include( 'admin/meta-boxes/views/html-product-attribute.php' );
@ -601,117 +591,18 @@ class WC_AJAX {
die( -1 ); die( -1 );
} }
// Get post data
parse_str( $_POST['data'], $data ); parse_str( $_POST['data'], $data );
$post_id = absint( $_POST['post_id'] ); $post_id = absint( $_POST['post_id'] );
$product = wc_get_product( $post_id );
// Save Attributes $attributes = WC_Meta_Box_Product_Data::prepare_attributes( $data );
$attributes = array();
if ( isset( $data['attribute_names'] ) ) {
$attribute_names = array_map( 'stripslashes', $data['attribute_names'] );
$attribute_values = isset( $data['attribute_values'] ) ? $data['attribute_values'] : array();
if ( isset( $data['attribute_visibility'] ) ) {
$attribute_visibility = $data['attribute_visibility'];
}
if ( isset( $data['attribute_variation'] ) ) {
$attribute_variation = $data['attribute_variation'];
}
$attribute_is_taxonomy = $data['attribute_is_taxonomy'];
$attribute_position = $data['attribute_position'];
$attribute_names_max_key = max( array_keys( $attribute_names ) );
for ( $i = 0; $i <= $attribute_names_max_key; $i++ ) {
if ( empty( $attribute_names[ $i ] ) ) {
continue;
}
$is_visible = isset( $attribute_visibility[ $i ] ) ? 1 : 0;
$is_variation = isset( $attribute_variation[ $i ] ) ? 1 : 0;
$is_taxonomy = $attribute_is_taxonomy[ $i ] ? 1 : 0;
if ( $is_taxonomy ) {
if ( isset( $attribute_values[ $i ] ) ) {
// Select based attributes - Format values (posted values are slugs)
if ( is_array( $attribute_values[ $i ] ) ) {
$values = array_map( 'sanitize_title', $attribute_values[ $i ] );
// Text based attributes - Posted values are term names, wp_set_object_terms wants ids or slugs.
} else {
$values = array();
$raw_values = array_map( 'wc_sanitize_term_text_based', explode( WC_DELIMITER, $attribute_values[ $i ] ) );
foreach ( $raw_values as $value ) {
$term = get_term_by( 'name', $value, $attribute_names[ $i ] );
if ( ! $term ) {
$term = wp_insert_term( $value, $attribute_names[ $i ] );
if ( $term && ! is_wp_error( $term ) ) {
$values[] = $term['term_id'];
}
} else {
$values[] = $term->term_id;
}
}
}
// Remove empty items in the array
$values = array_filter( $values, 'strlen' );
} else {
$values = array();
}
// Update post terms
if ( taxonomy_exists( $attribute_names[ $i ] ) ) {
wp_set_object_terms( $post_id, $values, $attribute_names[ $i ] );
}
if ( ! empty( $values ) ) {
// Add attribute to array, but don't set values
$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
'name' => wc_clean( $attribute_names[ $i ] ),
'value' => '',
'position' => $attribute_position[ $i ],
'is_visible' => $is_visible,
'is_variation' => $is_variation,
'is_taxonomy' => $is_taxonomy,
);
}
} elseif ( isset( $attribute_values[ $i ] ) ) {
// Text based, possibly separated by pipes (WC_DELIMITER). Preserve line breaks in non-variation attributes.
$values = $is_variation ? wc_clean( $attribute_values[ $i ] ) : implode( "\n", array_map( 'wc_clean', explode( "\n", $attribute_values[ $i ] ) ) );
$values = implode( ' ' . WC_DELIMITER . ' ', wc_get_text_attributes( $values ) );
// Custom attribute - Add attribute to array and set the values
$attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
'name' => wc_clean( $attribute_names[ $i ] ),
'value' => $values,
'position' => $attribute_position[ $i ],
'is_visible' => $is_visible,
'is_variation' => $is_variation,
'is_taxonomy' => $is_taxonomy,
);
}
}
}
uasort( $attributes, 'wc_product_attribute_uasort_comparison' );
update_post_meta( $post_id, '_product_attributes', $attributes );
$product->set_attributes( $attributes );
$product->save();
die(); die();
} }
/** /**
* Add variation via ajax function. * Add variation via ajax function. @todo CRUD
*/ */
public static function add_variation() { public static function add_variation() {
@ -721,140 +612,23 @@ class WC_AJAX {
die( -1 ); die( -1 );
} }
global $post; global $post; // Set $post global so its available, like within the admin screens
$post_id = intval( $_POST['post_id'] );
$post = get_post( $post_id ); // Set $post global so its available like within the admin screens
$loop = intval( $_POST['loop'] );
$variation = array(
'post_title' => 'Product #' . $post_id . ' Variation',
'post_content' => '',
'post_status' => 'publish',
'post_author' => get_current_user_id(),
'post_parent' => $post_id,
'post_type' => 'product_variation',
'menu_order' => -1,
);
$variation_id = wp_insert_post( $variation );
do_action( 'woocommerce_create_product_variation', $variation_id );
if ( $variation_id ) {
$variation = get_post( $variation_id );
$variation_meta = get_post_meta( $variation_id );
$variation_data = array();
$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
$variation_fields = array(
'_sku' => '',
'_stock' => '',
'_regular_price' => '',
'_sale_price' => '',
'_weight' => '',
'_length' => '',
'_width' => '',
'_height' => '',
'_download_limit' => '',
'_download_expiry' => '',
'_downloadable_files' => '',
'_downloadable' => '',
'_virtual' => '',
'_thumbnail_id' => '',
'_sale_price_dates_from' => '',
'_sale_price_dates_to' => '',
'_manage_stock' => '',
'_stock_status' => '',
'_backorders' => null,
'_tax_class' => null,
'_variation_description' => '',
);
foreach ( $variation_fields as $field => $value ) {
$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
}
// Add the variation attributes
$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
// Formatting
$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
$variation_data['_sale_price'] = wc_format_localized_price( $variation_data['_sale_price'] );
$variation_data['_weight'] = wc_format_localized_decimal( $variation_data['_weight'] );
$variation_data['_length'] = wc_format_localized_decimal( $variation_data['_length'] );
$variation_data['_width'] = wc_format_localized_decimal( $variation_data['_width'] );
$variation_data['_height'] = wc_format_localized_decimal( $variation_data['_height'] );
$variation_data['_thumbnail_id'] = absint( $variation_data['_thumbnail_id'] );
$variation_data['image'] = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
$variation_data['menu_order'] = $variation->menu_order;
$variation_data['_stock'] = wc_stock_amount( $variation_data['_stock'] );
// Get tax classes
$tax_classes = WC_Tax::get_tax_classes();
$tax_class_options = array();
$tax_class_options[''] = __( 'Standard', 'woocommerce' );
if ( ! empty( $tax_classes ) ) {
foreach ( $tax_classes as $class ) {
$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
}
}
// Set backorder options
$backorder_options = array(
'no' => __( 'Do not allow', 'woocommerce' ),
'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
'yes' => __( 'Allow', 'woocommerce' ),
);
// set stock status options
$stock_status_options = array(
'instock' => __( 'In stock', 'woocommerce' ),
'outofstock' => __( 'Out of stock', 'woocommerce' ),
);
// Get attributes
$attributes = (array) maybe_unserialize( get_post_meta( $post_id, '_product_attributes', true ) );
$parent_data = array(
'id' => $post_id,
'attributes' => $attributes,
'tax_class_options' => $tax_class_options,
'sku' => get_post_meta( $post_id, '_sku', true ),
'weight' => wc_format_localized_decimal( get_post_meta( $post_id, '_weight', true ) ),
'length' => wc_format_localized_decimal( get_post_meta( $post_id, '_length', true ) ),
'width' => wc_format_localized_decimal( get_post_meta( $post_id, '_width', true ) ),
'height' => wc_format_localized_decimal( get_post_meta( $post_id, '_height', true ) ),
'tax_class' => get_post_meta( $post_id, '_tax_class', true ),
'backorder_options' => $backorder_options,
'stock_status_options' => $stock_status_options,
);
if ( ! $parent_data['weight'] ) {
$parent_data['weight'] = wc_format_localized_decimal( 0 );
}
if ( ! $parent_data['length'] ) {
$parent_data['length'] = wc_format_localized_decimal( 0 );
}
if ( ! $parent_data['width'] ) {
$parent_data['width'] = wc_format_localized_decimal( 0 );
}
if ( ! $parent_data['height'] ) {
$parent_data['height'] = wc_format_localized_decimal( 0 );
}
include( 'admin/meta-boxes/views/html-variation-admin.php' );
}
$product_id = intval( $_POST['post_id'] );
$post = get_post( $product_id );
$loop = intval( $_POST['loop'] );
$product_object = wc_get_product( $product_id );
$variation_object = new WC_Product_Variation();
$variation_object->set_parent_id( $product_id );
$variation_id = $variation_object->save();
$variation = get_post( $variation_id );
$variation_data = array_merge( array_map( 'maybe_unserialize', get_post_custom( $variation_id ) ), wc_get_product_variation_attributes( $variation_id ) ); // kept for BW compat.
include( 'admin/meta-boxes/views/html-variation-admin.php' );
die(); die();
} }
/** /**
* Link all variations via ajax function. * Link all variations via ajax function. @todo CRUD
*/ */
public static function link_all_variations() { public static function link_all_variations() {
@ -877,42 +651,36 @@ class WC_AJAX {
} }
$variations = array(); $variations = array();
$_product = wc_get_product( $post_id, array( 'product_type' => 'variable' ) ); $product = wc_get_product( $post_id, array( 'product_type' => 'variable' ) );
// Put variation attributes into an array // Put variation attributes into an array
foreach ( $_product->get_attributes() as $attribute ) { foreach ( $product->get_attributes() as $attribute ) {
if ( ! $attribute->get_variation() ) {
if ( ! $attribute['is_variation'] ) {
continue; continue;
} }
$attribute_field_name = 'attribute_' . sanitize_title( $attribute['name'] ); $attribute_field_name = 'attribute_' . sanitize_title( $attribute->get_name() );
if ( $attribute['is_taxonomy'] ) { if ( $attribute->is_taxonomy() ) {
$options = wc_get_product_terms( $post_id, $attribute['name'], array( 'fields' => 'slugs' ) ); $options = wc_get_product_terms( $post_id, $attribute->get_name(), array( 'fields' => 'slugs' ) );
} else { } else {
$options = explode( WC_DELIMITER, $attribute['value'] ); $options = $attribute->get_options();
} }
$options = array_map( 'trim', $options );
$variations[ $attribute_field_name ] = $options; $variations[ $attribute_field_name ] = $options;
} }
// Quit out if none were found // Quit out if none were found
if ( sizeof( $variations ) == 0 ) { if ( 0 === sizeof( $variations ) ) {
die(); die();
} }
// Get existing variations so we don't create duplicates // Get existing variations so we don't create duplicates
$available_variations = array(); $available_variations = array();
foreach ( $_product->get_children() as $child_id ) { foreach ( $product->get_children() as $child_id ) {
$child = $_product->get_child( $child_id ); $child = wc_get_product( $child_id );
$available_variations[] = $child->get_attributes();
if ( ! empty( $child->variation_id ) ) {
$available_variations[] = $child->get_variation_attributes();
}
} }
// Created posts will all have the following data // Created posts will all have the following data
@ -1014,7 +782,7 @@ class WC_AJAX {
foreach ( $product_ids as $product_id ) { foreach ( $product_ids as $product_id ) {
$product = wc_get_product( $product_id ); $product = wc_get_product( $product_id );
$files = $product->get_files(); $files = $product->get_downloads();
if ( ! $order->get_billing_email() ) { if ( ! $order->get_billing_email() ) {
die(); die();
@ -1030,8 +798,8 @@ class WC_AJAX {
$loop ++; $loop ++;
$file_counter ++; $file_counter ++;
if ( isset( $file['name'] ) ) { if ( $file->get_name() ) {
$file_count = $file['name']; $file_count = $file->get_name();
} else { } else {
$file_count = sprintf( __( 'File %d', 'woocommerce' ), $file_counter ); $file_count = sprintf( __( 'File %d', 'woocommerce' ), $file_counter );
} }
@ -1292,14 +1060,8 @@ class WC_AJAX {
if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) { if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
$stock_change = apply_filters( 'woocommerce_reduce_order_stock_quantity', $order_item_qty[ $item_id ], $item_id ); $stock_change = apply_filters( 'woocommerce_reduce_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
$new_stock = $_product->reduce_stock( $stock_change ); $new_stock = $_product->reduce_stock( $stock_change );
$item_name = $_product->get_sku() ? $_product->get_sku() : $_product->id; $item_name = $_product->get_sku() ? $_product->get_sku() : $_product->get_id();
$note = sprintf( __( 'Item %1$s stock reduced from %2$s to %3$s.', 'woocommerce' ), $item_name, $new_stock + $stock_change, $new_stock );
if ( ! empty( $_product->variation_id ) ) {
$note = sprintf( __( 'Item %1$s variation #%2$s stock reduced from %3$s to %4$s.', 'woocommerce' ), $item_name, $_product->variation_id, $new_stock + $stock_change, $new_stock );
} else {
$note = sprintf( __( 'Item %1$s stock reduced from %2$s to %3$s.', 'woocommerce' ), $item_name, $new_stock + $stock_change, $new_stock );
}
$return[] = $note; $return[] = $note;
$order->add_order_note( $note ); $order->add_order_note( $note );
} }
@ -1338,14 +1100,8 @@ class WC_AJAX {
$old_stock = $_product->get_stock_quantity(); $old_stock = $_product->get_stock_quantity();
$stock_change = apply_filters( 'woocommerce_restore_order_stock_quantity', $order_item_qty[ $item_id ], $item_id ); $stock_change = apply_filters( 'woocommerce_restore_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
$new_quantity = $_product->increase_stock( $stock_change ); $new_quantity = $_product->increase_stock( $stock_change );
$item_name = $_product->get_sku() ? $_product->get_sku() : $_product->id; $item_name = $_product->get_sku() ? $_product->get_sku() : $_product->get_id();
$note = sprintf( __( 'Item %1$s stock increased from %2$s to %3$s.', 'woocommerce' ), $item_name, $old_stock, $new_quantity );
if ( ! empty( $_product->variation_id ) ) {
$note = sprintf( __( 'Item %1$s variation #%2$s stock increased from %3$s to %4$s.', 'woocommerce' ), $item_name, $_product->variation_id, $old_stock, $new_quantity );
} else {
$note = sprintf( __( 'Item %1$s stock increased from %2$s to %3$s.', 'woocommerce' ), $item_name, $old_stock, $new_quantity );
}
$return[] = $note; $return[] = $note;
$order->add_order_note( $note ); $order->add_order_note( $note );
} }
@ -1577,7 +1333,7 @@ class WC_AJAX {
continue; continue;
} }
if ( ! $product || ( $product->is_type( 'variation' ) && empty( $product->parent ) ) ) { if ( ! $product || ( $product->is_type( 'variation' ) && ! $product->get_parent_id() ) ) {
continue; continue;
} }
@ -1599,68 +1355,6 @@ class WC_AJAX {
self::json_search_products( '', array( 'product', 'product_variation' ) ); self::json_search_products( '', array( 'product', 'product_variation' ) );
} }
/**
* Search for grouped products and return json.
*/
public static function json_search_grouped_products() {
ob_start();
check_ajax_referer( 'search-products', 'security' );
$term = (string) wc_clean( stripslashes( $_GET['term'] ) );
$exclude = array();
if ( empty( $term ) ) {
die();
}
if ( ! empty( $_GET['exclude'] ) ) {
$exclude = array_map( 'intval', explode( ',', $_GET['exclude'] ) );
}
$found_products = array();
if ( $grouped_term = get_term_by( 'slug', 'grouped', 'product_type' ) ) {
$posts_in = array_unique( (array) get_objects_in_term( $grouped_term->term_id, 'product_type' ) );
if ( sizeof( $posts_in ) > 0 ) {
$args = array(
'post_type' => 'product',
'post_status' => 'any',
'numberposts' => -1,
'orderby' => 'title',
'order' => 'asc',
'post_parent' => 0,
'suppress_filters' => 0,
'include' => $posts_in,
's' => $term,
'fields' => 'ids',
'exclude' => $exclude,
);
$posts = get_posts( $args );
if ( ! empty( $posts ) ) {
foreach ( $posts as $post ) {
$product = wc_get_product( $post );
if ( ! current_user_can( 'read_product', $post ) ) {
continue;
}
$found_products[ $post ] = rawurldecode( $product->get_formatted_name() );
}
}
}
}
$found_products = apply_filters( 'woocommerce_json_search_found_grouped_products', $found_products );
wp_send_json( $found_products );
}
/** /**
* Search for downloadable product variations and return json. * Search for downloadable product variations and return json.
* *
@ -1998,7 +1692,7 @@ class WC_AJAX {
$order->add_order_note( sprintf( __( 'Item #%s stock increased from %1$s to %2$s.', 'woocommerce' ), $order_item['product_id'], $old_stock, $new_quantity ) ); $order->add_order_note( sprintf( __( 'Item #%s stock increased from %1$s to %2$s.', 'woocommerce' ), $order_item['product_id'], $old_stock, $new_quantity ) );
do_action( 'woocommerce_restock_refunded_item', $_product->id, $old_stock, $new_quantity, $order, $_product ); do_action( 'woocommerce_restock_refunded_item', $_product->get_id(), $old_stock, $new_quantity, $order, $_product );
} }
} }
} }
@ -2175,146 +1869,38 @@ class WC_AJAX {
check_ajax_referer( 'load-variations', 'security' ); check_ajax_referer( 'load-variations', 'security' );
// Check permissions again and make sure we have what we need if ( ! current_user_can( 'edit_products' ) || empty( $_POST['product_id'] ) ) {
if ( ! current_user_can( 'edit_products' ) || empty( $_POST['product_id'] ) || empty( $_POST['attributes'] ) ) {
die( -1 ); die( -1 );
} }
// Set $post global so its available, like within the admin screens
global $post; global $post;
$product_id = absint( $_POST['product_id'] ); $loop = 0;
$post = get_post( $product_id ); // Set $post global so its available like within the admin screens $product_id = absint( $_POST['product_id'] );
$per_page = ! empty( $_POST['per_page'] ) ? absint( $_POST['per_page'] ) : 10; $post = get_post( $product_id );
$page = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1; $product_object = wc_get_product( $product_id );
$per_page = ! empty( $_POST['per_page'] ) ? absint( $_POST['per_page'] ) : 10;
// Get attributes $page = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
$attributes = array(); $variations = wc_get_products( array(
$posted_attributes = wp_unslash( $_POST['attributes'] ); 'status' => array( 'private', 'publish' ),
'type' => 'variation',
foreach ( $posted_attributes as $key => $value ) { 'parent' => $product_id,
$attributes[ $key ] = array_map( 'wc_clean', $value ); 'limit' => $per_page,
} 'page' => $page,
'orderby' => array(
// Get tax classes 'menu_order' => 'ASC',
$tax_classes = WC_Tax::get_tax_classes(); 'ID' => 'DESC',
$tax_class_options = array(); ),
$tax_class_options[''] = __( 'Standard', 'woocommerce' ); 'return' => 'objects',
) );
if ( ! empty( $tax_classes ) ) {
foreach ( $tax_classes as $class ) {
$tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
}
}
// Set backorder options
$backorder_options = array(
'no' => __( 'Do not allow', 'woocommerce' ),
'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
'yes' => __( 'Allow', 'woocommerce' ),
);
// set stock status options
$stock_status_options = array(
'instock' => __( 'In stock', 'woocommerce' ),
'outofstock' => __( 'Out of stock', 'woocommerce' ),
);
$parent_data = array(
'id' => $product_id,
'attributes' => $attributes,
'tax_class_options' => $tax_class_options,
'sku' => get_post_meta( $product_id, '_sku', true ),
'weight' => wc_format_localized_decimal( get_post_meta( $product_id, '_weight', true ) ),
'length' => wc_format_localized_decimal( get_post_meta( $product_id, '_length', true ) ),
'width' => wc_format_localized_decimal( get_post_meta( $product_id, '_width', true ) ),
'height' => wc_format_localized_decimal( get_post_meta( $product_id, '_height', true ) ),
'tax_class' => get_post_meta( $product_id, '_tax_class', true ),
'backorder_options' => $backorder_options,
'stock_status_options' => $stock_status_options,
);
if ( ! $parent_data['weight'] ) {
$parent_data['weight'] = wc_format_localized_decimal( 0 );
}
if ( ! $parent_data['length'] ) {
$parent_data['length'] = wc_format_localized_decimal( 0 );
}
if ( ! $parent_data['width'] ) {
$parent_data['width'] = wc_format_localized_decimal( 0 );
}
if ( ! $parent_data['height'] ) {
$parent_data['height'] = wc_format_localized_decimal( 0 );
}
// Get variations
$args = apply_filters( 'woocommerce_ajax_admin_get_variations_args', array(
'post_type' => 'product_variation',
'post_status' => array( 'private', 'publish' ),
'posts_per_page' => $per_page,
'paged' => $page,
'orderby' => array( 'menu_order' => 'ASC', 'ID' => 'DESC' ),
'post_parent' => $product_id,
), $product_id );
$variations = get_posts( $args );
$loop = 0;
if ( $variations ) { if ( $variations ) {
foreach ( $variations as $variation_object ) {
foreach ( $variations as $variation ) { $variation_id = $variation_object->get_id();
$variation_id = absint( $variation->ID ); $variation = get_post( $variation_id );
$variation_meta = get_post_meta( $variation_id ); $variation_data = array_merge( array_map( 'maybe_unserialize', get_post_custom( $variation_id ) ), wc_get_product_variation_attributes( $variation_id ) ); // kept for BW compat.
$variation_data = array();
$shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
$variation_fields = array(
'_sku' => '',
'_stock' => '',
'_regular_price' => '',
'_sale_price' => '',
'_weight' => '',
'_length' => '',
'_width' => '',
'_height' => '',
'_download_limit' => '',
'_download_expiry' => '',
'_downloadable_files' => '',
'_downloadable' => '',
'_virtual' => '',
'_thumbnail_id' => '',
'_sale_price_dates_from' => '',
'_sale_price_dates_to' => '',
'_manage_stock' => '',
'_stock_status' => '',
'_backorders' => null,
'_tax_class' => null,
'_variation_description' => '',
);
foreach ( $variation_fields as $field => $value ) {
$variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
}
// Add the variation attributes
$variation_data = array_merge( $variation_data, wc_get_product_variation_attributes( $variation_id ) );
// Formatting
$variation_data['_regular_price'] = wc_format_localized_price( $variation_data['_regular_price'] );
$variation_data['_sale_price'] = wc_format_localized_price( $variation_data['_sale_price'] );
$variation_data['_weight'] = wc_format_localized_decimal( $variation_data['_weight'] );
$variation_data['_length'] = wc_format_localized_decimal( $variation_data['_length'] );
$variation_data['_width'] = wc_format_localized_decimal( $variation_data['_width'] );
$variation_data['_height'] = wc_format_localized_decimal( $variation_data['_height'] );
$variation_data['_thumbnail_id'] = absint( $variation_data['_thumbnail_id'] );
$variation_data['image'] = $variation_data['_thumbnail_id'] ? wp_get_attachment_thumb_url( $variation_data['_thumbnail_id'] ) : '';
$variation_data['shipping_class'] = $shipping_classes && ! is_wp_error( $shipping_classes ) ? current( $shipping_classes )->term_id : '';
$variation_data['menu_order'] = $variation->menu_order;
$variation_data['_stock'] = '' === $variation_data['_stock'] ? '' : wc_stock_amount( $variation_data['_stock'] );
include( 'admin/meta-boxes/views/html-variation-admin.php' ); include( 'admin/meta-boxes/views/html-variation-admin.php' );
$loop++; $loop++;
} }
} }
@ -2335,26 +1921,12 @@ class WC_AJAX {
die( -1 ); die( -1 );
} }
// Remove previous meta box errors $product_id = absint( $_POST['product_id'] );
WC_Admin_Meta_Boxes::$meta_box_errors = array(); WC_Admin_Meta_Boxes::$meta_box_errors = array();
$product_id = absint( $_POST['product_id'] );
$product_type = empty( $_POST['product-type'] ) ? 'simple' : sanitize_title( stripslashes( $_POST['product-type'] ) );
$product_type_terms = wp_get_object_terms( $product_id, 'product_type' );
// If the product type hasn't been set or it has changed, update it before saving variations
if ( empty( $product_type_terms ) || sanitize_title( current( $product_type_terms )->name ) !== $product_type ) {
wp_set_object_terms( $product_id, $product_type, 'product_type' );
}
WC_Meta_Box_Product_Data::save_variations( $product_id, get_post( $product_id ) ); WC_Meta_Box_Product_Data::save_variations( $product_id, get_post( $product_id ) );
do_action( 'woocommerce_ajax_save_product_variations', $product_id ); do_action( 'woocommerce_ajax_save_product_variations', $product_id );
// Clear cache/transients
wc_delete_product_transients( $product_id );
if ( $errors = WC_Admin_Meta_Boxes::$meta_box_errors ) { if ( $errors = WC_Admin_Meta_Boxes::$meta_box_errors ) {
echo '<div class="error notice is-dismissible">'; echo '<div class="error notice is-dismissible">';
@ -2372,7 +1944,7 @@ class WC_AJAX {
} }
/** /**
* Bulk action - Toggle Enabled. * Bulk action - Toggle Enabled. @todo CRUD
* @access private * @access private
* @used-by bulk_edit_variations * @used-by bulk_edit_variations
* @param array $variations * @param array $variations
@ -2446,17 +2018,9 @@ class WC_AJAX {
} }
foreach ( $variations as $variation_id ) { foreach ( $variations as $variation_id ) {
// Price fields $variation = wc_get_product( $variation_id );
$regular_price = wc_clean( $data['value'] ); $variation->save_regular_price( wc_clean( $data['value'] ) );
$sale_price = get_post_meta( $variation_id, '_sale_price', true ); $variation->save();
// Date fields
$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
$date_to = get_post_meta( $variation_id, '_sale_price_dates_to', true );
$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
$date_to = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
_wc_save_product_price( $variation_id, $regular_price, $sale_price, $date_from, $date_to );
} }
} }
@ -2473,22 +2037,14 @@ class WC_AJAX {
} }
foreach ( $variations as $variation_id ) { foreach ( $variations as $variation_id ) {
// Price fields $variation = wc_get_product( $variation_id );
$regular_price = get_post_meta( $variation_id, '_regular_price', true ); $variation->save_sale_price( wc_clean( $data['value'] ) );
$sale_price = wc_clean( $data['value'] ); $variation->save();
// Date fields
$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
$date_to = get_post_meta( $variation_id, '_sale_price_dates_to', true );
$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
$date_to = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
_wc_save_product_price( $variation_id, $regular_price, $sale_price, $date_from, $date_to );
} }
} }
/** /**
* Bulk action - Set Stock. * Bulk action - Set Stock. @todo CRUDIFY ALL THESE ACTIONS
* @access private * @access private
* @used-by bulk_edit_variations * @used-by bulk_edit_variations
* @param array $variations * @param array $variations
@ -2604,27 +2160,17 @@ class WC_AJAX {
} }
foreach ( $variations as $variation_id ) { foreach ( $variations as $variation_id ) {
// Price fields $variation = wc_get_product( $variation_id );
$regular_price = get_post_meta( $variation_id, '_regular_price', true );
$sale_price = get_post_meta( $variation_id, '_sale_price', true );
// Date fields if ( 'false' !== $data['date_from'] ) {
$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true ); $variation->set_date_on_sale_from( wc_clean( $data['date_from'] ) );
$date_to = get_post_meta( $variation_id, '_sale_price_dates_to', true );
if ( 'false' === $data['date_from'] ) {
$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
} else {
$date_from = $data['date_from'];
} }
if ( 'false' === $data['date_to'] ) { if ( 'false' !== $data['date_to'] ) {
$date_to = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : ''; $variation->set_date_on_sale_from( wc_clean( $data['date_to'] ) );
} else {
$date_to = $data['date_to'];
} }
_wc_save_product_price( $variation_id, $regular_price, $sale_price, $date_from, $date_to ); $variation->save();
} }
} }
@ -2676,28 +2222,25 @@ class WC_AJAX {
* Bulk action - Set Price. * Bulk action - Set Price.
* @access private * @access private
* @used-by bulk_edit_variations * @used-by bulk_edit_variations
* @param array $variations * @param array $variations
* @param string $operator + or - * @param string $operator + or -
* @param string $field price being adjusted * @param string $field price being adjusted _regular_price or _sale_price
* @param string $value Price or Percent * @param string $value Price or Percent
*/ */
private static function variation_bulk_adjust_price( $variations, $field, $operator, $value ) { private static function variation_bulk_adjust_price( $variations, $field, $operator, $value ) {
foreach ( $variations as $variation_id ) { foreach ( $variations as $variation_id ) {
// Get existing data $variation = wc_get_product( $variation_id );
$_regular_price = get_post_meta( $variation_id, '_regular_price', true ); $field_value = $variation->{"get$field"}( 'edit' );
$_sale_price = get_post_meta( $variation_id, '_sale_price', true );
$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
$date_to = get_post_meta( $variation_id, '_sale_price_dates_to', true );
$date_from = ! empty( $date_from ) ? date( 'Y-m-d', $date_from ) : '';
$date_to = ! empty( $date_to ) ? date( 'Y-m-d', $date_to ) : '';
if ( '%' === substr( $value, -1 ) ) { if ( '%' === substr( $value, -1 ) ) {
$percent = wc_format_decimal( substr( $value, 0, -1 ) ); $percent = wc_format_decimal( substr( $value, 0, -1 ) );
$$field += ( ( $$field / 100 ) * $percent ) * "{$operator}1"; $field_value += ( ( $field_value / 100 ) * $percent ) * "{$operator}1";
} else { } else {
$$field += $value * "{$operator}1"; $field_value += $value * "{$operator}1";
} }
_wc_save_product_price( $variation_id, $_regular_price, $_sale_price, $date_from, $date_to );
$variation->{"set$field"}( $field_value );
$variation->save();
} }
} }

View File

@ -227,22 +227,22 @@ class WC_Cart {
update_meta_cache( 'post', wp_list_pluck( $cart, 'product_id' ) ); update_meta_cache( 'post', wp_list_pluck( $cart, 'product_id' ) );
foreach ( $cart as $key => $values ) { foreach ( $cart as $key => $values ) {
$_product = wc_get_product( $values['variation_id'] ? $values['variation_id'] : $values['product_id'] ); $product = wc_get_product( $values['variation_id'] ? $values['variation_id'] : $values['product_id'] );
if ( ! empty( $_product ) && $_product->exists() && $values['quantity'] > 0 ) { if ( ! empty( $product ) && $product->exists() && $values['quantity'] > 0 ) {
if ( ! $_product->is_purchasable() ) { if ( ! $product->is_purchasable() ) {
// Flag to indicate the stored cart should be update // Flag to indicate the stored cart should be update
$update_cart_session = true; $update_cart_session = true;
/* translators: %s: product name */ /* translators: %s: product name */
wc_add_notice( sprintf( __( '%s has been removed from your cart because it can no longer be purchased. Please contact us if you need assistance.', 'woocommerce' ), $_product->get_title() ), 'error' ); wc_add_notice( sprintf( __( '%s has been removed from your cart because it can no longer be purchased. Please contact us if you need assistance.', 'woocommerce' ), $product->get_name() ), 'error' );
do_action( 'woocommerce_remove_cart_item_from_session', $key, $values ); do_action( 'woocommerce_remove_cart_item_from_session', $key, $values );
} else { } else {
// Put session data into array. Run through filter so other plugins can load their own session data // Put session data into array. Run through filter so other plugins can load their own session data
$session_data = array_merge( $values, array( 'data' => $_product ) ); $session_data = array_merge( $values, array( 'data' => $product ) );
$this->cart_contents[ $key ] = apply_filters( 'woocommerce_get_cart_item_from_session', $session_data, $values, $key ); $this->cart_contents[ $key ] = apply_filters( 'woocommerce_get_cart_item_from_session', $session_data, $values, $key );
} }
@ -429,14 +429,8 @@ class WC_Cart {
$quantities = array(); $quantities = array();
foreach ( $this->get_cart() as $cart_item_key => $values ) { foreach ( $this->get_cart() as $cart_item_key => $values ) {
$_product = $values['data']; $product = $values['data'];
$quantities[ $product->get_stock_managed_by_id() ] = isset( $quantities[ $product->get_stock_managed_by_id() ] ) ? $quantities[ $product->get_stock_managed_by_id() ] + $values['quantity'] : $values['quantity'];
if ( $_product->is_type( 'variation' ) && true === $_product->managing_stock() ) {
// Variation has stock levels defined so its handled individually
$quantities[ $values['variation_id'] ] = isset( $quantities[ $values['variation_id'] ] ) ? $quantities[ $values['variation_id'] ] + $values['quantity'] : $values['quantity'];
} else {
$quantities[ $values['product_id'] ] = isset( $quantities[ $values['product_id'] ] ) ? $quantities[ $values['product_id'] ] + $values['quantity'] : $values['quantity'];
}
} }
return $quantities; return $quantities;
@ -451,9 +445,9 @@ class WC_Cart {
$return = true; $return = true;
foreach ( $this->get_cart() as $cart_item_key => $values ) { foreach ( $this->get_cart() as $cart_item_key => $values ) {
$_product = $values['data']; $product = $values['data'];
if ( ! $_product || ! $_product->exists() || 'trash' === $_product->post->post_status ) { if ( ! $product || ! $product->exists() || 'trash' === $product->get_status() ) {
$this->set_quantity( $cart_item_key, 0 ); $this->set_quantity( $cart_item_key, 0 );
$return = new WP_Error( 'invalid', __( 'An item which is no longer available was removed from your cart.', 'woocommerce' ) ); $return = new WP_Error( 'invalid', __( 'An item which is no longer available was removed from your cart.', 'woocommerce' ) );
} }
@ -475,36 +469,34 @@ class WC_Cart {
// First stock check loop // First stock check loop
foreach ( $this->get_cart() as $cart_item_key => $values ) { foreach ( $this->get_cart() as $cart_item_key => $values ) {
$_product = $values['data']; $product = $values['data'];
/** /**
* Check stock based on stock-status. * Check stock based on stock-status.
*/ */
if ( ! $_product->is_in_stock() ) { if ( ! $product->is_in_stock() ) {
/* translators: %s: product name */ /* translators: %s: product name */
$error->add( 'out-of-stock', sprintf( __( 'Sorry, "%s" is not in stock. Please edit your cart and try again. We apologise for any inconvenience caused.', 'woocommerce' ), $_product->get_title() ) ); $error->add( 'out-of-stock', sprintf( __( 'Sorry, "%s" is not in stock. Please edit your cart and try again. We apologise for any inconvenience caused.', 'woocommerce' ), $product->get_name() ) );
return $error; return $error;
} }
if ( ! $_product->managing_stock() ) { if ( ! $product->managing_stock() ) {
continue; continue;
} }
$check_qty = $_product->is_type( 'variation' ) && true === $_product->managing_stock() ? $product_qty_in_cart[ $values['variation_id'] ] : $product_qty_in_cart[ $values['product_id'] ];
/** /**
* Check stock based on all items in the cart. * Check stock based on all items in the cart.
*/ */
if ( ! $_product->has_enough_stock( $check_qty ) ) { if ( ! $product->has_enough_stock( $product_qty_in_cart[ $product->get_stock_managed_by_id() ] ) ) {
/* translators: 1: product name 2: quantity in stock */ /* translators: 1: product name 2: quantity in stock */
$error->add( 'out-of-stock', sprintf( __( 'Sorry, we do not have enough "%1$s" in stock to fulfill your order (%2$s in stock). Please edit your cart and try again. We apologise for any inconvenience caused.', 'woocommerce' ), $_product->get_title(), $_product->get_stock_quantity() ) ); $error->add( 'out-of-stock', sprintf( __( 'Sorry, we do not have enough "%1$s" in stock to fulfill your order (%2$s in stock). Please edit your cart and try again. We apologise for any inconvenience caused.', 'woocommerce' ), $product->get_name(), $product->get_stock_quantity() ) );
return $error; return $error;
} }
/** /**
* Finally consider any held stock, from pending orders. * Finally consider any held stock, from pending orders.
*/ */
if ( get_option( 'woocommerce_hold_stock_minutes' ) > 0 && ! $_product->backorders_allowed() ) { if ( get_option( 'woocommerce_hold_stock_minutes' ) > 0 && ! $product->backorders_allowed() ) {
$order_id = isset( WC()->session->order_awaiting_payment ) ? absint( WC()->session->order_awaiting_payment ) : 0; $order_id = isset( WC()->session->order_awaiting_payment ) ? absint( WC()->session->order_awaiting_payment ) : 0;
$held_stock = $wpdb->get_var( $held_stock = $wpdb->get_var(
$wpdb->prepare( " $wpdb->prepare( "
@ -518,22 +510,15 @@ class WC_Cart {
AND posts.post_type IN ( '" . implode( "','", wc_get_order_types() ) . "' ) AND posts.post_type IN ( '" . implode( "','", wc_get_order_types() ) . "' )
AND posts.post_status = 'wc-pending' AND posts.post_status = 'wc-pending'
AND posts.ID != %d;", AND posts.ID != %d;",
$_product->is_type( 'variation' ) && true === $_product->managing_stock() ? '_variation_id' : '_product_id', 'variation' === get_post_type( $product->get_stock_managed_by_id() ) ? '_variation_id' : '_product_id',
$_product->is_type( 'variation' ) && true === $_product->managing_stock() ? $values['variation_id'] : $values['product_id'], $product->get_stock_managed_by_id(),
$order_id $order_id
) )
); );
$not_enough_stock = false; if ( $product->get_stock_quantity() < ( $held_stock + $product_qty_in_cart[ $product->get_stock_managed_by_id() ] ) ) {
if ( $_product->is_type( 'variation' ) && 'parent' === $_product->managing_stock() && $_product->parent->get_stock_quantity() < ( $held_stock + $check_qty ) ) {
$not_enough_stock = true;
} elseif ( $_product->get_stock_quantity() < ( $held_stock + $check_qty ) ) {
$not_enough_stock = true;
}
if ( $not_enough_stock ) {
/* translators: 1: product name 2: minutes */ /* translators: 1: product name 2: minutes */
$error->add( 'out-of-stock', sprintf( __( 'Sorry, we do not have enough "%1$s" in stock to fulfill your order right now. Please try again in %2$d minutes or edit your cart and try again. We apologise for any inconvenience caused.', 'woocommerce' ), $_product->get_title(), get_option( 'woocommerce_hold_stock_minutes' ) ) ); $error->add( 'out-of-stock', sprintf( __( 'Sorry, we do not have enough "%1$s" in stock to fulfill your order right now. Please try again in %2$d minutes or edit your cart and try again. We apologise for any inconvenience caused.', 'woocommerce' ), $product->get_name(), get_option( 'woocommerce_hold_stock_minutes' ) ) );
return $error; return $error;
} }
} }
@ -553,12 +538,13 @@ class WC_Cart {
$item_data = array(); $item_data = array();
// Variation data // Variation data
if ( ! empty( $cart_item['data']->variation_id ) && is_array( $cart_item['variation'] ) ) { if ( $cart_item['data']->is_type( 'variation' ) && is_array( $cart_item['variation'] ) ) {
foreach ( $cart_item['variation'] as $name => $value ) { foreach ( $cart_item['variation'] as $name => $value ) {
if ( '' === $value ) if ( '' === $value ) {
continue; continue;
}
$taxonomy = wc_attribute_taxonomy_name( str_replace( 'attribute_pa_', '', urldecode( $name ) ) ); $taxonomy = wc_attribute_taxonomy_name( str_replace( 'attribute_pa_', '', urldecode( $name ) ) );
@ -631,7 +617,7 @@ class WC_Cart {
if ( ! $this->is_empty() ) { if ( ! $this->is_empty() ) {
foreach ( $this->get_cart() as $cart_item_key => $values ) { foreach ( $this->get_cart() as $cart_item_key => $values ) {
if ( $values['quantity'] > 0 ) { if ( $values['quantity'] > 0 ) {
$cross_sells = array_merge( $values['data']->get_cross_sells(), $cross_sells ); $cross_sells = array_merge( $values['data']->get_cross_sell_ids(), $cross_sells );
$in_cart[] = $values['product_id']; $in_cart[] = $values['product_id'];
} }
} }
@ -898,7 +884,7 @@ class WC_Cart {
$variation_id = absint( $variation_id ); $variation_id = absint( $variation_id );
// Ensure we don't add a variation to the cart directly by variation ID // Ensure we don't add a variation to the cart directly by variation ID
if ( 'product_variation' == get_post_type( $product_id ) ) { if ( 'product_variation' === get_post_type( $product_id ) ) {
$variation_id = $product_id; $variation_id = $product_id;
$product_id = wp_get_post_parent_id( $variation_id ); $product_id = wp_get_post_parent_id( $variation_id );
} }
@ -907,7 +893,7 @@ class WC_Cart {
$product_data = wc_get_product( $variation_id ? $variation_id : $product_id ); $product_data = wc_get_product( $variation_id ? $variation_id : $product_id );
// Sanity check // Sanity check
if ( $quantity <= 0 || ! $product_data || 'trash' === $product_data->post->post_status ) { if ( $quantity <= 0 || ! $product_data || 'trash' === $product_data->get_status() ) {
return false; return false;
} }
@ -927,7 +913,7 @@ class WC_Cart {
if ( $in_cart_quantity > 0 ) { if ( $in_cart_quantity > 0 ) {
/* translators: %s: product name */ /* translators: %s: product name */
throw new Exception( sprintf( '<a href="%s" class="button wc-forward">%s</a> %s', wc_get_cart_url(), __( 'View cart', 'woocommerce' ), sprintf( __( 'You cannot add another "%s" to your cart.', 'woocommerce' ), $product_data->get_title() ) ) ); throw new Exception( sprintf( '<a href="%s" class="button wc-forward">%s</a> %s', wc_get_cart_url(), __( 'View cart', 'woocommerce' ), sprintf( __( 'You cannot add another "%s" to your cart.', 'woocommerce' ), $product_data->get_name() ) ) );
} }
} }
@ -938,33 +924,24 @@ class WC_Cart {
// Stock check - only check if we're managing stock and backorders are not allowed // Stock check - only check if we're managing stock and backorders are not allowed
if ( ! $product_data->is_in_stock() ) { if ( ! $product_data->is_in_stock() ) {
throw new Exception( sprintf( __( 'You cannot add &quot;%s&quot; to the cart because the product is out of stock.', 'woocommerce' ), $product_data->get_title() ) ); throw new Exception( sprintf( __( 'You cannot add &quot;%s&quot; to the cart because the product is out of stock.', 'woocommerce' ), $product_data->get_name() ) );
} }
if ( ! $product_data->has_enough_stock( $quantity ) ) { if ( ! $product_data->has_enough_stock( $quantity ) ) {
/* translators: 1: product name 2: quantity in stock */ /* translators: 1: product name 2: quantity in stock */
throw new Exception( sprintf( __( 'You cannot add that amount of &quot;%1$s&quot; to the cart because there is not enough stock (%2$s remaining).', 'woocommerce' ), $product_data->get_title(), $product_data->get_stock_quantity() ) ); throw new Exception( sprintf( __( 'You cannot add that amount of &quot;%1$s&quot; to the cart because there is not enough stock (%2$s remaining).', 'woocommerce' ), $product_data->get_name(), $product_data->get_stock_quantity() ) );
} }
// Stock check - this time accounting for whats already in-cart // Stock check - this time accounting for whats already in-cart
if ( $managing_stock = $product_data->managing_stock() ) { if ( $product_data->managing_stock() ) {
$products_qty_in_cart = $this->get_cart_item_quantities(); $products_qty_in_cart = $this->get_cart_item_quantities();
if ( $product_data->is_type( 'variation' ) && true === $managing_stock ) { if ( ! $product_data->has_enough_stock( $products_qty_in_cart[ $product_data->get_stock_managed_by_id() ] + $quantity ) ) {
$check_qty = isset( $products_qty_in_cart[ $variation_id ] ) ? $products_qty_in_cart[ $variation_id ] : 0;
} else {
$check_qty = isset( $products_qty_in_cart[ $product_id ] ) ? $products_qty_in_cart[ $product_id ] : 0;
}
/**
* Check stock based on all items in the cart.
*/
if ( ! $product_data->has_enough_stock( $check_qty + $quantity ) ) {
throw new Exception( sprintf( throw new Exception( sprintf(
'<a href="%s" class="button wc-forward">%s</a> %s', '<a href="%s" class="button wc-forward">%s</a> %s',
wc_get_cart_url(), wc_get_cart_url(),
__( 'View cart', 'woocommerce' ), __( 'View Cart', 'woocommerce' ),
sprintf( __( 'You cannot add that amount to the cart &mdash; we have %1$s in stock and you already have %2$s in your cart.', 'woocommerce' ), $product_data->get_stock_quantity(), $check_qty ) sprintf( __( 'You cannot add that amount to the cart &mdash; we have %1$s in stock and you already have %2$s in your cart.', 'woocommerce' ), $product_data->get_stock_quantity(), $products_qty_in_cart[ $product_data->get_id() ] )
) ); ) );
} }
} }
@ -1136,15 +1113,15 @@ class WC_Cart {
* Calculate subtotals for items. This is done first so that discount logic can use the values. * Calculate subtotals for items. This is done first so that discount logic can use the values.
*/ */
foreach ( $cart as $cart_item_key => $values ) { foreach ( $cart as $cart_item_key => $values ) {
$_product = $values['data']; $product = $values['data'];
$line_price = $_product->get_price() * $values['quantity']; $line_price = $product->get_price() * $values['quantity'];
$line_subtotal = 0; $line_subtotal = 0;
$line_subtotal_tax = 0; $line_subtotal_tax = 0;
/** /**
* No tax to calculate. * No tax to calculate.
*/ */
if ( ! $_product->is_taxable() ) { if ( ! $product->is_taxable() ) {
// Subtotal is the undiscounted price // Subtotal is the undiscounted price
$this->subtotal += $line_price; $this->subtotal += $line_price;
@ -1165,17 +1142,17 @@ class WC_Cart {
} elseif ( $this->prices_include_tax ) { } elseif ( $this->prices_include_tax ) {
// Get base tax rates // Get base tax rates
if ( empty( $shop_tax_rates[ $_product->tax_class ] ) ) { if ( empty( $shop_tax_rates[ $product->get_tax_class( true ) ] ) ) {
$shop_tax_rates[ $_product->tax_class ] = WC_Tax::get_base_tax_rates( $_product->tax_class ); $shop_tax_rates[ $product->get_tax_class( true ) ] = WC_Tax::get_base_tax_rates( $product->get_tax_class( true ) );
} }
// Get item tax rates // Get item tax rates
if ( empty( $tax_rates[ $_product->get_tax_class() ] ) ) { if ( empty( $tax_rates[ $product->get_tax_class() ] ) ) {
$tax_rates[ $_product->get_tax_class() ] = WC_Tax::get_rates( $_product->get_tax_class() ); $tax_rates[ $product->get_tax_class() ] = WC_Tax::get_rates( $product->get_tax_class() );
} }
$base_tax_rates = $shop_tax_rates[ $_product->tax_class ]; $base_tax_rates = $shop_tax_rates[ $product->get_tax_class( true ) ];
$item_tax_rates = $tax_rates[ $_product->get_tax_class() ]; $item_tax_rates = $tax_rates[ $product->get_tax_class() ];
/** /**
* ADJUST TAX - Calculations when base tax is not equal to the item tax. * ADJUST TAX - Calculations when base tax is not equal to the item tax.
@ -1215,11 +1192,11 @@ class WC_Cart {
} else { } else {
// Get item tax rates // Get item tax rates
if ( empty( $tax_rates[ $_product->get_tax_class() ] ) ) { if ( empty( $tax_rates[ $product->get_tax_class() ] ) ) {
$tax_rates[ $_product->get_tax_class() ] = WC_Tax::get_rates( $_product->get_tax_class() ); $tax_rates[ $product->get_tax_class() ] = WC_Tax::get_rates( $product->get_tax_class() );
} }
$item_tax_rates = $tax_rates[ $_product->get_tax_class() ]; $item_tax_rates = $tax_rates[ $product->get_tax_class() ];
// Base tax for line before discount - we will store this in the order data // Base tax for line before discount - we will store this in the order data
$taxes = WC_Tax::calc_tax( $line_price, $item_tax_rates ); $taxes = WC_Tax::calc_tax( $line_price, $item_tax_rates );
@ -1241,11 +1218,11 @@ class WC_Cart {
*/ */
foreach ( $cart as $cart_item_key => $values ) { foreach ( $cart as $cart_item_key => $values ) {
$_product = $values['data']; $product = $values['data'];
// Prices // Prices
$base_price = $_product->get_price(); $base_price = $product->get_price();
$line_price = $_product->get_price() * $values['quantity']; $line_price = $product->get_price() * $values['quantity'];
// Tax data // Tax data
$taxes = array(); $taxes = array();
@ -1254,7 +1231,7 @@ class WC_Cart {
/** /**
* No tax to calculate. * No tax to calculate.
*/ */
if ( ! $_product->is_taxable() ) { if ( ! $product->is_taxable() ) {
// Discounted Price (price with any pre-tax discounts applied) // Discounted Price (price with any pre-tax discounts applied)
$discounted_price = $this->get_discounted_price( $values, $base_price, true ); $discounted_price = $this->get_discounted_price( $values, $base_price, true );
@ -1268,8 +1245,8 @@ class WC_Cart {
*/ */
} elseif ( $this->prices_include_tax ) { } elseif ( $this->prices_include_tax ) {
$base_tax_rates = $shop_tax_rates[ $_product->tax_class ]; $base_tax_rates = $shop_tax_rates[ $product->get_tax_class( true ) ];
$item_tax_rates = $tax_rates[ $_product->get_tax_class() ]; $item_tax_rates = $tax_rates[ $product->get_tax_class() ];
/** /**
* ADJUST TAX - Calculations when base tax is not equal to the item tax. * ADJUST TAX - Calculations when base tax is not equal to the item tax.
@ -1336,7 +1313,7 @@ class WC_Cart {
*/ */
} else { } else {
$item_tax_rates = $tax_rates[ $_product->get_tax_class() ]; $item_tax_rates = $tax_rates[ $product->get_tax_class() ];
// Work out a new base price without the shop's base tax // Work out a new base price without the shop's base tax
$taxes = WC_Tax::calc_tax( $line_price, $item_tax_rates ); $taxes = WC_Tax::calc_tax( $line_price, $item_tax_rates );
@ -1368,7 +1345,7 @@ class WC_Cart {
* *
* Tax exclusive prices are not affected. * Tax exclusive prices are not affected.
*/ */
if ( ! $_product->is_taxable() || $this->prices_include_tax ) { if ( ! $product->is_taxable() || $this->prices_include_tax ) {
$this->cart_contents[ $cart_item_key ]['line_total'] = round( $line_total + $line_tax - wc_round_tax_total( $line_tax ), $this->dp ); $this->cart_contents[ $cart_item_key ]['line_total'] = round( $line_total + $line_tax - wc_round_tax_total( $line_tax ), $this->dp );
$this->cart_contents[ $cart_item_key ]['line_subtotal'] = round( $line_subtotal + $line_subtotal_tax - wc_round_tax_total( $line_subtotal_tax ), $this->dp ); $this->cart_contents[ $cart_item_key ]['line_subtotal'] = round( $line_subtotal + $line_subtotal_tax - wc_round_tax_total( $line_subtotal_tax ), $this->dp );
$this->cart_contents[ $cart_item_key ]['line_tax'] = wc_round_tax_total( $line_tax ); $this->cart_contents[ $cart_item_key ]['line_tax'] = wc_round_tax_total( $line_tax );
@ -1532,8 +1509,8 @@ class WC_Cart {
if ( ! empty( $this->cart_contents ) ) { if ( ! empty( $this->cart_contents ) ) {
foreach ( $this->cart_contents as $cart_item_key => $values ) { foreach ( $this->cart_contents as $cart_item_key => $values ) {
$_product = $values['data']; $product = $values['data'];
if ( $_product->needs_shipping() ) { if ( $product->needs_shipping() ) {
$needs_shipping = true; $needs_shipping = true;
} }
} }
@ -2119,17 +2096,16 @@ class WC_Cart {
/** /**
* Get the product row price per item. * Get the product row price per item.
* *
* @param WC_Product $_product * @param WC_Product $product
* @return string formatted price * @return string formatted price
*/ */
public function get_product_price( $_product ) { public function get_product_price( $product ) {
if ( 'excl' === $this->tax_display_cart ) { if ( 'excl' === $this->tax_display_cart ) {
$product_price = $_product->get_price_excluding_tax(); $product_price = wc_get_price_excluding_tax( $product );
} else { } else {
$product_price = $_product->get_price_including_tax(); $product_price = wc_get_price_including_tax( $product );
} }
return apply_filters( 'woocommerce_cart_product_price', wc_price( $product_price ), $product );
return apply_filters( 'woocommerce_cart_product_price', wc_price( $product_price ), $_product );
} }
/** /**
@ -2139,21 +2115,20 @@ class WC_Cart {
* *
* When on the checkout (review order), this will get the subtotal based on the customer's tax rate rather than the base rate. * When on the checkout (review order), this will get the subtotal based on the customer's tax rate rather than the base rate.
* *
* @param WC_Product $_product * @param WC_Product $product
* @param int $quantity * @param int $quantity
* @return string formatted price * @return string formatted price
*/ */
public function get_product_subtotal( $_product, $quantity ) { public function get_product_subtotal( $product, $quantity ) {
$price = $product->get_price();
$price = $_product->get_price(); $taxable = $product->is_taxable();
$taxable = $_product->is_taxable();
// Taxable // Taxable
if ( $taxable ) { if ( $taxable ) {
if ( 'excl' === $this->tax_display_cart ) { if ( 'excl' === $this->tax_display_cart ) {
$row_price = $_product->get_price_excluding_tax( $quantity ); $row_price = wc_get_price_excluding_tax( $product, array( 'qty' => $quantity ) );
$product_subtotal = wc_price( $row_price ); $product_subtotal = wc_price( $row_price );
if ( $this->prices_include_tax && $this->tax_total > 0 ) { if ( $this->prices_include_tax && $this->tax_total > 0 ) {
@ -2161,7 +2136,7 @@ class WC_Cart {
} }
} else { } else {
$row_price = $_product->get_price_including_tax( $quantity ); $row_price = wc_get_price_including_tax( $product, array( 'qty' => $quantity ) );
$product_subtotal = wc_price( $row_price ); $product_subtotal = wc_price( $row_price );
if ( ! $this->prices_include_tax && $this->tax_total > 0 ) { if ( ! $this->prices_include_tax && $this->tax_total > 0 ) {
@ -2177,7 +2152,7 @@ class WC_Cart {
} }
return apply_filters( 'woocommerce_cart_product_subtotal', $product_subtotal, $_product, $quantity, $this ); return apply_filters( 'woocommerce_cart_product_subtotal', $product_subtotal, $product, $quantity, $this );
} }
/** /**

View File

@ -232,10 +232,10 @@ class WC_Checkout {
$product = $values['data']; $product = $values['data'];
$item = new WC_Order_Item_Product( array( $item = new WC_Order_Item_Product( array(
'quantity' => $values['quantity'], 'quantity' => $values['quantity'],
'name' => $product ? $product->get_title() : '', 'name' => $product ? $product->get_name() : '',
'tax_class' => $product ? $product->get_tax_class() : '', 'tax_class' => $product ? $product->get_tax_class() : '',
'product_id' => $product && isset( $product->id ) ? $product->id : 0, 'product_id' => $product ? ( $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id() ) : 0,
'variation_id' => $product && isset( $product->variation_id ) ? $product->variation_id : 0, 'variation_id' => $product && $product->is_type( 'variation' ) ? $product->get_id() : 0,
'variation' => $values['variation'], 'variation' => $values['variation'],
'subtotal' => $values['line_subtotal'], 'subtotal' => $values['line_subtotal'],
'total' => $values['line_total'], 'total' => $values['line_total'],
@ -245,7 +245,7 @@ class WC_Checkout {
) ); ) );
$item->set_backorder_meta(); $item->set_backorder_meta();
// Set this to pass to legacy actions @todo remove in future release // Set this to pass to legacy actions.
$item->legacy_values = $values; $item->legacy_values = $values;
$item->legacy_cart_item_key = $cart_item_key; $item->legacy_cart_item_key = $cart_item_key;
@ -264,7 +264,7 @@ class WC_Checkout {
), ),
) ); ) );
// Set this to pass to legacy actions @todo remove in future release // Set this to pass to legacy actions.
$item->legacy_fee = $fee; $item->legacy_fee = $fee;
$item->legacy_fee_key = $fee_key; $item->legacy_fee_key = $fee_key;
@ -283,7 +283,7 @@ class WC_Checkout {
'meta_data' => $shipping_rate->get_meta_data(), 'meta_data' => $shipping_rate->get_meta_data(),
) ); ) );
// Set this to pass to legacy actions @todo remove in future release // Set this to pass to legacy actions.
$item->legacy_package_key = $package_key; $item->legacy_package_key = $package_key;
$order->add_item( $item ); $order->add_item( $item );

View File

@ -238,10 +238,10 @@ class WC_Comments {
public static function clear_transients( $post_id ) { public static function clear_transients( $post_id ) {
if ( 'product' === get_post_type( $post_id ) ) { if ( 'product' === get_post_type( $post_id ) ) {
delete_post_meta( $post_id, '_wc_average_rating' ); $product = wc_get_product( $post_id );
delete_post_meta( $post_id, '_wc_rating_count' ); self::get_rating_counts_for_product( $product );
delete_post_meta( $post_id, '_wc_review_count' ); self::get_average_rating_for_product( $product );
WC_Product::sync_average_rating( $post_id ); self::get_review_count_for_product( $product );
} }
} }
@ -328,6 +328,98 @@ class WC_Comments {
} }
return $verified; return $verified;
} }
/**
* Get product rating for a product. Please note this is not cached.
*
* @since 2.7.0
* @param WC_Product $product
* @return float
*/
public static function get_average_rating_for_product( &$product ) {
global $wpdb;
$count = $product->get_rating_count();
if ( $count ) {
$ratings = $wpdb->get_var( $wpdb->prepare("
SELECT SUM(meta_value) FROM $wpdb->commentmeta
LEFT JOIN $wpdb->comments ON $wpdb->commentmeta.comment_id = $wpdb->comments.comment_ID
WHERE meta_key = 'rating'
AND comment_post_ID = %d
AND comment_approved = '1'
AND meta_value > 0
", $product->get_id() ) );
$average = number_format( $ratings / $count, 2, '.', '' );
} else {
$average = 0;
}
$product->set_average_rating( $average );
$data_store = $product->get_data_store();
$data_store->update_average_rating( $product );
return $average;
}
/**
* Get product review count for a product (not replies). Please note this is not cached.
*
* @since 2.7.0
* @param WC_Product $product
* @return int
*/
public static function get_review_count_for_product( &$product ) {
global $wpdb;
$count = $wpdb->get_var( $wpdb->prepare("
SELECT COUNT(*) FROM $wpdb->comments
WHERE comment_parent = 0
AND comment_post_ID = %d
AND comment_approved = '1'
", $product->get_id() ) );
$product->set_review_count( $count );
$data_store = $product->get_data_store();
$data_store->update_review_count( $product );
return $count;
}
/**
* Get product rating count for a product. Please note this is not cached.
*
* @since 2.7.0
* @param WC_Product $product
* @return array of integers
*/
public static function get_rating_counts_for_product( &$product ) {
global $wpdb;
$counts = array();
$raw_counts = $wpdb->get_results( $wpdb->prepare( "
SELECT meta_value, COUNT( * ) as meta_value_count FROM $wpdb->commentmeta
LEFT JOIN $wpdb->comments ON $wpdb->commentmeta.comment_id = $wpdb->comments.comment_ID
WHERE meta_key = 'rating'
AND comment_post_ID = %d
AND comment_approved = '1'
AND meta_value > 0
GROUP BY meta_value
", $product->get_id() ) );
foreach ( $raw_counts as $count ) {
$counts[ $count->meta_value ] = absint( $count->meta_value_count );
}
$product->set_rating_counts( $counts );
$data_store = $product->get_data_store();
$data_store->update_rating_counts( $product );
return $counts;
}
} }
WC_Comments::init(); WC_Comments::init();

View File

@ -627,7 +627,6 @@ class WC_Countries {
/** /**
* Get country locale settings. * Get country locale settings.
* @return array * @return array
* @todo [2.4] Check select2 4.0.0 compatibility with `placeholder` attribute and uncomment relevant lines. https://github.com/woocommerce/woocommerce/issues/7729
*/ */
public function get_country_locale() { public function get_country_locale() {
if ( empty( $this->locale ) ) { if ( empty( $this->locale ) ) {

View File

@ -391,9 +391,9 @@ class WC_Coupon extends WC_Legacy_Coupon {
* Uses price inc tax if prices include tax to work around https://github.com/woocommerce/woocommerce/issues/7669 and https://github.com/woocommerce/woocommerce/issues/8074. * Uses price inc tax if prices include tax to work around https://github.com/woocommerce/woocommerce/issues/7669 and https://github.com/woocommerce/woocommerce/issues/8074.
*/ */
if ( wc_prices_include_tax() ) { if ( wc_prices_include_tax() ) {
$discount_percent = ( $cart_item['data']->get_price_including_tax() * $cart_item_qty ) / WC()->cart->subtotal; $discount_percent = ( wc_get_price_including_tax( $cart_item['data'] ) * $cart_item_qty ) / WC()->cart->subtotal;
} else { } else {
$discount_percent = ( $cart_item['data']->get_price_excluding_tax() * $cart_item_qty ) / WC()->cart->subtotal_ex_tax; $discount_percent = ( wc_get_price_excluding_tax( $cart_item['data'] ) * $cart_item_qty ) / WC()->cart->subtotal_ex_tax;
} }
$discount = ( $this->get_amount() * $discount_percent ) / $cart_item_qty; $discount = ( $this->get_amount() * $discount_percent ) / $cart_item_qty;
@ -845,7 +845,7 @@ class WC_Coupon extends WC_Legacy_Coupon {
$valid_for_cart = false; $valid_for_cart = false;
if ( ! WC()->cart->is_empty() ) { if ( ! WC()->cart->is_empty() ) {
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) { foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
if ( in_array( $cart_item['product_id'], $this->get_product_ids() ) || in_array( $cart_item['variation_id'], $this->get_product_ids() ) || in_array( $cart_item['data']->get_parent(), $this->get_product_ids() ) ) { if ( in_array( $cart_item['product_id'], $this->get_product_ids() ) || in_array( $cart_item['variation_id'], $this->get_product_ids() ) || in_array( $cart_item['data']->get_parent_id(), $this->get_product_ids() ) ) {
$valid_for_cart = true; $valid_for_cart = true;
} }
} }
@ -949,7 +949,7 @@ class WC_Coupon extends WC_Legacy_Coupon {
$valid_for_cart = true; $valid_for_cart = true;
if ( ! WC()->cart->is_empty() ) { if ( ! WC()->cart->is_empty() ) {
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) { foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
if ( in_array( $cart_item['product_id'], $this->get_excluded_product_ids() ) || in_array( $cart_item['variation_id'], $this->get_excluded_product_ids() ) || in_array( $cart_item['data']->get_parent(), $this->get_excluded_product_ids() ) ) { if ( in_array( $cart_item['product_id'], $this->get_excluded_product_ids() ) || in_array( $cart_item['variation_id'], $this->get_excluded_product_ids() ) || in_array( $cart_item['data']->get_parent_id(), $this->get_excluded_product_ids() ) ) {
$valid_for_cart = false; $valid_for_cart = false;
} }
} }
@ -1060,8 +1060,8 @@ class WC_Coupon extends WC_Legacy_Coupon {
} }
$valid = false; $valid = false;
$product_cats = wc_get_product_cat_ids( $product->id ); $product_cats = wc_get_product_cat_ids( $product->get_id() );
$product_ids = array( $product->id, ( isset( $product->variation_id ) ? $product->variation_id : 0 ), $product->get_parent() ); $product_ids = array( $product->get_id(), $product->get_parent_id() );
// Specific products get the discount // Specific products get the discount
if ( sizeof( $this->get_product_ids() ) && sizeof( array_intersect( $product_ids, $this->get_product_ids() ) ) ) { if ( sizeof( $this->get_product_ids() ) && sizeof( array_intersect( $product_ids, $this->get_product_ids() ) ) ) {
@ -1092,11 +1092,7 @@ class WC_Coupon extends WC_Legacy_Coupon {
if ( $this->get_exclude_sale_items() ) { if ( $this->get_exclude_sale_items() ) {
$product_ids_on_sale = wc_get_product_ids_on_sale(); $product_ids_on_sale = wc_get_product_ids_on_sale();
if ( isset( $product->variation_id ) ) { if ( in_array( $product->get_id(), $product_ids_on_sale, true ) ) {
if ( in_array( $product->variation_id, $product_ids_on_sale, true ) ) {
$valid = false;
}
} elseif ( in_array( $product->id, $product_ids_on_sale, true ) ) {
$valid = false; $valid = false;
} }
} }
@ -1197,7 +1193,7 @@ class WC_Coupon extends WC_Legacy_Coupon {
$products = array(); $products = array();
if ( ! WC()->cart->is_empty() ) { if ( ! WC()->cart->is_empty() ) {
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) { foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
if ( in_array( $cart_item['product_id'], $this->get_excluded_product_ids() ) || in_array( $cart_item['variation_id'], $this->get_excluded_product_ids() ) || in_array( $cart_item['data']->get_parent(), $this->get_excluded_product_ids() ) ) { if ( in_array( $cart_item['product_id'], $this->get_excluded_product_ids() ) || in_array( $cart_item['variation_id'], $this->get_excluded_product_ids() ) || in_array( $cart_item['data']->get_parent_id(), $this->get_excluded_product_ids() ) ) {
$products[] = $cart_item['data']->get_title(); $products[] = $cart_item['data']->get_title();
} }
} }

View File

@ -28,11 +28,16 @@ class WC_Data_Store {
* Ran through `woocommerce_data_stores`. * Ran through `woocommerce_data_stores`.
*/ */
private $stores = array( private $stores = array(
'customer' => 'WC_Customer_Data_Store', 'coupon' => 'WC_Coupon_Data_Store_CPT',
'customer-session' => 'WC_Customer_Data_Store_Session', 'payment-token' => 'WC_Payment_Token_Data_Store_Table',
'coupon' => 'WC_Coupon_Data_Store_CPT', 'product' => 'WC_Product_Data_Store_CPT',
'payment-token' => 'WC_Payment_Token_Data_Store_Table', 'product_grouped' => 'WC_Product_Grouped_Data_Store_CPT',
'product_variable' => 'WC_Product_Variable_Data_Store_CPT',
'product_variation' => 'WC_Product_Variation_Data_Store_CPT',
'customer' => 'WC_Customer_Data_Store',
'customer-session' => 'WC_Customer_Data_Store_Session',
); );
/** /**
* Contains the name of the current data store's class name. * Contains the name of the current data store's class name.
*/ */

View File

@ -399,7 +399,7 @@ class WC_Emails {
$message = sprintf( $message = sprintf(
__( '%1$s is low in stock. There are %2$d left.', 'woocommerce' ), __( '%1$s is low in stock. There are %2$d left.', 'woocommerce' ),
html_entity_decode( strip_tags( $product->get_formatted_name() ), ENT_QUOTES, get_bloginfo( 'charset' ) ), html_entity_decode( strip_tags( $product->get_formatted_name() ), ENT_QUOTES, get_bloginfo( 'charset' ) ),
html_entity_decode( strip_tags( $product->get_total_stock() ) ) html_entity_decode( strip_tags( $product->get_stock_quantity() ) )
); );
wp_mail( wp_mail(

View File

@ -485,7 +485,7 @@ class WC_Form_Handler {
$product = wc_get_product( $cart_item['product_id'] ); $product = wc_get_product( $cart_item['product_id'] );
$item_removed_title = apply_filters( 'woocommerce_cart_item_removed_title', $product ? sprintf( _x( '&ldquo;%s&rdquo;', 'Item name in quotes', 'woocommerce' ), $product->get_title() ) : __( 'Item', 'woocommerce' ), $cart_item ); $item_removed_title = apply_filters( 'woocommerce_cart_item_removed_title', $product ? sprintf( _x( '&ldquo;%s&rdquo;', 'Item name in quotes', 'woocommerce' ), $product->get_name() ) : __( 'Item', 'woocommerce' ), $cart_item );
// Don't show undo link if removed item is out of stock. // Don't show undo link if removed item is out of stock.
if ( $product->is_in_stock() && $product->has_enough_stock( $cart_item['quantity'] ) ) { if ( $product->is_in_stock() && $product->has_enough_stock( $cart_item['quantity'] ) ) {
@ -542,7 +542,7 @@ class WC_Form_Handler {
// is_sold_individually // is_sold_individually
if ( $_product->is_sold_individually() && $quantity > 1 ) { if ( $_product->is_sold_individually() && $quantity > 1 ) {
wc_add_notice( sprintf( __( 'You can only have 1 %s in your cart.', 'woocommerce' ), $_product->get_title() ), 'error' ); wc_add_notice( sprintf( __( 'You can only have 1 %s in your cart.', 'woocommerce' ), $_product->get_name() ), 'error' );
$passed_validation = false; $passed_validation = false;
} }
@ -695,7 +695,7 @@ class WC_Form_Handler {
return; return;
} }
$add_to_cart_handler = apply_filters( 'woocommerce_add_to_cart_handler', $adding_to_cart->product_type, $adding_to_cart ); $add_to_cart_handler = apply_filters( 'woocommerce_add_to_cart_handler', $adding_to_cart->get_type(), $adding_to_cart );
// Variable product handling // Variable product handling
if ( 'variable' === $add_to_cart_handler ) { if ( 'variable' === $add_to_cart_handler ) {
@ -801,16 +801,18 @@ class WC_Form_Handler {
// If no variation ID is set, attempt to get a variation ID from posted attributes. // If no variation ID is set, attempt to get a variation ID from posted attributes.
if ( empty( $variation_id ) ) { if ( empty( $variation_id ) ) {
$variation_id = $adding_to_cart->get_matching_variation( wp_unslash( $_POST ) ); $data_store = WC_Data_Store::load( 'product' );
$variation_id = $data_store->find_matching_product_variation( $adding_to_cart, wp_unslash( $_POST ) );
} }
$variation = wc_get_product( $variation_id );
// Validate the attributes. // Validate the attributes.
try { try {
if ( empty( $variation_id ) ) { if ( empty( $variation_id ) ) {
throw new Exception( __( 'Please choose product options&hellip;', 'woocommerce' ) ); throw new Exception( __( 'Please choose product options&hellip;', 'woocommerce' ) );
} }
$variation_data = wc_get_product_variation_attributes( $variation_id );
foreach ( $attributes as $attribute ) { foreach ( $attributes as $attribute ) {
if ( ! $attribute['is_variation'] ) { if ( ! $attribute['is_variation'] ) {
continue; continue;
@ -828,7 +830,7 @@ class WC_Form_Handler {
} }
// Get valid value from variation // Get valid value from variation
$valid_value = isset( $variation->variation_data[ $taxonomy ] ) ? $variation->variation_data[ $taxonomy ] : ''; $valid_value = isset( $variation_data[ $taxonomy ] ) ? $variation_data[ $taxonomy ] : '';
// Allow if valid or show error. // Allow if valid or show error.
if ( '' === $valid_value || $valid_value === $value ) { if ( '' === $valid_value || $valid_value === $value ) {

View File

@ -74,6 +74,7 @@ class WC_Install {
), ),
'2.7.0' => array( '2.7.0' => array(
'wc_update_270_webhooks', 'wc_update_270_webhooks',
'wc_update_270_grouped_products',
), ),
); );

View File

@ -221,7 +221,7 @@ class WC_Order_Item_Product extends WC_Order_Item {
*/ */
public function set_backorder_meta() { public function set_backorder_meta() {
if ( $this->get_product()->backorders_require_notification() && $this->get_product()->is_on_backorder( $this->get_quantity() ) ) { if ( $this->get_product()->backorders_require_notification() && $this->get_product()->is_on_backorder( $this->get_quantity() ) ) {
$this->add_meta_data( apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $this->get_quantity() - max( 0, $this->get_product()->get_total_stock() ), true ); $this->add_meta_data( apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $this->get_quantity() - max( 0, $this->get_product()->get_stock_quantity() ), true );
} }
} }
@ -364,11 +364,15 @@ class WC_Order_Item_Product extends WC_Order_Item {
if ( ! is_a( $product, 'WC_Product' ) ) { if ( ! is_a( $product, 'WC_Product' ) ) {
$this->error( 'order_item_product_invalid_product', __( 'Invalid product', 'woocommerce' ) ); $this->error( 'order_item_product_invalid_product', __( 'Invalid product', 'woocommerce' ) );
} }
$this->set_product_id( $product->get_id() ); if ( $product->is_type( 'variation' ) ) {
$this->set_name( $product->get_title() ); $this->set_product_id( $product->get_parent_id() );
$this->set_variation_id( $product->get_id() );
$this->set_variation( is_callable( array( $product, 'get_variation_attributes' ) ) ? $product->get_variation_attributes() : array() );
} else {
$this->set_product_id( $product->get_id() );
}
$this->set_name( $product->get_name() );
$this->set_tax_class( $product->get_tax_class() ); $this->set_tax_class( $product->get_tax_class() );
$this->set_variation_id( is_callable( array( $product, 'get_variation_id' ) ) ? $product->get_variation_id() : 0 );
$this->set_variation( is_callable( array( $product, 'get_variation_attributes' ) ) ? $product->get_variation_attributes() : array() );
} }
/* /*

View File

@ -28,6 +28,7 @@ class WC_Post_Data {
* Hook in methods. * Hook in methods.
*/ */
public static function init() { public static function init() {
add_action( 'woocommerce_deferred_product_sync', array( __CLASS__, 'deferred_product_sync' ), 10, 1 );
add_action( 'set_object_terms', array( __CLASS__, 'set_object_terms' ), 10, 6 ); add_action( 'set_object_terms', array( __CLASS__, 'set_object_terms' ), 10, 6 );
add_action( 'transition_post_status', array( __CLASS__, 'transition_post_status' ), 10, 3 ); add_action( 'transition_post_status', array( __CLASS__, 'transition_post_status' ), 10, 3 );
@ -39,8 +40,25 @@ class WC_Post_Data {
add_filter( 'update_order_item_metadata', array( __CLASS__, 'update_order_item_metadata' ), 10, 5 ); add_filter( 'update_order_item_metadata', array( __CLASS__, 'update_order_item_metadata' ), 10, 5 );
add_filter( 'update_post_metadata', array( __CLASS__, 'update_post_metadata' ), 10, 5 ); add_filter( 'update_post_metadata', array( __CLASS__, 'update_post_metadata' ), 10, 5 );
add_filter( 'wp_insert_post_data', array( __CLASS__, 'wp_insert_post_data' ) ); add_filter( 'wp_insert_post_data', array( __CLASS__, 'wp_insert_post_data' ) );
add_action( 'pre_post_update', array( __CLASS__, 'pre_post_update' ) );
add_action( 'update_post_meta', array( __CLASS__, 'sync_product_stock_status' ), 10, 4 ); // Status transitions
add_action( 'delete_post', array( __CLASS__, 'delete_post' ) );
add_action( 'wp_trash_post', array( __CLASS__, 'trash_post' ) );
add_action( 'untrash_post', array( __CLASS__, 'untrash_post' ) );
add_action( 'before_delete_post', array( __CLASS__, 'delete_order_items' ) );
add_action( 'before_delete_post', array( __CLASS__, 'delete_order_downloadable_permissions' ) );
}
/**
* Sync a product.
* @param int $product_id
*/
public static function deferred_product_sync( $product_id ) {
$product = wc_get_product( $product_id );
if ( is_callable( array( $product, 'sync' ) ) ) {
$product->sync( $product );
}
} }
/** /**
@ -171,14 +189,10 @@ class WC_Post_Data {
* @param int $meta_id * @param int $meta_id
* @param int $object_id * @param int $object_id
* @param string $meta_key * @param string $meta_key
* @param mixed $_meta_value * @param mixed $meta_value
* @deprecated
*/ */
public static function sync_product_stock_status( $meta_id, $object_id, $meta_key, $_meta_value ) { public static function sync_product_stock_status( $meta_id, $object_id, $meta_key, $meta_value ) {}
if ( '_stock' === $meta_key && 'product_variation' === get_post_type( $object_id ) ) {
$product = wc_get_product( $object_id );
$product->check_stock_status();
}
}
/** /**
* Forces the order posts to have a title in a certain format (containing the date). * Forces the order posts to have a title in a certain format (containing the date).
@ -208,22 +222,176 @@ class WC_Post_Data {
} }
/** /**
* Some functions, like the term recount, require the visibility to be set prior. Lets save that here. * Removes variations etc belonging to a deleted post, and clears transients.
* *
* @param int $post_id * @param mixed $id ID of post being deleted
*/ */
public static function pre_post_update( $post_id ) { public static function delete_post( $id ) {
if ( 'product' === get_post_type( $post_id ) ) { global $woocommerce, $wpdb;
$product_type = empty( $_POST['product-type'] ) ? 'simple' : sanitize_title( stripslashes( $_POST['product-type'] ) );
if ( isset( $_POST['_visibility'] ) ) { if ( ! current_user_can( 'delete_posts' ) ) {
if ( update_post_meta( $post_id, '_visibility', wc_clean( $_POST['_visibility'] ) ) ) { return;
do_action( 'woocommerce_product_set_visibility', $post_id, wc_clean( $_POST['_visibility'] ) ); }
if ( $id > 0 ) {
$post_type = get_post_type( $id );
switch ( $post_type ) {
case 'product' :
$child_product_variations = get_children( 'post_parent=' . $id . '&post_type=product_variation' );
if ( ! empty( $child_product_variations ) ) {
foreach ( $child_product_variations as $child ) {
wp_delete_post( $child->ID, true );
}
}
$child_products = get_children( 'post_parent=' . $id . '&post_type=product' );
if ( ! empty( $child_products ) ) {
foreach ( $child_products as $child ) {
$child_post = array();
$child_post['ID'] = $child->ID;
$child_post['post_parent'] = 0;
wp_update_post( $child_post );
}
}
if ( $parent_id = wp_get_post_parent_id( $id ) ) {
wc_delete_product_transients( $parent_id );
}
break;
case 'product_variation' :
wc_delete_product_transients( wp_get_post_parent_id( $id ) );
break;
case 'shop_order' :
$refunds = $wpdb->get_results( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type = 'shop_order_refund' AND post_parent = %d", $id ) );
if ( ! is_null( $refunds ) ) {
foreach ( $refunds as $refund ) {
wp_delete_post( $refund->ID, true );
}
}
break;
}
}
}
/**
* woocommerce_trash_post function.
*
* @param mixed $id
*/
public static function trash_post( $id ) {
global $wpdb;
if ( $id > 0 ) {
$post_type = get_post_type( $id );
if ( in_array( $post_type, wc_get_order_types( 'order-count' ) ) ) {
// Delete count - meta doesn't work on trashed posts
$user_id = get_post_meta( $id, '_customer_user', true );
if ( $user_id > 0 ) {
delete_user_meta( $user_id, '_money_spent' );
delete_user_meta( $user_id, '_order_count' );
}
$refunds = $wpdb->get_results( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type = 'shop_order_refund' AND post_parent = %d", $id ) );
foreach ( $refunds as $refund ) {
$wpdb->update( $wpdb->posts, array( 'post_status' => 'trash' ), array( 'ID' => $refund->ID ) );
}
delete_transient( 'woocommerce_processing_order_count' );
wc_delete_shop_order_transients( $id );
}
}
}
/**
* woocommerce_untrash_post function.
*
* @param mixed $id
*/
public static function untrash_post( $id ) {
global $wpdb;
if ( $id > 0 ) {
$post_type = get_post_type( $id );
if ( in_array( $post_type, wc_get_order_types( 'order-count' ) ) ) {
// Delete count - meta doesn't work on trashed posts
$user_id = get_post_meta( $id, '_customer_user', true );
if ( $user_id > 0 ) {
delete_user_meta( $user_id, '_money_spent' );
delete_user_meta( $user_id, '_order_count' );
}
$refunds = $wpdb->get_results( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type = 'shop_order_refund' AND post_parent = %d", $id ) );
foreach ( $refunds as $refund ) {
$wpdb->update( $wpdb->posts, array( 'post_status' => 'wc-completed' ), array( 'ID' => $refund->ID ) );
}
delete_transient( 'woocommerce_processing_order_count' );
wc_delete_shop_order_transients( $id );
} elseif ( 'product' === $post_type ) {
// Check if SKU is valid before untrash the product.
$sku = get_post_meta( $id, '_sku', true );
if ( ! empty( $sku ) ) {
if ( ! wc_product_has_unique_sku( $id, $sku ) ) {
update_post_meta( $id, '_sku', '' );
}
} }
} }
if ( isset( $_POST['_stock_status'] ) && 'variable' !== $product_type ) { }
wc_update_product_stock_status( $post_id, wc_clean( $_POST['_stock_status'] ) ); }
}
/**
* Remove item meta on permanent deletion.
*/
public static function delete_order_items( $postid ) {
global $wpdb;
if ( in_array( get_post_type( $postid ), wc_get_order_types() ) ) {
do_action( 'woocommerce_delete_order_items', $postid );
$wpdb->query( "
DELETE {$wpdb->prefix}woocommerce_order_items, {$wpdb->prefix}woocommerce_order_itemmeta
FROM {$wpdb->prefix}woocommerce_order_items
JOIN {$wpdb->prefix}woocommerce_order_itemmeta ON {$wpdb->prefix}woocommerce_order_items.order_item_id = {$wpdb->prefix}woocommerce_order_itemmeta.order_item_id
WHERE {$wpdb->prefix}woocommerce_order_items.order_id = '{$postid}';
" );
do_action( 'woocommerce_deleted_order_items', $postid );
}
}
/**
* Remove downloadable permissions on permanent order deletion.
*/
public static function delete_order_downloadable_permissions( $postid ) {
global $wpdb;
if ( in_array( get_post_type( $postid ), wc_get_order_types() ) ) {
do_action( 'woocommerce_delete_order_downloadable_permissions', $postid );
$wpdb->query( $wpdb->prepare( "
DELETE FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions
WHERE order_id = %d
", $postid ) );
do_action( 'woocommerce_deleted_order_downloadable_permissions', $postid );
} }
} }
} }

View File

@ -0,0 +1,273 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Represents a product attribute.
*
* Attributes can be global (taxonomy based) or local to the product itself.
* Uses ArrayAccess to be BW compatible with previous ways of reading attributes.
*
* @version 2.7.0
* @since 2.7.0
* @package WooCommerce/Classes
* @author WooThemes
*/
class WC_Product_Attribute implements ArrayAccess {
/**
* Data array.
* @since 2.7.0
* @var array
*/
protected $data = array(
'id' => 0,
'name' => '',
'options' => '',
'position' => 0,
'visible' => false,
'variation' => false,
);
/**
* Return if this attribute is a taxonomy.
* @return boolean
*/
public function is_taxonomy() {
return 0 < $this->get_id();
}
/**
* Get taxonomy name if applicable.
* @return string
*/
public function get_taxonomy() {
return $this->is_taxonomy() ? $this->get_name() : '';
}
/**
* Get taxonomy object.
* @return array|null
*/
public function get_taxonomy_object() {
global $wc_product_attributes;
return $this->is_taxonomy() ? $wc_product_attributes[ $this->get_name() ] : null;
}
/**
* Gets terms from the stored options.
* @return array|null
*/
public function get_terms() {
if ( ! $this->is_taxonomy() || ! taxonomy_exists( $this->get_name() ) ) {
return null;
}
foreach ( $this->get_options() as $option ) {
if ( is_int( $option ) ) {
$term = get_term_by( 'id', $option, $this->get_name() );
} else {
$term = get_term_by( 'name', $option, $this->get_name() );
}
if ( $term && ! is_wp_error( $term ) ) {
$terms[] = $term;
}
}
return $terms;
}
/**
* Returns all data for this object.
* @return array
*/
public function get_data() {
return array_merge( $this->data, array(
'is_visible' => $this->get_visible() ? 1 : 0,
'is_variation' => $this->get_variation() ? 1 : 0,
'is_taxonomy' => $this->is_taxonomy() ? 1 : 0,
'value' => $this->is_taxonomy() ? '' : wc_implode_text_attributes( $this->get_options() ),
) );
}
/*
|--------------------------------------------------------------------------
| Setters
|--------------------------------------------------------------------------
*/
/**
* Set ID (this is the attribute ID).
* @param int $value
*/
public function set_id( $value ) {
$this->data['id'] = absint( $value );
}
/**
* Set name (this is the attribute name or taxonomy).
* @param int $value
*/
public function set_name( $value ) {
$this->data['name'] = $value;
}
/**
* Set options.
* @param array $value
*/
public function set_options( $value ) {
$this->data['options'] = $value;
}
/**
* Set position.
* @param int $value
*/
public function set_position( $value ) {
$this->data['position'] = absint( $value );
}
/**
* Set if visible.
* @param bool $value
*/
public function set_visible( $value ) {
$this->data['visible'] = wc_string_to_bool( $value );
}
/**
* Set if variation.
* @param bool $value
*/
public function set_variation( $value ) {
$this->data['variation'] = wc_string_to_bool( $value );
}
/*
|--------------------------------------------------------------------------
| Getters
|--------------------------------------------------------------------------
*/
/**
* Get the ID.
* @return int
*/
public function get_id() {
return $this->data['id'];
}
/**
* Get name.
* @return int
*/
public function get_name() {
return $this->data['name'];
}
/**
* Get options.
* @return array
*/
public function get_options() {
return $this->data['options'];
}
/**
* Get position.
* @return int
*/
public function get_position() {
return $this->data['position'];
}
/**
* Get if visible.
* @return bool
*/
public function get_visible() {
return $this->data['visible'];
}
/**
* Get if variation.
* @return bool
*/
public function get_variation() {
return $this->data['variation'];
}
/*
|--------------------------------------------------------------------------
| ArrayAccess/Backwards compatibility.
|--------------------------------------------------------------------------
*/
/**
* offsetGet
* @param string $offset
* @return mixed
*/
public function offsetGet( $offset ) {
switch ( $offset ) {
case 'is_variation' :
return $this->get_variation() ? 1 : 0;
break;
case 'is_visible' :
return $this->get_visible() ? 1 : 0;
break;
case 'is_taxonomy' :
return $this->is_taxonomy() ? 1 : 0;
break;
case 'value' :
return $this->is_taxonomy() ? '' : wc_implode_text_attributes( $this->get_options() );
break;
default :
if ( is_callable( array( $this, "get_$offset" ) ) ) {
return $this->{"get_$offset"}();
}
break;
}
return '';
}
/**
* offsetSet
* @param string $offset
* @param mixed $value
*/
public function offsetSet( $offset, $value ) {
switch ( $offset ) {
case 'is_variation' :
$this->set_variation( $value );
break;
case 'is_visible' :
$this->set_visible( $value );
break;
case 'value' :
$this->set_options( $value );
break;
default :
if ( is_callable( array( $this, "set_$offset" ) ) ) {
return $this->{"set_$offset"}( $value );
}
break;
}
}
/**
* offsetUnset
* @param string $offset
*/
public function offsetUnset( $offset ) {}
/**
* offsetExists
* @param string $offset
* @return bool
*/
public function offsetExists( $offset ) {
return in_array( $offset, array_merge( array( 'is_variation', 'is_visible', 'is_taxonomy', 'value' ), array_keys( $this->data ) ) );
}
}

View File

@ -0,0 +1,221 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Represents a file which can be downloaded.
*
* @version 2.7.0
* @since 2.7.0
* @package WooCommerce/Classes
* @author WooThemes
*/
class WC_Product_Download implements ArrayAccess {
/**
* Data array.
* @since 2.7.0
* @var array
*/
protected $data = array(
'id' => '',
'name' => '',
'file' => '',
);
/**
* Returns all data for this object.
* @return array
*/
public function get_data() {
return $this->data;
}
/**
* Get allowed mime types.
* @return array
*/
public function get_allowed_mime_types() {
return apply_filters( 'woocommerce_downloadable_file_allowed_mime_types', get_allowed_mime_types() );
}
/**
* Get type of file path set.
* @param string $file_path optional.
* @return string absolute, relative, or shortcode.
*/
public function get_type_of_file_path( $file_path = '' ) {
$file_path = $file_path ? $file_path : $this->get_file();
if ( 0 === strpos( $file_path, 'http' ) ) {
return 'absolute';
} elseif ( '[' === substr( $file_path, 0, 1 ) && ']' === substr( $file_path, -1 ) ) {
return 'shortcode';
} else {
return 'relative';
}
}
/**
* Get file type.
* @return string
*/
public function get_file_type() {
$type = wp_check_filetype( strtok( $this->get_file(), '?' ), $this->get_allowed_mime_types() );
return $type['type'];
}
/**
* Get file extension.
* @return string
*/
public function get_file_extension() {
$parsed_url = parse_url( $this->get_file(), PHP_URL_PATH );
return pathinfo( $parsed_url, PATHINFO_EXTENSION );
}
/**
* Check if file is allowed.
* @return boolean
*/
public function is_allowed_filetype() {
if ( 'shortcode' === $this->get_type_of_file_path() ) {
return true;
}
return empty( $this->get_file_extension() ) || in_array( $this->get_file_type(), $this->get_allowed_mime_types() );
}
/**
* Validate file exists.
* @return boolean
*/
public function file_exists() {
if ( 'relative' !== $this->get_type_of_file_path() ) {
return true;
}
$file_url = $this->get_file();
if ( '..' === substr( $file_url, 0, 2 ) || '/' !== substr( $file_url, 0, 1 ) ) {
$file_url = realpath( ABSPATH . $file_url );
}
return apply_filters( 'woocommerce_downloadable_file_exists', file_exists( $file_url ), $this->get_file() );
}
/*
|--------------------------------------------------------------------------
| Setters
|--------------------------------------------------------------------------
*/
/**
* Set ID.
* @param string $value
*/
public function set_id( $value ) {
$this->data['id'] = wc_clean( $value );
}
/**
* Set name.
* @param string $value
*/
public function set_name( $value ) {
$this->data['name'] = wc_clean( $value );
}
/**
* Set file.
* @param string $value
*/
public function set_file( $value ) {
switch ( $this->get_type_of_file_path( $value ) ) {
case 'absolute' :
$this->data['file'] = esc_url_raw( $value );
break;
default:
$this->data['file'] = wc_clean( $value );
break;
}
}
/*
|--------------------------------------------------------------------------
| Getters
|--------------------------------------------------------------------------
*/
/**
* Get id.
* @return string
*/
public function get_id() {
return $this->data['id'];
}
/**
* Get name.
* @return string
*/
public function get_name() {
return $this->data['name'];
}
/**
* Get file.
* @return string
*/
public function get_file() {
return $this->data['file'];
}
/*
|--------------------------------------------------------------------------
| ArrayAccess/Backwards compatibility.
|--------------------------------------------------------------------------
*/
/**
* offsetGet
* @param string $offset
* @return mixed
*/
public function offsetGet( $offset ) {
switch ( $offset ) {
default :
if ( is_callable( array( $this, "get_$offset" ) ) ) {
return $this->{"get_$offset"}();
}
break;
}
return '';
}
/**
* offsetSet
* @param string $offset
* @param mixed $value
*/
public function offsetSet( $offset, $value ) {
switch ( $offset ) {
default :
if ( is_callable( array( $this, "set_$offset" ) ) ) {
return $this->{"set_$offset"}( $value );
}
break;
}
}
/**
* offsetUnset
* @param string $offset
*/
public function offsetUnset( $offset ) {}
/**
* offsetExists
* @param string $offset
* @return bool
*/
public function offsetExists( $offset ) {
return in_array( $offset, array_keys( $this->data ) );
}
}

View File

@ -1,7 +1,6 @@
<?php <?php
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly exit;
} }
/** /**
@ -10,7 +9,7 @@ if ( ! defined( 'ABSPATH' ) ) {
* External products cannot be bought; they link offsite. Extends simple products. * External products cannot be bought; they link offsite. Extends simple products.
* *
* @class WC_Product_External * @class WC_Product_External
* @version 2.0.0 * @version 2.7.0
* @package WooCommerce/Classes/Products * @package WooCommerce/Classes/Products
* @category Class * @category Class
* @author WooThemes * @author WooThemes
@ -18,16 +17,138 @@ if ( ! defined( 'ABSPATH' ) ) {
class WC_Product_External extends WC_Product { class WC_Product_External extends WC_Product {
/** /**
* Constructor. * Stores product data.
* *
* @access public * @var array
* @param mixed $product
*/ */
public function __construct( $product ) { protected $extra_data = array(
$this->product_type = 'external'; 'product_url' => '',
'button_text' => '',
);
/**
* Merges external product data into the parent object.
* @param int|WC_Product|object $product Product to init.
*/
public function __construct( $product = 0 ) {
$this->data = array_merge( $this->data, $this->extra_data );
parent::__construct( $product ); parent::__construct( $product );
} }
/**
* Get internal type.
* @return string
*/
public function get_type() {
return 'external';
}
/*
|--------------------------------------------------------------------------
| Getters
|--------------------------------------------------------------------------
|
| Methods for getting data from the product object.
*/
/**
* Get product url.
*
* @param string $context
* @return string
*/
public function get_product_url( $context = 'view' ) {
return esc_url( $this->get_prop( 'product_url', $context ) );
}
/**
* Get button text.
*
* @param string $context
* @return string
*/
public function get_button_text( $context = 'view' ) {
return $this->get_prop( 'button_text', $context );
}
/*
|--------------------------------------------------------------------------
| Setters
|--------------------------------------------------------------------------
|
| Functions for setting product data. These should not update anything in the
| database itself and should only change what is stored in the class
| object.
*/
/**
* Set product URL.
*
* @since 2.7.0
* @param string $product_url Product URL.
*/
public function set_product_url( $product_url ) {
$this->set_prop( 'product_url', $product_url );
}
/**
* Set button text.
*
* @since 2.7.0
* @param string $button_text Button text.
*/
public function set_button_text( $button_text ) {
$this->set_prop( 'button_text', $button_text );
}
/**
* External products cannot be stock managed.
*
* @since 2.7.0
* @param bool
*/
public function set_manage_stock( $manage_stock ) {
$this->set_prop( 'manage_stock', false );
if ( true === $manage_stock ) {
$this->error( 'product_external_invalid_manage_stock', __( 'External products cannot be stock managed.', 'woocommerce' ) );
}
}
/**
* External products cannot be stock managed.
*
* @since 2.7.0
* @param bool
*/
public function set_stock_status( $stock_status = '' ) {
$this->set_prop( 'stock_status', 'instock' );
if ( 'instock' !== $stock_status ) {
$this->error( 'product_external_invalid_stock_status', __( 'External products cannot be stock managed.', 'woocommerce' ) );
}
}
/**
* xternal products cannot be backordered.
*
* @since 2.7.0
* @param string $backorders Options: 'yes', 'no' or 'notify'.
*/
public function set_backorders( $backorders ) {
$this->set_prop( 'backorders', 'no' );
if ( 'no' !== $backorders ) {
$this->error( 'product_external_invalid_backorders', __( 'External products cannot be backordered.', 'woocommerce' ) );
}
}
/*
|--------------------------------------------------------------------------
| Other Actions
|--------------------------------------------------------------------------
*/
/** /**
* Returns false if the product cannot be bought. * Returns false if the product cannot be bought.
* *
@ -68,23 +189,34 @@ class WC_Product_External extends WC_Product {
return apply_filters( 'woocommerce_product_single_add_to_cart_text', $this->get_button_text(), $this ); return apply_filters( 'woocommerce_product_single_add_to_cart_text', $this->get_button_text(), $this );
} }
/*
|--------------------------------------------------------------------------
| CRUD methods
|--------------------------------------------------------------------------
*/
/** /**
* Get product url. * Read post data.
* *
* @access public * @since 2.7.0
* @return string
*/ */
public function get_product_url() { protected function read_product_data() {
return esc_url( $this->product_url ); parent::read_product_data();
$this->set_props( array(
'product_url' => get_post_meta( $this->get_id(), '_product_url', true ),
'button_text' => get_post_meta( $this->get_id(), '_button_text', true ) ? get_post_meta( $this->get_id(), '_button_text', true ) : __( 'Buy product', 'woocommerce' ),
) );
} }
/** /**
* Get button text. * Helper method that updates all the post meta for an external product.
* *
* @access public * @since 2.7.0
* @return string
*/ */
public function get_button_text() { protected function update_post_meta() {
return $this->button_text ? $this->button_text : __( 'Buy product', 'woocommerce' ); parent::update_post_meta();
update_post_meta( $this->get_id(), '_product_url', $this->get_product_url() );
update_post_meta( $this->get_id(), '_button_text', $this->get_button_text() );
} }
} }

View File

@ -10,7 +10,7 @@ if ( ! defined( 'ABSPATH' ) ) {
* The WooCommerce product factory creating the right product object. * The WooCommerce product factory creating the right product object.
* *
* @class WC_Product_Factory * @class WC_Product_Factory
* @version 2.3.0 * @version 2.7.0
* @package WooCommerce/Classes * @package WooCommerce/Classes
* @category Class * @category Class
* @author WooThemes * @author WooThemes
@ -20,33 +20,57 @@ class WC_Product_Factory {
/** /**
* Get a product. * Get a product.
* *
* @param bool $the_product (default: false) * @param mixed $product_id (default: false)
* @param array $args (default: array()) * @param array $deprecated
* @return WC_Product|bool false if the product cannot be loaded * @return WC_Product|null Product object or null if the product cannot be loaded.
*/ */
public function get_product( $the_product = false, $args = array() ) { public function get_product( $product_id = false, $deprecated = array() ) {
try { $product_id = $this->get_product_id( $product_id );
$the_product = $this->get_product_object( $the_product ); if ( ! $product_id ) {
if ( ! $the_product ) {
throw new Exception( 'Product object does not exist', 422 );
}
$classname = $this->get_product_class( $the_product, $args );
if ( ! $classname ) {
throw new Exception( 'Missing classname', 422 );
}
if ( ! class_exists( $classname ) ) {
$classname = 'WC_Product_Simple';
}
return new $classname( $the_product, $args );
} catch ( Exception $e ) {
return false; return false;
} }
$product_type = $this->get_product_type( $product_id );
$classname = $this->get_classname_from_product_type( $product_type );
// backwards compat filter
$post_type = 'variation' === $product_type ? 'product_variation' : 'product';
$classname = apply_filters( 'woocommerce_product_class', $classname, $product_type, $post_type, $product_id );
if ( ! $classname ) {
return null;
}
if ( ! class_exists( $classname ) ) {
$classname = 'WC_Product_Simple';
}
try {
return new $classname( $product_id );
} catch ( Exception $e ) {
return null;
}
}
/**
* Get the product type for a product.
*
* @since 2.7.0
* @param int $product_id
* @return string|false
*/
public static function get_product_type( $product_id ) {
// Allow the overriding of the lookup in this function. Return the product type here.
$override = apply_filters( 'woocommerce_product_type_query', false, $product_id );
if ( ! $override ) {
if ( 'product_variation' === get_post_type( $product_id ) ) {
return 'variation';
}
$terms = get_the_terms( $product_id, 'product_type' );
return ! empty( $terms ) ? sanitize_title( current( $terms )->name ) : 'simple';
} else {
return $override;
}
} }
/** /**
@ -54,56 +78,26 @@ class WC_Product_Factory {
* @param string $product_type * @param string $product_type
* @return string|false * @return string|false
*/ */
private function get_classname_from_product_type( $product_type ) { public static function get_classname_from_product_type( $product_type ) {
return $product_type ? 'WC_Product_' . implode( '_', array_map( 'ucfirst', explode( '-', $product_type ) ) ) : false; return $product_type ? 'WC_Product_' . implode( '_', array_map( 'ucfirst', explode( '-', $product_type ) ) ) : false;
} }
/** /**
* Get the product class name. * Get the product ID depending on what was passed.
* @param WP_Post $the_product *
* @param array $args (default: array()) * @since 2.7.0
* @return string * @param mixed $product
* @return int|bool false on failure
*/ */
private function get_product_class( $the_product, $args = array() ) { private function get_product_id( $product ) {
$product_id = absint( $the_product->ID ); if ( is_numeric( $product ) ) {
$post_type = $the_product->post_type; return $product;
} elseif ( $product instanceof WC_Product ) {
if ( 'product' === $post_type ) { return $product->get_id();
if ( isset( $args['product_type'] ) ) { } elseif ( ! empty( $product->ID ) ) {
$product_type = $args['product_type']; return $product->ID;
} else {
$terms = get_the_terms( $the_product, 'product_type' );
$product_type = ! empty( $terms ) ? sanitize_title( current( $terms )->name ) : 'simple';
}
} elseif ( 'product_variation' === $post_type ) {
$product_type = 'variation';
} else { } else {
$product_type = false; return false;
} }
$classname = $this->get_classname_from_product_type( $product_type );
// Filter classname so that the class can be overridden if extended.
return apply_filters( 'woocommerce_product_class', $classname, $product_type, $post_type, $product_id );
}
/**
* Get the product object.
* @param mixed $the_product
* @uses WP_Post
* @return WP_Post|bool false on failure
*/
private function get_product_object( $the_product ) {
if ( false === $the_product ) {
$the_product = $GLOBALS['post'];
} elseif ( is_numeric( $the_product ) ) {
$the_product = get_post( $the_product );
} elseif ( $the_product instanceof WC_Product ) {
$the_product = get_post( $the_product->id );
} elseif ( ! ( $the_product instanceof WP_Post ) ) {
$the_product = false;
}
return apply_filters( 'woocommerce_product_object', $the_product );
} }
} }

View File

@ -1,7 +1,6 @@
<?php <?php
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly exit;
} }
/** /**
@ -10,27 +9,39 @@ if ( ! defined( 'ABSPATH' ) ) {
* Grouped products cannot be purchased - they are wrappers for other products. * Grouped products cannot be purchased - they are wrappers for other products.
* *
* @class WC_Product_Grouped * @class WC_Product_Grouped
* @version 2.3.0 * @version 2.7.0
* @package WooCommerce/Classes/Products * @package WooCommerce/Classes/Products
* @category Class * @category Class
* @author WooThemes * @author WooThemes
*/ */
class WC_Product_Grouped extends WC_Product { class WC_Product_Grouped extends WC_Product {
/** @public array Array of child products/posts/variations. */ /**
public $children; * Stores product data.
*
* @var array
*/
protected $extra_data = array(
'children' => array(),
);
/** /**
* Constructor. * Merges grouped product data into the parent object.
* * @param int|WC_Product|object $product Product to init.
* @access public
* @param mixed $product
*/ */
public function __construct( $product ) { public function __construct( $product = 0 ) {
$this->product_type = 'grouped'; $this->data = array_merge( $this->data, $this->extra_data );
parent::__construct( $product ); parent::__construct( $product );
} }
/**
* Get internal type.
* @return string
*/
public function get_type() {
return 'grouped';
}
/** /**
* Get the add to cart button text. * Get the add to cart button text.
* *
@ -41,79 +52,20 @@ class WC_Product_Grouped extends WC_Product {
return apply_filters( 'woocommerce_product_add_to_cart_text', __( 'View products', 'woocommerce' ), $this ); return apply_filters( 'woocommerce_product_add_to_cart_text', __( 'View products', 'woocommerce' ), $this );
} }
/**
* Return the products children posts.
*
* @access public
* @return array
*/
public function get_children() {
if ( ! is_array( $this->children ) || empty( $this->children ) ) {
$transient_name = 'wc_product_children_' . $this->id;
$this->children = array_filter( array_map( 'absint', (array) get_transient( $transient_name ) ) );
if ( empty( $this->children ) ) {
$args = apply_filters( 'woocommerce_grouped_children_args', array(
'post_parent' => $this->id,
'post_type' => 'product',
'orderby' => 'menu_order',
'order' => 'ASC',
'fields' => 'ids',
'post_status' => 'publish',
'numberposts' => -1,
) );
$this->children = get_posts( $args );
set_transient( $transient_name, $this->children, DAY_IN_SECONDS * 30 );
}
}
return (array) $this->children;
}
/**
* Returns whether or not the product has any child product.
*
* @access public
* @return bool
*/
public function has_child() {
return sizeof( $this->get_children() ) ? true : false;
}
/** /**
* Returns whether or not the product is on sale. * Returns whether or not the product is on sale.
* *
* @access public
* @return bool * @return bool
*/ */
public function is_on_sale() { public function is_on_sale() {
$is_on_sale = false; global $wpdb;
$on_sale = $this->get_children() && 1 === $wpdb->get_var( "SELECT 1 FROM $wpdb->postmeta WHERE meta_key = '_sale_price' AND meta_value > 0 AND post_id IN (" . implode( ',', array_map( 'esc_sql', $this->get_children() ) ) . ");" );
if ( $this->has_child() ) { return apply_filters( 'woocommerce_product_is_on_sale', $on_sale, $this );
foreach ( $this->get_children() as $child_id ) {
$sale_price = get_post_meta( $child_id, '_sale_price', true );
if ( '' !== $sale_price && $sale_price >= 0 ) {
$is_on_sale = true;
}
}
} else {
if ( $this->sale_price && $this->sale_price == $this->price ) {
$is_on_sale = true;
}
}
return apply_filters( 'woocommerce_product_is_on_sale', $is_on_sale, $this );
} }
/** /**
* Returns false if the product cannot be bought. * Returns false if the product cannot be bought.
* *
* @access public
* @return bool * @return bool
*/ */
public function is_purchasable() { public function is_purchasable() {
@ -132,9 +84,9 @@ class WC_Product_Grouped extends WC_Product {
$child_prices = array(); $child_prices = array();
foreach ( $this->get_children() as $child_id ) { foreach ( $this->get_children() as $child_id ) {
$child = wc_get_product( $child_id ); $child = wc_get_product( $child_id );
if ( '' !== $child->get_price() ) { if ( '' !== $child->get_price() ) {
$child_prices[] = 'incl' === $tax_display_mode ? $child->get_price_including_tax() : $child->get_price_excluding_tax(); $child_prices[] = 'incl' === $tax_display_mode ? wc_get_price_including_tax( $child ) : wc_get_price_excluding_tax( $child );
} }
} }
@ -153,7 +105,7 @@ class WC_Product_Grouped extends WC_Product {
if ( $is_free ) { if ( $is_free ) {
$price = apply_filters( 'woocommerce_grouped_free_price_html', __( 'Free!', 'woocommerce' ), $this ); $price = apply_filters( 'woocommerce_grouped_free_price_html', __( 'Free!', 'woocommerce' ), $this );
} else { } else {
$price = apply_filters( 'woocommerce_grouped_price_html', $price . $this->get_price_suffix(), $this, $child_prices ); $price = apply_filters( 'woocommerce_grouped_price_html', $price . wc_get_price_suffix( $this ), $this, $child_prices );
} }
} else { } else {
$price = apply_filters( 'woocommerce_grouped_empty_price_html', '', $this ); $price = apply_filters( 'woocommerce_grouped_empty_price_html', '', $this );
@ -161,4 +113,67 @@ class WC_Product_Grouped extends WC_Product {
return apply_filters( 'woocommerce_get_price_html', $price, $this ); return apply_filters( 'woocommerce_get_price_html', $price, $this );
} }
/*
|--------------------------------------------------------------------------
| Getters
|--------------------------------------------------------------------------
|
| Methods for getting data from the product object.
*/
/**
* Return the children of this product.
*
* @param string $context
* @return array
*/
public function get_children( $context = 'view' ) {
return $this->get_prop( 'children', $context );
}
/*
|--------------------------------------------------------------------------
| Setters
|--------------------------------------------------------------------------
|
| Methods for getting data from the product object.
*/
/**
* Return the children of this product.
*
* @param array $children
*/
public function set_children( $children ) {
$this->set_prop( 'children', array_filter( wp_parse_id_list( (array) $children ) ) );
}
/*
|--------------------------------------------------------------------------
| Sync with children.
|--------------------------------------------------------------------------
*/
/**
* Sync a grouped product with it's children. These sync functions sync
* upwards (from child to parent) when the variation is saved.
*
* @param WC_Product|int $product Product object or ID for which you wish to sync.
* @param bool $save If true, the prouduct object will be saved to the DB before returning it.
* @return WC_Product Synced product object.
*/
public static function sync( $product, $save = true ) {
if ( ! is_a( $product, 'WC_Product' ) ) {
$product = wc_get_product( $product );
}
if ( is_a( $product, 'WC_Product_Grouped' ) ) {
$data_store = WC_Data_Store::load( 'product_' . $product->get_type() );
$data_store->sync_price( $product );
if ( $save ) {
$product->save();
}
}
return $product;
}
} }

View File

@ -10,7 +10,6 @@ if ( ! defined( 'ABSPATH' ) ) {
* The default product type kinda product. * The default product type kinda product.
* *
* @class WC_Product_Simple * @class WC_Product_Simple
* @version 2.0.0
* @package WooCommerce/Classes/Products * @package WooCommerce/Classes/Products
* @category Class * @category Class
* @author WooThemes * @author WooThemes
@ -22,12 +21,19 @@ class WC_Product_Simple extends WC_Product {
* *
* @param mixed $product * @param mixed $product
*/ */
public function __construct( $product ) { public function __construct( $product = 0 ) {
$this->product_type = 'simple';
$this->supports[] = 'ajax_add_to_cart'; $this->supports[] = 'ajax_add_to_cart';
parent::__construct( $product ); parent::__construct( $product );
} }
/**
* Get internal type.
* @return string
*/
public function get_type() {
return 'simple';
}
/** /**
* Get the add to url used mainly in loops. * Get the add to url used mainly in loops.
* *
@ -49,48 +55,4 @@ class WC_Product_Simple extends WC_Product {
return apply_filters( 'woocommerce_product_add_to_cart_text', $text, $this ); return apply_filters( 'woocommerce_product_add_to_cart_text', $text, $this );
} }
/**
* Get the title of the product.
*
* @return string
*/
public function get_title() {
$title = $this->post->post_title;
if ( $this->get_parent() > 0 ) {
/* translators: 1: parent product title 2: product title */
$title = sprintf( __( '%1$s &rarr; %2$s' , 'woocommerce' ), get_the_title( $this->get_parent() ), $title );
}
return apply_filters( 'woocommerce_product_title', $title, $this );
}
/**
* Sync grouped products with the children lowest price (so they can be sorted by price accurately).
*/
public function grouped_product_sync() {
if ( ! $this->get_parent() ) return;
$children_by_price = get_posts( array(
'post_parent' => $this->get_parent(),
'orderby' => 'meta_value_num',
'order' => 'asc',
'meta_key' => '_price',
'posts_per_page' => 1,
'post_type' => 'product',
'fields' => 'ids',
));
if ( $children_by_price ) {
foreach ( $children_by_price as $child ) {
$child_price = get_post_meta( $child, '_price', true );
update_post_meta( $this->get_parent(), '_price', $child_price );
}
}
delete_transient( 'wc_products_onsale' );
do_action( 'woocommerce_grouped_product_sync', $this->id, $children_by_price );
}
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -693,7 +693,7 @@ class WC_Shortcodes {
if ( isset( $atts['sku'] ) && $single_product->have_posts() && 'product_variation' === $single_product->post->post_type ) { if ( isset( $atts['sku'] ) && $single_product->have_posts() && 'product_variation' === $single_product->post->post_type ) {
$variation = new WC_Product_Variation( $single_product->post->ID ); $variation = new WC_Product_Variation( $single_product->post->ID );
$attributes = $variation->get_variation_attributes(); $attributes = $variation->get_attributes();
// set preselected id to be used by JS to provide context // set preselected id to be used by JS to provide context
$preselected_id = $single_product->post->ID; $preselected_id = $single_product->post->ID;

View File

@ -173,7 +173,7 @@ class WC_Structured_Data {
$markup['@type'] = 'Product'; $markup['@type'] = 'Product';
$markup['@id'] = get_permalink( $product->get_id() ); $markup['@id'] = get_permalink( $product->get_id() );
$markup['url'] = $markup['@id']; $markup['url'] = $markup['@id'];
$markup['name'] = $product->get_title(); $markup['name'] = $product->get_name();
if ( apply_filters( 'woocommerce_structured_data_product_limit', is_product_taxonomy() || is_shop() ) ) { if ( apply_filters( 'woocommerce_structured_data_product_limit', is_product_taxonomy() || is_shop() ) ) {
$this->set_data( apply_filters( 'woocommerce_structured_data_product_limited', $markup, $product ) ); $this->set_data( apply_filters( 'woocommerce_structured_data_product_limited', $markup, $product ) );
@ -202,7 +202,7 @@ class WC_Structured_Data {
'availability' => 'http://schema.org/' . $stock = ( $_product->is_in_stock() ? 'InStock' : 'OutOfStock' ), 'availability' => 'http://schema.org/' . $stock = ( $_product->is_in_stock() ? 'InStock' : 'OutOfStock' ),
'sku' => $_product->get_sku(), 'sku' => $_product->get_sku(),
'image' => wp_get_attachment_url( $_product->get_image_id() ), 'image' => wp_get_attachment_url( $_product->get_image_id() ),
'description' => $is_variable ? $_product->get_variation_description() : '', 'description' => $_product->get_description(),
'seller' => array( 'seller' => array(
'@type' => 'Organization', '@type' => 'Organization',
'name' => $shop_name, 'name' => $shop_name,

View File

@ -329,7 +329,6 @@ class WC_Tax {
* - State code * - State code
* @param array $rates * @param array $rates
* @return array * @return array
* @todo remove tax_rate_order column
*/ */
private static function sort_rates( $rates ) { private static function sort_rates( $rates ) {
uasort( $rates, __CLASS__ . '::sort_rates_callback' ); uasort( $rates, __CLASS__ . '::sort_rates_callback' );
@ -522,8 +521,10 @@ class WC_Tax {
*/ */
public static function get_shipping_tax_rates( $tax_class = null ) { public static function get_shipping_tax_rates( $tax_class = null ) {
// See if we have an explicitly set shipping tax class // See if we have an explicitly set shipping tax class
if ( $shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' ) ) { $shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
$tax_class = 'standard' === $shipping_tax_class ? '' : $shipping_tax_class;
if ( 'inherit' !== $shipping_tax_class ) {
$tax_class = $shipping_tax_class;
} }
$location = self::get_tax_location( $tax_class ); $location = self::get_tax_location( $tax_class );

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -12,4 +12,20 @@ if ( ! defined( 'ABSPATH' ) ) {
*/ */
class WC_Data_Store_CPT { class WC_Data_Store_CPT {
/**
* Get and store terms from a taxonomy.
*
* @since 2.7.0
* @param WC_Product
* @param string $taxonomy Taxonomy name e.g. product_cat
* @return array of terms
*/
protected function get_term_ids( $product, $taxonomy ) {
$terms = get_the_terms( $product->get_id(), $taxonomy );
if ( false === $terms || is_wp_error( $terms ) ) {
return array();
}
return wp_list_pluck( $terms, 'term_id' );
}
} }

View File

@ -0,0 +1,870 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Product Data Store: Stored in CPT.
*
* @version 2.7.0
* @category Class
* @author WooThemes
*/
class WC_Product_Data_Store_CPT extends WC_Data_Store_CPT implements WC_Object_Data_Store, WC_Product_Data_Store_Interface {
/**
* If we have already saved our extra data, don't do automatic / default handling.
*/
protected $extra_data_saved = false;
/*
|--------------------------------------------------------------------------
| CRUD Methods
|--------------------------------------------------------------------------
*/
/**
* Method to create a new product in the database.
* @param WC_Product
*/
public function create( &$product ) {
$product->set_date_created( current_time( 'timestamp' ) );
$id = wp_insert_post( apply_filters( 'woocommerce_new_product_data', array(
'post_type' => 'product',
'post_status' => $product->get_status() ? $product->get_status() : 'publish',
'post_author' => get_current_user_id(),
'post_title' => $product->get_name() ? $product->get_name() : __( 'Product', 'woocommerce' ),
'post_content' => $product->get_description(),
'post_excerpt' => $product->get_short_description(),
'post_parent' => $product->get_parent_id(),
'comment_status' => $product->get_reviews_allowed() ? 'open' : 'closed',
'ping_status' => 'closed',
'menu_order' => $product->get_menu_order(),
'post_date' => date( 'Y-m-d H:i:s', $product->get_date_created() ),
'post_date_gmt' => get_gmt_from_date( date( 'Y-m-d H:i:s', $product->get_date_created() ) ),
) ), true );
if ( $id && ! is_wp_error( $id ) ) {
$product->set_id( $id );
$this->update_post_meta( $product );
$this->update_terms( $product );
$this->update_attributes( $product );
$this->update_downloads( $product );
$product->save_meta_data();
do_action( 'woocommerce_new_product', $id );
$product->apply_changes();
$this->update_version_and_type( $product );
$this->update_term_counts( $product );
$this->clear_caches( $product );
}
}
/**
* Method to read a product from the database.
* @param WC_Product
*/
public function read( &$product ) {
$product->set_defaults();
if ( ! $product->get_id() || ! ( $post_object = get_post( $product->get_id() ) ) ) {
throw new Exception( __( 'Invalid product.', 'woocommerce' ) );
}
$id = $product->get_id();
$product->set_props( array(
'name' => get_the_title( $post_object ),
'slug' => $post_object->post_name,
'date_created' => $post_object->post_date,
'date_modified' => $post_object->post_modified,
'status' => $post_object->post_status,
'description' => $post_object->post_content,
'short_description' => $post_object->post_excerpt,
'parent_id' => $post_object->post_parent,
'menu_order' => $post_object->menu_order,
'reviews_allowed' => 'open' === $post_object->comment_status,
) );
$product->read_meta_data();
$this->read_attributes( $product );
$this->read_downloads( $product );
$this->read_product_data( $product );
$product->set_object_read( true );
}
/**
* Method to update a product in the database.
* @param WC_Product
*/
public function update( &$product ) {
$post_data = array(
'ID' => $product->get_id(),
'post_content' => $product->get_description(),
'post_excerpt' => $product->get_short_description(),
'post_title' => $product->get_name(),
'post_parent' => $product->get_parent_id(),
'comment_status' => $product->get_reviews_allowed() ? 'open' : 'closed',
'post_status' => $product->get_status() ? $product->get_status() : 'publish',
'menu_order' => $product->get_menu_order(),
);
wp_update_post( $post_data );
$this->update_post_meta( $product );
$this->update_terms( $product );
$this->update_attributes( $product );
$this->update_downloads( $product );
$product->save_meta_data();
do_action( 'woocommerce_update_product', $product->get_id() );
$product->apply_changes();
$this->update_version_and_type( $product );
$this->update_term_counts( $product );
$this->clear_caches( $product );
}
/**
* Method to delete a product from the database.
* @param WC_Product
* @param array $args Array of args to pass to the delete method.
*/
public function delete( &$product, $args = array() ) {
$id = $product->get_id();
$post_type = $product->is_type( 'variation' ) ? 'product_variation' : 'product';
$args = wp_parse_args( $args, array(
'force_delete' => false,
) );
if ( $args['force_delete'] ) {
wp_delete_post( $product->get_id() );
$product->set_id( 0 );
} else {
wp_trash_post( $product->get_id() );
$product->set_status( 'trash' );
}
do_action( 'woocommerce_delete_' . $post_type, $id );
}
/*
|--------------------------------------------------------------------------
| Additional Methods
|--------------------------------------------------------------------------
*/
/**
* Read product data. Can be overridden by child classes to load other props.
*
* @param WC_Product
* @since 2.7.0
*/
protected function read_product_data( &$product ) {
$id = $product->get_id();
if ( '' === ( $review_count = get_post_meta( $id, '_wc_review_count', true ) ) ) {
WC_Comments::get_review_count_for_product( $product );
} else {
$product->set_review_count( $review_count );
}
if ( '' === ( $rating_counts = get_post_meta( $id, '_wc_rating_count', true ) ) ) {
WC_Comments::get_rating_counts_for_product( $product );
} else {
$product->set_rating_counts( $rating_counts );
}
if ( '' === ( $average_rating = get_post_meta( $id, '_wc_average_rating', true ) ) ) {
WC_Comments::get_average_rating_for_product( $product );
} else {
$product->set_average_rating( $average_rating );
}
$product->set_props( array(
'featured' => get_post_meta( $id, '_featured', true ),
'catalog_visibility' => get_post_meta( $id, '_visibility', true ),
'sku' => get_post_meta( $id, '_sku', true ),
'regular_price' => get_post_meta( $id, '_regular_price', true ),
'sale_price' => get_post_meta( $id, '_sale_price', true ),
'price' => get_post_meta( $id, '_price', true ),
'date_on_sale_from' => get_post_meta( $id, '_sale_price_dates_from', true ),
'date_on_sale_to' => get_post_meta( $id, '_sale_price_dates_to', true ),
'total_sales' => get_post_meta( $id, 'total_sales', true ),
'tax_status' => get_post_meta( $id, '_tax_status', true ),
'tax_class' => get_post_meta( $id, '_tax_class', true ),
'manage_stock' => get_post_meta( $id, '_manage_stock', true ),
'stock_quantity' => get_post_meta( $id, '_stock', true ),
'stock_status' => get_post_meta( $id, '_stock_status', true ),
'backorders' => get_post_meta( $id, '_backorders', true ),
'sold_individually' => get_post_meta( $id, '_sold_individually', true ),
'weight' => get_post_meta( $id, '_weight', true ),
'length' => get_post_meta( $id, '_length', true ),
'width' => get_post_meta( $id, '_width', true ),
'height' => get_post_meta( $id, '_height', true ),
'upsell_ids' => get_post_meta( $id, '_upsell_ids', true ),
'cross_sell_ids' => get_post_meta( $id, '_crosssell_ids', true ),
'purchase_note' => get_post_meta( $id, '_purchase_note', true ),
'default_attributes' => get_post_meta( $id, '_default_attributes', true ),
'category_ids' => $this->get_term_ids( $product, 'product_cat' ),
'tag_ids' => $this->get_term_ids( $product, 'product_tag' ),
'shipping_class_id' => current( $this->get_term_ids( $product, 'product_shipping_class' ) ),
'virtual' => get_post_meta( $id, '_virtual', true ),
'downloadable' => get_post_meta( $id, '_downloadable', true ),
'gallery_image_ids' => array_filter( explode( ',', get_post_meta( $id, '_product_image_gallery', true ) ) ),
'download_limit' => get_post_meta( $id, '_download_limit', true ),
'download_expiry' => get_post_meta( $id, '_download_expiry', true ),
'image_id' => get_post_thumbnail_id( $id ),
) );
// Gets extra data associated with the product.
// Like button text or product URL for external products.
foreach ( $product->get_extra_data_keys() as $key ) {
$function = 'set_' . $key;
if ( is_callable( array( $product, $function ) ) ) {
$product->{$function}( get_post_meta( $product->get_id(), '_' . $key, true ) );
}
}
}
/**
* Read attributes from post meta.
*
* @param WC_Product
* @since 2.7.0
*/
protected function read_attributes( &$product ) {
$meta_values = maybe_unserialize( get_post_meta( $product->get_id(), '_product_attributes', true ) );
if ( $meta_values ) {
$attributes = array();
foreach ( $meta_values as $meta_value ) {
if ( ! empty( $meta_value['is_taxonomy'] ) ) {
if ( ! taxonomy_exists( $meta_value['name'] ) ) {
continue;
}
$options = wp_get_post_terms( $product->get_id(), $meta_value['name'], array( 'fields' => 'ids' ) );
} else {
$options = wc_get_text_attributes( $meta_value['value'] );
}
$attribute = new WC_Product_Attribute();
$attribute->set_id( wc_attribute_taxonomy_id_by_name( $meta_value['name'] ) );
$attribute->set_name( $meta_value['name'] );
$attribute->set_options( $options );
$attribute->set_position( $meta_value['position'] );
$attribute->set_visible( $meta_value['is_visible'] );
$attribute->set_variation( $meta_value['is_variation'] );
$attributes[] = $attribute;
}
$product->set_attributes( $attributes );
}
}
/**
* Read downloads from post meta.
*
* @param WC_Product
* @since 2.7.0
*/
protected function read_downloads( &$product ) {
$meta_values = array_filter( (array) maybe_unserialize( get_post_meta( $product->get_id(), '_downloadable_files', true ) ) );
if ( $meta_values ) {
$downloads = array();
foreach ( $meta_values as $key => $value ) {
$download = new WC_Product_Download();
$download->set_id( $key );
$download->set_name( $value['name'] ? $value['name'] : wc_get_filename_from_url( $value['file'] ) );
$download->set_file( apply_filters( 'woocommerce_file_download_path', $value['file'], $product, $key ) );
$downloads[] = $download;
}
$product->set_downloads( $downloads );
}
}
/**
* Helper method that updates all the post meta for a product based on it's settings in the WC_Product class.
*
* @param WC_Product
* @since 2.7.0
*/
protected function update_post_meta( &$product ) {
$updated_props = array();
$changed_props = array_keys( $product->get_changes() );
$meta_key_to_props = array(
'_visibility' => 'catalog_visibility',
'_sku' => 'sku',
'_regular_price' => 'regular_price',
'_sale_price' => 'sale_price',
'_sale_price_dates_from' => 'date_on_sale_from',
'_sale_price_dates_to' => 'date_on_sale_to',
'total_sales' => 'total_sales',
'_tax_status' => 'tax_status',
'_tax_class' => 'tax_class',
'_manage_stock' => 'manage_stock',
'_backorders' => 'backorders',
'_sold_individually' => 'sold_individually',
'_weight' => 'weight',
'_length' => 'length',
'_width' => 'width',
'_height' => 'height',
'_upsell_ids' => 'upsell_ids',
'_crosssell_ids' => 'cross_sell_ids',
'_purchase_note' => 'purchase_note',
'_default_attributes' => 'default_attributes',
'_virtual' => 'virtual',
'_downloadable' => 'downloadable',
'_product_image_gallery' => 'gallery_image_ids',
'_download_limit' => 'download_limit',
'_download_expiry' => 'download_expiry',
'_featured' => 'featured',
'_thumbnail_id' => 'image_id',
'_downloadable_files' => 'downloads',
'_stock' => 'stock_quantity',
'_stock_status' => 'stock_status',
'_wc_average_rating' => 'average_rating',
'_wc_rating_count' => 'rating_counts',
'_wc_review_count' => 'review_count',
);
foreach ( $meta_key_to_props as $meta_key => $prop ) {
if ( ! in_array( $prop, $changed_props ) ) {
continue;
}
$value = $product->{"get_$prop"}( 'edit' );
switch ( $prop ) {
case 'virtual' :
case 'downloadable' :
case 'manage_stock' :
case 'featured' :
case 'sold_individually' :
$updated = update_post_meta( $product->get_id(), $meta_key, wc_bool_to_string( $value ) );
break;
case 'gallery_image_ids' :
$updated = update_post_meta( $product->get_id(), $meta_key, implode( ',', $value ) );
break;
case 'downloads' :
// grant permission to any newly added files on any existing orders for this product prior to saving.
if ( $product->is_type( 'variation' ) ) {
do_action( 'woocommerce_process_product_file_download_paths', $product->get_parent_id(), $product->get_id(), $value );
} else {
do_action( 'woocommerce_process_product_file_download_paths', $product->get_id(), 0, $value );
}
$updated = update_post_meta( $product->get_id(), $meta_key, $value );
break;
case 'image_id' :
if ( ! empty( $value ) ) {
set_post_thumbnail( $product->get_id(), $value );
} else {
delete_post_meta( $product->get_id(), '_thumbnail_id' );
}
$updated = true;
break;
default :
$updated = update_post_meta( $product->get_id(), $meta_key, $value );
break;
}
if ( $updated ) {
$updated_props[] = $prop;
}
}
if ( in_array( 'date_on_sale_from', $updated_props ) || in_array( 'date_on_sale_to', $updated_props ) || in_array( 'regular_price', $updated_props ) || in_array( 'sale_price', $updated_props ) ) {
if ( $product->is_on_sale() ) {
update_post_meta( $product->get_id(), '_price', $product->get_sale_price() );
$product->set_price( $product->get_sale_price() );
} else {
update_post_meta( $product->get_id(), '_price', $product->get_regular_price() );
$product->set_price( $product->get_regular_price() );
}
}
if ( in_array( 'featured', $updated_props ) ) {
delete_transient( 'wc_featured_products' );
}
if ( in_array( 'catalog_visibility', $updated_props ) ) {
do_action( 'woocommerce_product_set_visibility', $product->get_id(), $product->get_catalog_visibility() );
}
if ( in_array( 'stock_quantity', $updated_props ) ) {
do_action( $product->is_type( 'variation' ) ? 'woocommerce_variation_set_stock' : 'woocommerce_product_set_stock' , $product );
}
if ( in_array( 'stock_status', $updated_props ) ) {
do_action( $product->is_type( 'variation' ) ? 'woocommerce_variation_set_stock_status' : 'woocommerce_product_set_stock_status' , $product->get_id(), $product->get_stock_status() );
}
// Update extra data associated with the product.
// Like button text or product URL for external products.
if ( ! $this->extra_data_saved ) {
foreach ( $product->get_extra_data_keys() as $key ) {
$function = 'get_' . $key;
if ( in_array( $key, $changed_props ) && is_callable( array( $product, $function ) ) ) {
update_post_meta( $product->get_id(), '_' . $key, $product->{$function}( 'edit' ) );
}
}
}
}
/**
* For all stored terms in all taxonomies, save them to the DB.
*
* @param WC_Product
* @since 2.7.0
*/
protected function update_terms( &$product ) {
wp_set_post_terms( $product->get_id(), $product->get_category_ids( 'edit' ), 'product_cat', false );
wp_set_post_terms( $product->get_id(), $product->get_tag_ids( 'edit' ), 'product_tag', false );
wp_set_post_terms( $product->get_id(), array( $product->get_shipping_class_id( 'edit' ) ), 'product_shipping_class', false );
}
/**
* Update attributes which are a mix of terms and meta data.
*
* @param WC_Product
* @since 2.7.0
*/
protected function update_attributes( &$product ) {
$attributes = $product->get_attributes();
$meta_values = array();
if ( $attributes ) {
foreach ( $attributes as $attribute_key => $attribute ) {
$value = '';
if ( is_null( $attribute ) ) {
if ( taxonomy_exists( $attribute_key ) ) {
// Handle attributes that have been unset.
wp_set_object_terms( $product->get_id(), array(), $attribute_key );
}
continue;
} elseif ( $attribute->is_taxonomy() ) {
wp_set_object_terms( $product->get_id(), wp_list_pluck( $attribute->get_terms(), 'term_id' ), $attribute->get_name() );
} else {
$value = wc_implode_text_attributes( $attribute->get_options() );
}
// Store in format WC uses in meta.
$meta_values[ $attribute_key ] = array(
'name' => $attribute->get_name(),
'value' => $value,
'position' => $attribute->get_position(),
'is_visible' => $attribute->get_visible() ? 1 : 0,
'is_variation' => $attribute->get_variation() ? 1 : 0,
'is_taxonomy' => $attribute->is_taxonomy() ? 1 : 0,
);
}
}
update_post_meta( $product->get_id(), '_product_attributes', $meta_values );
}
/**
* Update downloads.
*
* @since 2.7.0
*/
protected function update_downloads( &$product ) {
$downloads = $product->get_downloads();
$meta_values = array();
if ( $downloads ) {
foreach ( $downloads as $key => $download ) {
// Store in format WC uses in meta.
$meta_values[ $key ] = $download->get_data();
}
}
update_post_meta( $product->get_id(), '_downloadable_files', $meta_values );
}
/**
* Make sure we store the product type and version (to track data changes).
*
* @param WC_Product
* @since 2.7.0
*/
protected function update_version_and_type( &$product ) {
$type_term = get_term_by( 'name', $product->get_type(), 'product_type' );
wp_set_object_terms( $product->get_id(), absint( $type_term->term_id ), 'product_type' );
update_post_meta( $product->get_id(), '_product_version', WC_VERSION );
}
/**
* Count terms. These are done at this point so all product props are set in advance.
*
* @param WC_Product
* @since 2.7.0
*/
protected function update_term_counts( &$product ) {
if ( ! wp_defer_term_counting() ) {
global $wc_allow_term_recount;
$wc_allow_term_recount = true;
$post_type = $product->is_type( 'variation' ) ? 'product_variation' : 'product';
// Update counts for the post's terms.
foreach ( (array) get_object_taxonomies( $post_type ) as $taxonomy ) {
$tt_ids = wp_get_object_terms( $product->get_id(), $taxonomy, array( 'fields' => 'tt_ids' ) );
wp_update_term_count( $tt_ids, $taxonomy );
}
}
}
/**
* Clear any caches.
*
* @param WC_Product
* @since 2.7.0
*/
protected function clear_caches( &$product ) {
wc_delete_product_transients( $product->get_id() );
}
/*
|--------------------------------------------------------------------------
| wc-product-functions.php methods
|--------------------------------------------------------------------------
*/
/**
* Returns an array of on sale products, as an array of objects with an
* ID and parent_id present. Example: $return[0]->id, $return[0]->parent_id.
*
* @return array
* @since 2.7.0
*/
public function get_on_sale_products() {
global $wpdb;
return $wpdb->get_results( "
SELECT post.ID as id, post.post_parent as parent_id FROM `$wpdb->posts` AS post
LEFT JOIN `$wpdb->postmeta` AS meta ON post.ID = meta.post_id
LEFT JOIN `$wpdb->postmeta` AS meta2 ON post.ID = meta2.post_id
WHERE post.post_type IN ( 'product', 'product_variation' )
AND post.post_status = 'publish'
AND meta.meta_key = '_sale_price'
AND meta2.meta_key = '_price'
AND CAST( meta.meta_value AS DECIMAL ) >= 0
AND CAST( meta.meta_value AS CHAR ) != ''
AND CAST( meta.meta_value AS DECIMAL ) = CAST( meta2.meta_value AS DECIMAL )
GROUP BY post.ID;
" );
}
/**
* Returns a list of product IDs ( id as key => parent as value) that are
* featured. Uses get_posts instead of wc_get_products since we want
* some extra meta queries and ALL products (posts_per_page = -1).
*
* @return array
* @since 2.7.0
*/
public function get_featured_product_ids() {
return get_posts( array(
'post_type' => array( 'product', 'product_variation' ),
'posts_per_page' => -1,
'post_status' => 'publish',
'meta_query' => array(
array(
'key' => '_visibility',
'value' => array( 'catalog', 'visible' ),
'compare' => 'IN',
),
array(
'key' => '_featured',
'value' => 'yes',
),
),
'fields' => 'id=>parent',
) );
}
/**
* Check if product sku is found for any other product IDs.
*
* @since 2.7.0
* @param int $product_id
* @param string $sku Will be slashed to work around https://core.trac.wordpress.org/ticket/27421
* @return bool
*/
public function is_existing_sku( $product_id, $sku ) {
global $wpdb;
return $wpdb->get_var( $wpdb->prepare( "
SELECT $wpdb->posts.ID
FROM $wpdb->posts
LEFT JOIN $wpdb->postmeta ON ( $wpdb->posts.ID = $wpdb->postmeta.post_id )
WHERE $wpdb->posts.post_type IN ( 'product', 'product_variation' )
AND $wpdb->posts.post_status = 'publish'
AND $wpdb->postmeta.meta_key = '_sku' AND $wpdb->postmeta.meta_value = '%s'
AND $wpdb->postmeta.post_id <> %d LIMIT 1
", wp_slash( $sku ), $product_id ) );
}
/**
* Return product ID based on SKU.
*
* @since 2.7.0
* @param string $sku
* @return int
*/
public function get_product_id_by_sku( $sku ) {
global $wpdb;
return $wpdb->get_var( $wpdb->prepare( "
SELECT posts.ID
FROM $wpdb->posts AS posts
LEFT JOIN $wpdb->postmeta AS postmeta ON ( posts.ID = postmeta.post_id )
WHERE posts.post_type IN ( 'product', 'product_variation' )
AND postmeta.meta_key = '_sku' AND postmeta.meta_value = '%s'
LIMIT 1
", $sku ) );
}
/**
* Returns an array of IDs of products that have sales starting soon.
*
* @since 2.7.0
* @return array
*/
public function get_starting_sales() {
global $wpdb;
return $wpdb->get_col( $wpdb->prepare( "
SELECT postmeta.post_id FROM {$wpdb->postmeta} as postmeta
LEFT JOIN {$wpdb->postmeta} as postmeta_2 ON postmeta.post_id = postmeta_2.post_id
LEFT JOIN {$wpdb->postmeta} as postmeta_3 ON postmeta.post_id = postmeta_3.post_id
WHERE postmeta.meta_key = '_sale_price_dates_from'
AND postmeta_2.meta_key = '_price'
AND postmeta_3.meta_key = '_sale_price'
AND postmeta.meta_value > 0
AND postmeta.meta_value < %s
AND postmeta_2.meta_value != postmeta_3.meta_value
", current_time( 'timestamp' ) ) );
}
/**
* Returns an array of IDs of products that have sales which are due to end.
*
* @since 2.7.0
* @return array
*/
public function get_ending_sales() {
global $wpdb;
return $wpdb->get_col( $wpdb->prepare( "
SELECT postmeta.post_id FROM {$wpdb->postmeta} as postmeta
LEFT JOIN {$wpdb->postmeta} as postmeta_2 ON postmeta.post_id = postmeta_2.post_id
LEFT JOIN {$wpdb->postmeta} as postmeta_3 ON postmeta.post_id = postmeta_3.post_id
WHERE postmeta.meta_key = '_sale_price_dates_to'
AND postmeta_2.meta_key = '_price'
AND postmeta_3.meta_key = '_regular_price'
AND postmeta.meta_value > 0
AND postmeta.meta_value < %s
AND postmeta_2.meta_value != postmeta_3.meta_value
", current_time( 'timestamp' ) ) );
}
/**
* Find a matching (enabled) variation within a variable product.
*
* @since 2.7.0
* @param WC_Product $product Variable product.
* @param array $match_attributes Array of attributes we want to try to match.
* @return int Matching variation ID or 0.
*/
public function find_matching_product_variation( $product, $match_attributes = array() ) {
$query_args = array(
'post_parent' => $product->get_id(),
'post_type' => 'product_variation',
'orderby' => 'menu_order',
'order' => 'ASC',
'fields' => 'ids',
'post_status' => 'publish',
'numberposts' => 1,
'meta_query' => array(),
);
// Allow large queries in case user has many variations or attributes.
$GLOBALS['wpdb']->query( 'SET SESSION SQL_BIG_SELECTS=1' );
foreach ( $product->get_attributes() as $attribute ) {
if ( ! $attribute->get_variation() ) {
continue;
}
$attribute_field_name = 'attribute_' . sanitize_title( $attribute->get_name() );
if ( ! isset( $match_attributes[ $attribute_field_name ] ) ) {
return 0;
}
$value = wc_clean( $match_attributes[ $attribute_field_name ] );
$query_args['meta_query'][] = array(
'relation' => 'OR',
array(
'key' => $attribute_field_name,
'value' => array( '', $value ),
'compare' => 'IN',
),
array(
'key' => $attribute_field_name,
'compare' => 'NOT EXISTS',
)
);
}
$variations = get_posts( $query_args );
if ( $variations && ! is_wp_error( $variations ) ) {
return current( $variations );
} elseif ( version_compare( get_post_meta( $product->get_id(), '_product_version', true ), '2.4.0', '<' ) ) {
/**
* Pre 2.4 handling where 'slugs' were saved instead of the full text attribute.
* Fallback is here because there are cases where data will be 'synced' but the product version will remain the same.
*/
return ( array_map( 'sanitize_title', $match_attributes ) === $match_attributes ) ? 0 : $this->find_matching_product_variation( $product, array_map( 'sanitize_title', $match_attributes ) );
}
return 0;
}
/**
* Return a list of related products (using data like categories and IDs).
*
* @since 2.7.0
* @param array $cats_array List of categories IDs.
* @param array $tags_array List of tags IDs.
* @param array $exclude_ids Excluded IDs.
* @param int $limit Limit of results.
* @param int $product_id
* @return array
*/
public function get_related_products( $cats_array, $tags_array, $exclude_ids, $limit, $product_id ) {
global $wpdb;
return $wpdb->get_col( implode( ' ', apply_filters( 'woocommerce_product_related_posts_query', $this->get_related_products_query( $cats_array, $tags_array, $exclude_ids, $limit + 10 ), $product_id ) ) );
}
/**
* Builds the related posts query.
*
* @since 2.7.0
* @param array $cats_array List of categories IDs.
* @param array $tags_array List of tags IDs.
* @param array $exclude_ids Excluded IDs.
* @param int $limit Limit of results.
* @return string
*/
public function get_related_products_query( $cats_array, $tags_array, $exclude_ids, $limit ) {
global $wpdb;
// Arrays to string.
$exclude_ids = implode( ',', array_map( 'absint', $exclude_ids ) );
$cats_array = implode( ',', array_map( 'absint', $cats_array ) );
$tags_array = implode( ',', array_map( 'absint', $tags_array ) );
$limit = absint( $limit );
$query = array();
$query['fields'] = "SELECT DISTINCT ID FROM {$wpdb->posts} p";
$query['join'] = " INNER JOIN {$wpdb->postmeta} pm ON ( pm.post_id = p.ID AND pm.meta_key='_visibility' )";
$query['join'] .= " INNER JOIN {$wpdb->term_relationships} tr ON (p.ID = tr.object_id)";
$query['join'] .= " INNER JOIN {$wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id)";
$query['join'] .= " INNER JOIN {$wpdb->terms} t ON (t.term_id = tt.term_id)";
if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
$query['join'] .= " INNER JOIN {$wpdb->postmeta} pm2 ON ( pm2.post_id = p.ID AND pm2.meta_key='_stock_status' )";
}
$query['where'] = ' WHERE 1=1';
$query['where'] .= " AND p.post_status = 'publish'";
$query['where'] .= " AND p.post_type = 'product'";
$query['where'] .= " AND p.ID NOT IN ( {$exclude_ids} )";
$query['where'] .= " AND pm.meta_value IN ( 'visible', 'catalog' )";
if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
$query['where'] .= " AND pm2.meta_value = 'instock'";
}
if ( $cats_array || $tags_array ) {
$query['where'] .= ' AND (';
if ( $cats_array ) {
$query['where'] .= " ( tt.taxonomy = 'product_cat' AND t.term_id IN ( {$cats_array} ) ) ";
if ( $tags_array ) {
$query['where'] .= ' OR ';
}
}
if ( $tags_array ) {
$query['where'] .= " ( tt.taxonomy = 'product_tag' AND t.term_id IN ( {$tags_array} ) ) ";
}
$query['where'] .= ')';
}
$query['limits'] = " LIMIT {$limit} ";
return $query;
}
/**
* Update a product's stock amount directly.
*
* Uses queries rather than update_post_meta so we can do this in one query (to avoid stock issues).
*
* @since 2.7.0 this supports set, increase and decrease.
* @param int
* @param int|null $stock_quantity
* @param string $operation set, increase and decrease.
*/
public function update_product_stock( $product_id_with_stock, $stock_quantity = null, $operation = 'set' ) {
global $wpdb;
add_post_meta( $product_id_with_stock, '_stock', 0, true );
// Update stock in DB directly
switch ( $operation ) {
case 'increase' :
$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = meta_value + %f WHERE post_id = %d AND meta_key='_stock'", $stock_quantity, $product_id_with_stock ) );
break;
case 'decrease' :
$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = meta_value - %f WHERE post_id = %d AND meta_key='_stock'", $stock_quantity, $product_id_with_stock ) );
break;
default :
$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = %f WHERE post_id = %d AND meta_key='_stock'", $stock_quantity, $product_id_with_stock ) );
break;
}
wp_cache_delete( $product_id_with_stock, 'post_meta' );
}
/**
* Update a products average rating meta.
*
* @since 2.7.0
* @param WC_Product $product
*/
public function update_average_rating( $product ) {
update_post_meta( $product->get_id(), '_wc_average_rating', $product->get_average_rating( 'edit' ) );
}
/**
* Update a products review count meta.
*
* @since 2.7.0
* @param WC_Product $product
*/
public function update_review_count( $product ) {
update_post_meta( $product->get_id(), '_wc_review_count', $product->get_review_count( 'edit' ) );
}
/**
* Update a products rating counts.
*
* @since 2.7.0
* @param WC_Product $product
*/
public function update_rating_counts( $product ) {
update_post_meta( $product->get_id(), '_wc_rating_count', $product->get_rating_counts( 'edit' ) );
}
}

View File

@ -0,0 +1,71 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Grouped Product Data Store: Stored in CPT.
*
* @version 2.7.0
* @category Class
* @author WooThemes
*/
class WC_Product_Grouped_Data_Store_CPT extends WC_Product_Data_Store_CPT implements WC_Object_Data_Store {
/**
* Helper method that updates all the post meta for a grouped product.
*
* @param WC_Product
* @since 2.7.0
*/
protected function update_post_meta( &$product ) {
if ( update_post_meta( $product->get_id(), '_children', $product->get_children( 'edit' ) ) ) {
$child_prices = array();
foreach ( $product->get_children( 'edit' ) as $child_id ) {
$child = wc_get_product( $child_id );
if ( $child ) {
$child_prices[] = $child->get_price();
}
}
$child_prices = array_filter( $child_prices );
delete_post_meta( $product->get_id(), '_price' );
if ( ! empty( $child_prices ) ) {
add_post_meta( $product->get_id(), '_price', min( $child_prices ) );
add_post_meta( $product->get_id(), '_price', max( $child_prices ) );
}
$this->extra_data_saved = true;
}
parent::update_post_meta( $product );
}
/**
* Sync grouped product prices with children.
*
* @since 2.7.0
* @param WC_Product|int $product
*/
public function sync_price( &$product ) {
global $wpdb;
$children_ids = get_posts( array(
'post_parent' => $product->get_id(),
'post_type' => 'product',
'fields' => 'ids',
) );
$prices = $children_ids ? array_unique( $wpdb->get_col( "SELECT meta_value FROM $wpdb->postmeta WHERE meta_key = '_price' AND post_id IN ( " . implode( ',', array_map( 'absint', $children_ids ) ) . " )" ) ) : array();
delete_post_meta( $product->get_id(), '_price' );
delete_transient( 'wc_var_prices_' . $product->get_id() );
if ( $prices ) {
sort( $prices );
// To allow sorting and filtering by multiple values, we have no choice but to store child prices in this manner.
foreach ( $prices as $price ) {
add_post_meta( $product->get_id(), '_price', $price, false );
}
}
}
}

View File

@ -0,0 +1,358 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Variable Product Data Store: Stored in CPT.
*
* @version 2.7.0
* @category Class
* @author WooThemes
*/
class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT implements WC_Object_Data_Store, WC_Product_Variable_Data_Store_Interface {
/**
* Cached & hashed prices array for child variations.
*
* @var array
*/
private $prices_array = array();
/**
* Read product data.
*
* @since 2.7.0
*/
protected function read_product_data( &$product ) {
parent::read_product_data( $product );
$this->read_children( $product );
// Set directly since individual data needs changed at the WC_Product_Variation level -- these datasets just pull.
$this->read_price_data( $product );
$this->read_price_data( $product, true );
$this->read_variation_attributes( $product );
}
/**
* Loads variation child IDs.
* @param WC_Product
* @param bool $force_read True to bypass the transient.
* @return WC_Product
*/
public function read_children( &$product, $force_read = false ) {
$children_transient_name = 'wc_product_children_' . $product->get_id();
$children = get_transient( $children_transient_name );
if ( empty( $children ) || ! is_array( $children ) || ! isset( $children['all'] ) || ! isset( $children['visible'] ) || $force_read ) {
$all_args = $visible_only_args = array(
'post_parent' => $product->get_id(),
'post_type' => 'product_variation',
'orderby' => 'menu_order',
'order' => 'ASC',
'fields' => 'ids',
'post_status' => 'publish',
'numberposts' => -1,
);
if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
$visible_only_args['meta_query'][] = array(
'key' => '_stock_status',
'value' => 'instock',
'compare' => '=',
);
}
$children['all'] = get_posts( apply_filters( 'woocommerce_variable_children_args', $all_args, $product, false ) );
$children['visible'] = get_posts( apply_filters( 'woocommerce_variable_children_args', $visible_only_args, $product, true ) );
set_transient( $children_transient_name, $children, DAY_IN_SECONDS * 30 );
}
$product->set_children( wp_parse_id_list( (array) $children['all'] ) );
$product->set_visible_children( wp_parse_id_list( (array) $children['visible'] ) );
}
/**
* Loads an array of attributes used for variations, as well as their possible values.
*
* @param WC_Product
*/
private function read_variation_attributes( &$product ) {
global $wpdb;
$variation_attributes = array();
$attributes = $product->get_attributes();
$child_ids = $product->get_children();
if ( ! empty( $child_ids ) && ! empty( $attributes ) ) {
foreach ( $attributes as $attribute ) {
if ( empty( $attribute['is_variation'] ) ) {
continue;
}
// Get possible values for this attribute, for only visible variations.
$values = array_unique( $wpdb->get_col( $wpdb->prepare(
"SELECT meta_value FROM {$wpdb->postmeta} WHERE meta_key = %s AND post_id IN (" . implode( ',', array_map( 'esc_sql', $child_ids ) ) . ")",
wc_variation_attribute_name( $attribute['name'] )
) ) );
// Empty value indicates that all options for given attribute are available.
if ( in_array( '', $values ) || empty( $values ) ) {
$values = $attribute['is_taxonomy'] ? wp_get_post_terms( $product->get_id(), $attribute['name'], array( 'fields' => 'slugs' ) ) : wc_get_text_attributes( $attribute['value'] );
// Get custom attributes (non taxonomy) as defined.
} elseif ( ! $attribute['is_taxonomy'] ) {
$text_attributes = wc_get_text_attributes( $attribute['value'] );
$assigned_text_attributes = $values;
$values = array();
// Pre 2.4 handling where 'slugs' were saved instead of the full text attribute
if ( version_compare( get_post_meta( $product->get_id(), '_product_version', true ), '2.4.0', '<' ) ) {
$assigned_text_attributes = array_map( 'sanitize_title', $assigned_text_attributes );
foreach ( $text_attributes as $text_attribute ) {
if ( in_array( sanitize_title( $text_attribute ), $assigned_text_attributes ) ) {
$values[] = $text_attribute;
}
}
} else {
foreach ( $text_attributes as $text_attribute ) {
if ( in_array( $text_attribute, $assigned_text_attributes ) ) {
$values[] = $text_attribute;
}
}
}
}
$variation_attributes[ $attribute['name'] ] = array_unique( $values );
}
}
$product->set_variation_attributes( $variation_attributes );
}
/**
* Get an array of all sale and regular prices from all variations. This is used for example when displaying the price range at variable product level or seeing if the variable product is on sale.
*
* Can be filtered by plugins which modify costs, but otherwise will include the raw meta costs unlike get_price() which runs costs through the woocommerce_get_price filter.
* This is to ensure modified prices are not cached, unless intended.
*
* @since 2.7.0
* @param WC_Product
* @param bool $include_taxes If taxes should be calculated or not.
*/
private function read_price_data( &$product, $include_taxes = false ) {
global $wp_filter;
/**
* Transient name for storing prices for this product (note: Max transient length is 45)
* @since 2.5.0 a single transient is used per product for all prices, rather than many transients per product.
*/
$transient_name = 'wc_var_prices_' . $product->get_id();
/**
* Create unique cache key based on the tax location (affects displayed/cached prices), product version and active price filters.
* DEVELOPERS should filter this hash if offering conditonal pricing to keep it unique.
* @var string
*/
$price_hash = $include_taxes ? array( get_option( 'woocommerce_tax_display_shop', 'excl' ), WC_Tax::get_rates() ) : array( false );
$filter_names = array( 'woocommerce_variation_prices_price', 'woocommerce_variation_prices_regular_price', 'woocommerce_variation_prices_sale_price' );
foreach ( $filter_names as $filter_name ) {
if ( ! empty( $wp_filter[ $filter_name ] ) ) {
$price_hash[ $filter_name ] = array();
foreach ( $wp_filter[ $filter_name ] as $priority => $callbacks ) {
$price_hash[ $filter_name ][] = array_values( wp_list_pluck( $callbacks, 'function' ) );
}
}
}
$price_hash[] = WC_Cache_Helper::get_transient_version( 'product' );
$price_hash = md5( json_encode( apply_filters( 'woocommerce_get_variation_prices_hash', $price_hash, $product, $include_taxes ) ) );
/**
* $this->prices_array is an array of values which may have been modified from what is stored in transients - this may not match $transient_cached_prices_array.
* If the value has already been generated, we don't need to grab the values again so just return them. They are already filtered.
*/
if ( ! empty( $this->prices_array[ $price_hash ] ) ) {
if ( $include_taxes ) {
$product->set_variation_prices_including_taxes( $this->prices_array[ $price_hash ] );
} else {
$product->set_variation_prices( $this->prices_array[ $price_hash ] );
}
/**
* No locally cached value? Get the data from the transient or generate it.
*/
} else {
// Get value of transient
$transient_cached_prices_array = array_filter( (array) json_decode( strval( get_transient( $transient_name ) ), true ) );
// If the product version has changed since the transient was last saved, reset the transient cache.
if ( empty( $transient_cached_prices_array['version'] ) || WC_Cache_Helper::get_transient_version( 'product' ) !== $transient_cached_prices_array['version'] ) {
$transient_cached_prices_array = array( 'version' => WC_Cache_Helper::get_transient_version( 'product' ) );
}
// If the prices are not stored for this hash, generate them and add to the transient.
if ( empty( $transient_cached_prices_array[ $price_hash ] ) ) {
$prices = array();
$regular_prices = array();
$sale_prices = array();
$variation_ids = $product->get_visible_children();
foreach ( $variation_ids as $variation_id ) {
if ( $variation = wc_get_product( $variation_id ) ) {
$price = apply_filters( 'woocommerce_variation_prices_price', $variation->get_price(), $variation, $product );
$regular_price = apply_filters( 'woocommerce_variation_prices_regular_price', $variation->get_regular_price(), $variation, $product );
$sale_price = apply_filters( 'woocommerce_variation_prices_sale_price', $variation->get_sale_price(), $variation, $product );
// Skip empty prices
if ( '' === $price ) {
continue;
}
// If sale price does not equal price, the product is not yet on sale
if ( $sale_price === $regular_price || $sale_price !== $price ) {
$sale_price = $regular_price;
}
// If we are getting prices for display, we need to account for taxes
if ( $include_taxes ) {
if ( 'incl' === get_option( 'woocommerce_tax_display_shop' ) ) {
$price = '' === $price ? '' : wc_get_price_including_tax( $variation, array( 'qty' => 1, 'price' => $price ) );
$regular_price = '' === $regular_price ? '' : wc_get_price_including_tax( $variation, array( 'qty' => 1, 'price' => $regular_price ) );
$sale_price = '' === $sale_price ? '' : wc_get_price_including_tax( $variation, array( 'qty' => 1, 'price' => $sale_price ) );
} else {
$price = '' === $price ? '' : wc_get_price_excluding_tax( $variation, array( 'qty' => 1, 'price' => $price ) );
$regular_price = '' === $regular_price ? '' : wc_get_price_excluding_tax( $variation, array( 'qty' => 1, 'price' => $regular_price ) );
$sale_price = '' === $sale_price ? '' : wc_get_price_excluding_tax( $variation, array( 'qty' => 1, 'price' => $sale_price ) );
}
}
$prices[ $variation_id ] = wc_format_decimal( $price, wc_get_price_decimals() );
$regular_prices[ $variation_id ] = wc_format_decimal( $regular_price, wc_get_price_decimals() );
$sale_prices[ $variation_id ] = wc_format_decimal( $sale_price . '.00', wc_get_price_decimals() );
}
}
asort( $prices );
asort( $regular_prices );
asort( $sale_prices );
$transient_cached_prices_array[ $price_hash ] = array(
'price' => $prices,
'regular_price' => $regular_prices,
'sale_price' => $sale_prices,
);
set_transient( $transient_name, json_encode( $transient_cached_prices_array ), DAY_IN_SECONDS * 30 );
}
/**
* Give plugins one last chance to filter the variation prices array which has been generated and store locally to the class.
* This value may differ from the transient cache. It is filtered once before storing locally.
*/
$this->prices_array[ $price_hash ] = apply_filters( 'woocommerce_variation_prices', $transient_cached_prices_array[ $price_hash ], $product, $include_taxes );
if ( $include_taxes ) {
$product->set_variation_prices_including_taxes( $this->prices_array[ $price_hash ] );
} else {
$product->set_variation_prices( $this->prices_array[ $price_hash ] );
}
}
}
/**
* Does a child have a weight set?
*
* @since 2.7.0
* @param WC_Product
* @return boolean
*/
public function child_has_weight( $product ) {
global $wpdb;
$children = $product->get_visible_children( 'edit' );
return $children ? $wpdb->get_var( "SELECT 1 FROM $wpdb->postmeta WHERE meta_key = '_weight' AND meta_value > 0 AND post_id IN ( " . implode( ',', array_map( 'absint', $children ) ) . " )" ) : false;
}
/**
* Does a child have dimensions set?
*
* @since 2.7.0
* @param WC_Product
* @return boolean
*/
public function child_has_dimensions( $product ) {
global $wpdb;
$children = $product->get_visible_children( 'edit' );
return $children ? $wpdb->get_var( "SELECT 1 FROM $wpdb->postmeta WHERE meta_key IN ( '_length', '_width', '_height' ) AND post_id IN ( " . implode( ',', array_map( 'absint', $children ) ) . " )" ) : false;
}
/**
* Is a child in stock?
*
* @since 2.7.0
* @param WC_Product
* @return boolean
*/
public function child_is_in_stock( $product ) {
global $wpdb;
$children = $product->get_visible_children( 'edit' );
return $children ? $wpdb->get_var( "SELECT 1 FROM $wpdb->postmeta WHERE meta_key = '_stock_status' AND meta_value = 'instock' AND post_id IN ( " . implode( ',', array_map( 'absint', $children ) ) . " )" ) : false;
}
/**
* Stock managed at the parent level - update children being managed by this product.
* This sync function syncs downwards (from parent to child) when the variable product is saved.
*
* @param WC_Product
* @since 2.7.0
*/
public function sync_managed_variation_stock_status( &$product ) {
global $wpdb;
if ( $product->get_manage_stock() ) {
$status = $product->get_stock_status();
$children = $product->get_children();
$managed_children = $children ? array_unique( $wpdb->get_col( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_manage_stock' AND meta_value != 'yes' AND post_id IN ( " . implode( ',', array_map( 'absint', $children ) ) . " )" ) ) : array();
$changed = false;
foreach ( $managed_children as $managed_child ) {
if ( update_post_meta( $managed_child, '_stock_status', $status ) ) {
$changed = true;
}
}
if ( $changed ) {
$this->read_children( $product, true );
}
}
}
/**
* Sync variable product prices with children.
*
* @since 2.7.0
* @param WC_Product|int $product
*/
public function sync_price( &$product ) {
global $wpdb;
$children = $product->get_visible_children( 'edit' );
$prices = $children ? array_unique( $wpdb->get_col( "SELECT meta_value FROM $wpdb->postmeta WHERE meta_key = '_price' AND post_id IN ( " . implode( ',', array_map( 'absint', $children ) ) . " )" ) ) : array();
delete_post_meta( $product->get_id(), '_price' );
if ( $prices ) {
sort( $prices );
// To allow sorting and filtering by multiple values, we have no choice but to store child prices in this manner.
foreach ( $prices as $price ) {
add_post_meta( $product->get_id(), '_price', $price, false );
}
}
}
/**
* Sync variable product stock status with children.
* Change does not persist unless saved by caller.
*
* @since 2.7.0
* @param WC_Product|int $product
*/
public function sync_stock_status( &$product ) {
$product->set_stock_status( $product->child_is_in_stock() ? 'instock' : 'outofstock' );
}
}

View File

@ -0,0 +1,247 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Variation Product Data Store: Stored in CPT.
*
* @version 2.7.0
* @category Class
* @author WooThemes
*/
class WC_Product_Variation_Data_Store_CPT extends WC_Product_Data_Store_CPT implements WC_Object_Data_Store {
/*
|--------------------------------------------------------------------------
| CRUD Methods
|--------------------------------------------------------------------------
*/
/**
* Reads a product from the database and sets its data to the class.
*
* @since 2.7.0
* @param WC_Product
*/
public function read( &$product ) {
$product->set_defaults();
if ( ! $product->get_id() || ! ( $post_object = get_post( $product->get_id() ) ) ) {
return;
}
$id = $product->get_id();
$product->set_parent_id( $post_object->post_parent );
$parent_id = $product->get_parent_id();
// The post doesn't have a parent id, therefore its invalid and we should prevent this being created.
if ( empty( $parent_id ) ) {
throw new Exception( sprintf( 'No parent product set for variation #%d', $product->get_id() ), 422 );
}
// The post parent is not a valid variable product so we should prevent this being created.
if ( 'product' !== get_post_type( $product->get_parent_id() ) ) {
throw new Exception( sprintf( 'Invalid parent for variation #%d', $product->get_id() ), 422 );
}
$product->set_props( array(
'name' => get_the_title( $post_object ),
'slug' => $post_object->post_name,
'date_created' => $post_object->post_date,
'date_modified' => $post_object->post_modified,
'status' => $post_object->post_status,
'menu_order' => $post_object->menu_order,
'reviews_allowed' => 'open' === $post_object->comment_status,
) );
$this->read_product_data( $product );
$product->read_meta_data();
$product->set_attributes( wc_get_product_variation_attributes( $product->get_id() ) );
// Set object_read true once all data is read.
$product->set_object_read( true );
}
/**
* Create a new product.
*
* @since 2.7.0
* @param WC_Product
*/
public function create( &$product ) {
$product->set_date_created( current_time( 'timestamp' ) );
$id = wp_insert_post( apply_filters( 'woocommerce_new_product_variation_data', array(
'post_type' => 'product_variation',
'post_status' => $product->get_status() ? $product->get_status() : 'publish',
'post_author' => get_current_user_id(),
'post_title' => get_the_title( $product->get_parent_id() ) . ' &ndash;' . wc_get_formatted_variation( $product->get_attributes(), true ),
'post_content' => '',
'post_parent' => $product->get_parent_id(),
'comment_status' => 'closed',
'ping_status' => 'closed',
'menu_order' => $product->get_menu_order(),
'post_date' => date( 'Y-m-d H:i:s', $product->get_date_created() ),
'post_date_gmt' => get_gmt_from_date( date( 'Y-m-d H:i:s', $product->get_date_created() ) ),
) ), true );
if ( $id && ! is_wp_error( $id ) ) {
$product->set_id( $id );
$this->update_post_meta( $product );
$this->update_terms( $product );
$this->update_attributes( $product );
$product->save_meta_data();
do_action( 'woocommerce_create_product_variation', $id );
$product->apply_changes();
$this->update_version_and_type( $product );
$this->update_term_counts( $product );
$this->clear_caches( $product );
}
}
/**
* Updates an existing product.
*
* @since 2.7.0
* @param WC_Product
*/
public function update( &$product ) {
$post_data = array(
'ID' => $product->get_id(),
'post_title' => get_the_title( $product->get_parent_id() ) . ' &ndash;' . wc_get_formatted_variation( $product->get_attributes(), true ),
'post_parent' => $product->get_parent_id(),
'comment_status' => 'closed',
'post_status' => $product->get_status() ? $product->get_status() : 'publish',
'menu_order' => $product->get_menu_order(),
);
wp_update_post( $post_data );
$this->update_post_meta( $product );
$this->update_terms( $product );
$this->update_attributes( $product );
$product->save_meta_data();
do_action( 'woocommerce_update_product_variation', $product->get_id() );
$product->apply_changes();
$this->update_version_and_type( $product );
$this->update_term_counts( $product );
$this->clear_caches( $product );
}
/*
|--------------------------------------------------------------------------
| Additional Methods
|--------------------------------------------------------------------------
*/
/**
* Make sure we store the product version (to track data changes).
*
* @param WC_Product
* @since 2.7.0
*/
protected function update_version_and_type( &$product ) {
update_post_meta( $product->get_id(), '_product_version', WC_VERSION );
}
/**
* Read post data.
*
* @since 2.7.0
* @param WC_Product
*/
protected function read_product_data( &$product ) {
$id = $product->get_id();
$product->set_props( array(
'description' => get_post_meta( $id, '_variation_description', true ),
'regular_price' => get_post_meta( $id, '_regular_price', true ),
'sale_price' => get_post_meta( $id, '_sale_price', true ),
'date_on_sale_from' => get_post_meta( $id, '_sale_price_dates_from', true ),
'date_on_sale_to' => get_post_meta( $id, '_sale_price_dates_to', true ),
'tax_status' => get_post_meta( $id, '_tax_status', true ),
'manage_stock' => get_post_meta( $id, '_manage_stock', true ),
'stock_status' => get_post_meta( $id, '_stock_status', true ),
'shipping_class_id' => current( $this->get_term_ids( $product, 'product_shipping_class' ) ),
'virtual' => get_post_meta( $id, '_virtual', true ),
'downloadable' => get_post_meta( $id, '_downloadable', true ),
'downloads' => array_filter( (array) get_post_meta( $id, '_downloadable_files', true ) ),
'gallery_image_ids' => array_filter( explode( ',', get_post_meta( $id, '_product_image_gallery', true ) ) ),
'download_limit' => get_post_meta( $id, '_download_limit', true ),
'download_expiry' => get_post_meta( $id, '_download_expiry', true ),
'image_id' => get_post_thumbnail_id( $id ),
'backorders' => get_post_meta( $id, '_backorders', true ),
'sku' => get_post_meta( $id, '_sku', true ),
'stock_quantity' => get_post_meta( $id, '_stock', true ),
'weight' => get_post_meta( $id, '_weight', true ),
'length' => get_post_meta( $id, '_length', true ),
'width' => get_post_meta( $id, '_width', true ),
'height' => get_post_meta( $id, '_height', true ),
'tax_class' => get_post_meta( $id, '_tax_class', true ),
) );
if ( $product->is_on_sale() ) {
$product->set_price( $product->get_sale_price() );
} else {
$product->set_price( $product->get_regular_price() );
}
$product->set_parent_data( array(
'sku' => get_post_meta( $product->get_parent_id(), '_sku', true ),
'manage_stock' => get_post_meta( $product->get_parent_id(), '_manage_stock', true ),
'backorders' => get_post_meta( $product->get_parent_id(), '_backorders', true ),
'stock_quantity' => get_post_meta( $product->get_parent_id(), '_stock', true ),
'weight' => get_post_meta( $product->get_parent_id(), '_weight', true ),
'length' => get_post_meta( $product->get_parent_id(), '_length', true ),
'width' => get_post_meta( $product->get_parent_id(), '_width', true ),
'height' => get_post_meta( $product->get_parent_id(), '_height', true ),
'tax_class' => get_post_meta( $product->get_parent_id(), '_tax_class', true ),
'image_id' => get_post_thumbnail_id( $product->get_parent_id() ),
) );
}
/**
* For all stored terms in all taxonomies, save them to the DB.
*
* @since 2.7.0
* @param WC_Product
*/
protected function update_terms( &$product ) {
wp_set_post_terms( $product->get_id(), array( $product->get_shipping_class_id( 'edit' ) ), 'product_shipping_class', false );
}
/**
* Update attribute meta values.
*
* @since 2.7.0
* @param WC_Product
*/
protected function update_attributes( &$product ) {
global $wpdb;
$attributes = $product->get_attributes();
$updated_attribute_keys = array();
foreach ( $attributes as $key => $value ) {
update_post_meta( $product->get_id(), 'attribute_' . $key, $value );
$updated_attribute_keys[] = 'attribute_' . $key;
}
// Remove old taxonomies attributes so data is kept up to date - first get attribute key names.
$delete_attribute_keys = $wpdb->get_col( $wpdb->prepare( "SELECT meta_key FROM {$wpdb->postmeta} WHERE meta_key LIKE 'attribute_%%' AND meta_key NOT IN ( '" . implode( "','", array_map( 'esc_sql', $updated_attribute_keys ) ) . "' ) AND post_id = %d;", $product->get_id() ) );
foreach ( $delete_attribute_keys as $key ) {
delete_post_meta( $product->get_id(), $key );
}
}
/**
* Helper method that updates all the post meta for a product based on it's settings in the WC_Product class.
*
* @since 2.7.0
* @param WC_Product
*/
public function update_post_meta( &$product ) {
update_post_meta( $product->get_id(), '_variation_description', $product->get_description() );
parent::update_post_meta( $product );
}
}

View File

@ -0,0 +1,96 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Product Data Store Interface
*
* Functions that must be defined by product store classes.
*
* @version 2.7.0
* @category Interface
* @author WooThemes
*/
interface WC_Product_Data_Store_Interface {
/**
* Returns an array of on sale products, as an array of objects with an
* ID and parent_id present. Example: $return[0]->id, $return[0]->parent_id.
*
* @return array
*/
public function get_on_sale_products();
/**
* Returns a list of product IDs ( id as key => parent as value) that are
* featured. Uses get_posts instead of wc_get_products since we want
* some extra meta queries and ALL products (posts_per_page = -1).
*
* @return array
*/
public function get_featured_product_ids();
/**
* Check if product sku is found for any other product IDs.
*
* @param int $product_id
* @param string $sku
* @return bool
*/
public function is_existing_sku( $product_id, $sku );
/**
* Return product ID based on SKU.
*
* @param string $sku
* @return int
*/
public function get_product_id_by_sku( $sku );
/**
* Returns an array of IDs of products that have sales starting soon.
*
* @return array
*/
public function get_starting_sales();
/**
* Returns an array of IDs of products that have sales which are due to end.
*
* @return array
*/
public function get_ending_sales();
/**
* Find a matching (enabled) variation within a variable product.
*
* @param WC_Product $product Variable product.
* @param array $match_attributes Array of attributes we want to try to match.
* @return int Matching variation ID or 0.
*/
public function find_matching_product_variation( $product, $match_attributes = array() );
/**
* Return a list of related products (using data like categories and IDs).
*
* @param array $cats_array List of categories IDs.
* @param array $tags_array List of tags IDs.
* @param array $exclude_ids Excluded IDs.
* @param int $limit Limit of results.
* @param int $product_id
* @return array
*/
public function get_related_products( $cats_array, $tags_array, $exclude_ids, $limit, $product_id );
/**
* Update a product's stock amount directly.
*
* Uses queries rather than update_post_meta so we can do this in one query (to avoid stock issues).
*
* @param int
* @param int|null $stock_quantity
* @param string $operation set, increase and decrease.
*/
function update_product_stock( $product_id_with_stock, $stock_quantity = null, $operation = 'set' );
}

View File

@ -0,0 +1,54 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Product Variable Data Store Interface
*
* Functions that must be defined by product variable store classes.
*
* @version 2.7.0
* @category Interface
* @author WooThemes
*/
interface WC_Product_Variable_Data_Store_Interface {
/**
* Does a child have a weight set?
*
* @param WC_Product
* @return boolean
*/
public function child_has_weight( $product );
/**
* Does a child have dimensions set?
*
* @param WC_Product
* @return boolean
*/
public function child_has_dimensions( $product );
/**
* Is a child in stock?
*
* @param WC_Product
* @return boolean
*/
public function child_is_in_stock( $product );
/**
* Stock managed at the parent level - update children being managed by this product.
* This sync function syncs downwards (from parent to child) when the variable product is saved.
*
* @param WC_Product
*/
public function sync_managed_variation_stock_status( &$product );
/**
* Sync variable product prices with children.
*
* @param WC_Product|int $product
*/
public function sync_price( &$product );
}

View File

@ -44,7 +44,6 @@ abstract class WC_Legacy_Customer extends WC_Data {
/** /**
* __get function. * __get function.
* @todo use get_* methods
* @param string $key * @param string $key
* @return string * @return string
*/ */
@ -59,7 +58,6 @@ abstract class WC_Legacy_Customer extends WC_Data {
/** /**
* __set function. * __set function.
* @todo use set_* methods
* @param mixed $property * @param mixed $property
* @param mixed $key * @param mixed $key
*/ */
@ -164,7 +162,6 @@ abstract class WC_Legacy_Customer extends WC_Data {
/** /**
* Is the user a paying customer? * Is the user a paying customer?
* @todo should this be moved to a get_ readonly?
* @return bool * @return bool
*/ */
function is_paying_customer( $user_id = '' ) { function is_paying_customer( $user_id = '' ) {

View File

@ -32,6 +32,16 @@ function wc_get_text_attributes_filter_callback( $value ) {
return '' !== $value; return '' !== $value;
} }
/**
* Implode an array of attributes using WC_DELIMITER.
* @since 2.7.0
* @param array $attributes
* @return string
*/
function wc_implode_text_attributes( $attributes ) {
return implode( ' ' . WC_DELIMITER . ' ', $attributes );
}
/** /**
* Get attribute taxonomies. * Get attribute taxonomies.
* *
@ -269,3 +279,14 @@ function wc_check_if_attribute_name_is_reserved( $attribute_name ) {
return in_array( $attribute_name, $reserved_terms ); return in_array( $attribute_name, $reserved_terms );
} }
/**
* Callback for array filter to get visible only.
*
* @since 2.7.0
* @param WC_Product $product
* @return bool
*/
function wc_attributes_array_filter_visible( $attribute ) {
return $attribute && is_a( $attribute, 'WC_Product_Attribute' ) && $attribute->get_visible() && ( ! $attribute->is_taxonomy() || taxonomy_exists( $attribute->get_name() ) );
}

View File

@ -205,7 +205,7 @@ function wc_cart_totals_shipping_html() {
if ( sizeof( $packages ) > 1 ) { if ( sizeof( $packages ) > 1 ) {
foreach ( $package['contents'] as $item_id => $values ) { foreach ( $package['contents'] as $item_id => $values ) {
$product_names[] = $values['data']->get_title() . ' &times;' . $values['quantity']; $product_names[] = $values['data']->get_name() . ' &times;' . $values['quantity'];
} }
} }

View File

@ -23,6 +23,7 @@ include( 'wc-formatting-functions.php' );
include( 'wc-order-functions.php' ); include( 'wc-order-functions.php' );
include( 'wc-page-functions.php' ); include( 'wc-page-functions.php' );
include( 'wc-product-functions.php' ); include( 'wc-product-functions.php' );
include( 'wc-stock-functions.php' );
include( 'wc-account-functions.php' ); include( 'wc-account-functions.php' );
include( 'wc-term-functions.php' ); include( 'wc-term-functions.php' );
include( 'wc-attribute-functions.php' ); include( 'wc-attribute-functions.php' );
@ -920,7 +921,6 @@ function wc_format_country_state_string( $country_string ) {
/** /**
* Get the store's base location. * Get the store's base location.
* *
* @todo should the woocommerce_default_country option be renamed to contain 'base'?
* @since 2.3.0 * @since 2.3.0
* @return array * @return array
*/ */
@ -936,8 +936,6 @@ function wc_get_base_location() {
* Filtered, and set to base location or left blank. If cache-busting, * Filtered, and set to base location or left blank. If cache-busting,
* this should only be used when 'location' is set in the querystring. * this should only be used when 'location' is set in the querystring.
* *
* @todo should the woocommerce_default_country option be renamed to contain 'base'?
* @todo deprecate woocommerce_customer_default_location and support an array filter only to cover all cases.
* @since 2.3.0 * @since 2.3.0
* @return array * @return array
*/ */
@ -1363,7 +1361,7 @@ function wc_set_time_limit( $limit = 0 ) {
* @since 2.6.0 * @since 2.6.0
*/ */
function wc_product_attribute_uasort_comparison( $a, $b ) { function wc_product_attribute_uasort_comparison( $a, $b ) {
if ( $a['position'] == $b['position'] ) { if ( $a['position'] === $b['position'] ) {
return 0; return 0;
} }
return ( $a['position'] < $b['position'] ) ? -1 : 1; return ( $a['position'] < $b['position'] ) ? -1 : 1;
@ -1409,21 +1407,6 @@ function wc_get_logger() {
return new $class; return new $class;
} }
/**
* Runs a deprecated action with notice only if used.
* @since 2.7.0
* @param string $action
* @param array $args
* @param string $deprecated_in
* @param string $replacement
*/
function wc_do_deprecated_action( $action, $args, $deprecated_in, $replacement ) {
if ( has_action( $action ) ) {
_deprecated_function( 'Action: ' . $action, $deprecated_in, $replacement );
do_action_ref_array( $action, $args );
}
}
/** /**
* Store user agents. Used for tracker. * Store user agents. Used for tracker.
* @since 2.7.0 * @since 2.7.0
@ -1436,3 +1419,45 @@ function wc_maybe_store_user_agent( $user_login, $user ) {
} }
} }
add_action( 'wp_login', 'wc_maybe_store_user_agent', 10, 2 ); add_action( 'wp_login', 'wc_maybe_store_user_agent', 10, 2 );
/**
* Based on wp_list_pluck, this calls a method instead of returning a property.
*
* @since 2.7.0
* @param array $list List of objects or arrays
* @param int|string $callback_or_field Callback method from the object to place instead of the entire object
* @param int|string $index_key Optional. Field from the object to use as keys for the new array.
* Default null.
* @return array Array of values.
*/
function wc_list_pluck( $list, $callback_or_field, $index_key = null ) {
// Use wp_list_pluck if this isn't a callback
$first_el = current( $list );
if ( ! is_object( $first_el ) || ! is_callable( array( $first_el, $callback_or_field ) ) ) {
return wp_list_pluck( $list, $callback_or_field, $index_key );
}
if ( ! $index_key ) {
/*
* This is simple. Could at some point wrap array_column()
* if we knew we had an array of arrays.
*/
foreach ( $list as $key => $value ) {
$list[ $key ] = $value->{$callback_or_field}();
}
return $list;
}
/*
* When index_key is not set for a particular item, push the value
* to the end of the stack. This is how array_column() behaves.
*/
$newlist = array();
foreach ( $list as $value ) {
if ( isset( $value->$index_key ) ) {
$newlist[ $value->$index_key ] = $value->{$callback_or_field}();
} else {
$newlist[] = $value->{$callback_or_field}();
}
}
return $newlist;
}

View File

@ -11,7 +11,55 @@
*/ */
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly exit;
}
/**
* Runs a deprecated action with notice only if used.
*
* @since 2.7.0
* @param string $action
* @param array $args
* @param string $deprecated_in
* @param string $replacement
*/
function wc_do_deprecated_action( $action, $args, $deprecated_in, $replacement ) {
if ( has_action( $action ) ) {
_deprecated_function( 'Action: ' . $action, $deprecated_in, $replacement );
do_action_ref_array( $action, $args );
}
}
/**
* Soft deprecate a function so that it's technically deprecated, but not shown
* until a future version to ease transition for developers.
*
* @since 2.7.0
* @param string $function
* @param string $version
* @param string $deprecate_in_version
* @param string $replacement
*/
function wc_soft_deprecated_function( $function, $version, $deprecate_in_version, $replacement = null ) {
if ( version_compare( WC_VERSION, $deprecate_in_version, '>=' ) ) {
_deprecated_function( $function, $version, $replacement );
}
}
/**
* Soft deprecate an argument so that it's technically deprecated, but not shown
* until a future version to ease transition for developers.
*
* @since 2.7.0
* @param string $argument
* @param string $version
* @param string $deprecate_in_version
* @param string $replacement
*/
function wc_soft_deprecated_argument( $argument, $version, $deprecate_in_version, $message = null ) {
if ( version_compare( WC_VERSION, $deprecate_in_version, '>=' ) ) {
_deprecated_argument( $argument, $version, $message );
}
} }
/** /**
@ -508,9 +556,23 @@ function woocommerce_list_pages( $pages ) {
global $wc_map_deprecated_filters; global $wc_map_deprecated_filters;
$wc_map_deprecated_filters = array( $wc_map_deprecated_filters = array(
'woocommerce_add_to_cart_fragments' => 'add_to_cart_fragments', 'woocommerce_structured_data_order' => 'woocommerce_email_order_schema_markup',
'woocommerce_add_to_cart_redirect' => 'add_to_cart_redirect', 'woocommerce_add_to_cart_fragments' => 'add_to_cart_fragments',
'woocommerce_structured_data_order' => 'woocommerce_email_order_schema_markup', 'woocommerce_add_to_cart_redirect' => 'add_to_cart_redirect',
'woocommerce_product_get_width' => 'woocommerce_product_width',
'woocommerce_product_get_height' => 'woocommerce_product_height',
'woocommerce_product_get_length' => 'woocommerce_product_length',
'woocommerce_product_get_weight' => 'woocommerce_product_weight',
'woocommerce_product_get_sku' => 'woocommerce_get_sku',
'woocommerce_product_get_price' => 'woocommerce_get_price',
'woocommerce_product_get_regular_price' => 'woocommerce_get_regular_price',
'woocommerce_product_get_sale_price' => 'woocommerce_get_sale_price',
'woocommerce_product_get_tax_class' => 'woocommerce_product_tax_class',
'woocommerce_product_get_stock_quantity' => 'woocommerce_get_stock_quantity',
'woocommerce_product_get_attributes' => 'woocommerce_get_product_attributes',
'woocommerce_product_get_gallery_image_ids' => 'woocommerce_product_gallery_attachment_ids',
'woocommerce_product_get_review_count' => 'woocommerce_product_review_count',
'woocommerce_product_get_downloads' => 'woocommerce_product_files',
); );
foreach ( $wc_map_deprecated_filters as $new => $old ) { foreach ( $wc_map_deprecated_filters as $new => $old ) {
@ -667,56 +729,6 @@ function woocommerce_prepare_attachment_for_js( $response ) {
function woocommerce_track_product_view() { function woocommerce_track_product_view() {
return wc_track_product_view(); return wc_track_product_view();
} }
/**
* Shop order status.
*
* @since 2.2
* @param WP_Query $q
*/
function wc_shop_order_status_backwards_compatibility( $q ) {
if ( $q->is_main_query() ) {
return;
}
if (
isset( $q->query_vars['post_type'] ) && 'shop_order' == $q->query_vars['post_type']
&& isset( $q->query_vars['post_status'] ) && 'publish' == $q->query_vars['post_status']
) {
$tax_query = isset( $q->query_vars['tax_query'] ) ? $q->query_vars['tax_query'] : array();
$order_status = array();
$tax_key = '';
// Look for shop_order_status taxonomy and get the terms
foreach ( $tax_query as $key => $tax ) {
if ( 'shop_order_status' == $tax['taxonomy'] ) {
$tax_key = $key;
$order_status = $tax['terms'];
break;
}
}
if ( $order_status ) {
// Remove old tax_query
unset( $tax_query[ $tax_key ] );
// Set the new order status
$order_status = is_array( $order_status ) ? 'wc-' . implode( ',wc-', $order_status ) : 'wc-' . $order_status;
$q->set( 'post_status', $order_status );
$q->set( 'tax_query', $tax_query );
_doing_it_wrong( 'WP_Query', sprintf( __( 'The shop_order_status taxonomy is no more in WooCommerce 2.2! You should use the new WooCommerce post_status instead, <a href="%s">read more...</a>', 'woocommerce' ), 'https://woocommerce.wordpress.com/2014/08/wc-2-2-order-statuses-plugin-compatibility/' ), 'WooCommerce 2.2' );
} else {
$q->set( 'post_status', array_keys( wc_get_order_statuses() ) );
_doing_it_wrong( 'WP_Query', sprintf( __( 'The "publish" order status is no more in WooCommerce 2.2! You should use the new WooCommerce post_status instead, <a href="%s">read more...</a>', 'woocommerce' ), 'https://woocommerce.wordpress.com/2014/08/wc-2-2-order-statuses-plugin-compatibility/' ), 'WooCommerce 2.2' );
}
}
}
add_action( 'pre_get_posts', 'wc_shop_order_status_backwards_compatibility' );
/** /**
* @since 2.3 * @since 2.3
* @deprecated has no replacement * @deprecated has no replacement
@ -770,3 +782,54 @@ function woocommerce_get_product_schema() {
return 'http://schema.org/' . $schema; return 'http://schema.org/' . $schema;
} }
/**
* Save product price.
*
* This is a private function (internal use ONLY) used until a data manipulation api is built.
*
* @deprecated 2.7.0
* @param int $product_id
* @param float $regular_price
* @param float $sale_price
* @param string $date_from
* @param string $date_to
*/
function _wc_save_product_price( $product_id, $regular_price, $sale_price = '', $date_from = '', $date_to = '' ) {
_doing_it_wrong( '_wc_save_product_price()', 'This function is not for developer use and is deprecated.', '2.7' );
$product_id = absint( $product_id );
$regular_price = wc_format_decimal( $regular_price );
$sale_price = '' === $sale_price ? '' : wc_format_decimal( $sale_price );
$date_from = wc_clean( $date_from );
$date_to = wc_clean( $date_to );
update_post_meta( $product_id, '_regular_price', $regular_price );
update_post_meta( $product_id, '_sale_price', $sale_price );
// Save Dates
update_post_meta( $product_id, '_sale_price_dates_from', $date_from ? strtotime( $date_from ) : '' );
update_post_meta( $product_id, '_sale_price_dates_to', $date_to ? strtotime( $date_to ) : '' );
if ( $date_to && ! $date_from ) {
$date_from = strtotime( 'NOW', current_time( 'timestamp' ) );
update_post_meta( $product_id, '_sale_price_dates_from', $date_from );
}
// Update price if on sale
if ( '' !== $sale_price && '' === $date_to && '' === $date_from ) {
update_post_meta( $product_id, '_price', $sale_price );
} else {
update_post_meta( $product_id, '_price', $regular_price );
}
if ( '' !== $sale_price && $date_from && strtotime( $date_from ) < strtotime( 'NOW', current_time( 'timestamp' ) ) ) {
update_post_meta( $product_id, '_price', $sale_price );
}
if ( $date_to && strtotime( $date_to ) < strtotime( 'NOW', current_time( 'timestamp' ) ) ) {
update_post_meta( $product_id, '_price', $regular_price );
update_post_meta( $product_id, '_sale_price_dates_from', '' );
update_post_meta( $product_id, '_sale_price_dates_to', '' );
}
}

View File

@ -335,6 +335,16 @@ function wc_clean( $var ) {
} }
} }
/**
* Run wc_clean over posted textarea but maintain line breaks.
* @since 2.7.0
* @param string $var
* @return string
*/
function wc_sanitize_textarea( $var ) {
return implode( "\n", array_map( 'wc_clean', explode( "\n", $var ) ) );
}
/** /**
* Sanitize a string destined to be a tooltip. * Sanitize a string destined to be a tooltip.
* *
@ -909,3 +919,92 @@ if ( ! function_exists( 'wc_make_numeric_postcode' ) ) {
return $numeric_postcode; return $numeric_postcode;
} }
} }
/**
* Format the stock amount ready for display based on settings.
* @since 2.7.0
* @param int $stock_amount
* @param boolean $show_backorder_notification
* @return string
*/
function wc_format_stock_for_display( $stock_amount, $show_backorder_notification = false ) {
$display = __( 'In stock', 'woocommerce' );
switch ( get_option( 'woocommerce_stock_format' ) ) {
case 'low_amount' :
if ( $stock_amount <= get_option( 'woocommerce_notify_low_stock_amount' ) ) {
$display = sprintf( __( 'Only %s left in stock', 'woocommerce' ), $stock_amount );
}
break;
case '' :
$display = sprintf( __( '%s in stock', 'woocommerce' ), $stock_amount );
break;
}
if ( $show_backorder_notification ) {
$display .= ' ' . __( '(can be backordered)', 'woocommerce' );
}
return $display;
}
/**
* Format a sale price for display.
* @since 2.7.0
* @param string $regular_price
* @param string $sale_price
* @return string
*/
function wc_format_sale_price( $regular_price, $sale_price ) {
$price = '<del>' . ( is_numeric( $regular_price ) ? wc_price( $regular_price ) : $regular_price ) . '</del> <ins>' . ( is_numeric( $sale_price ) ? wc_price( $sale_price ) : $sale_price ) . '</ins>';
return apply_filters( 'woocommerce_format_sale_price', $price, $regular_price, $sale_price );
}
/**
* Format a price range for display.
* @param string $from
* @param string $to
* @return string
*/
function wc_format_price_range( $from, $to ) {
$price = sprintf( _x( '%1$s &ndash; %2$s', 'Price range: from-to', 'woocommerce' ), is_numeric( $from ) ? wc_price( $from ) : $from, is_numeric( $to ) ? wc_price( $to ) : $to );
return apply_filters( 'woocommerce_format_price_range', $price, $from, $to );
}
/**
* Format a weight for display.
*
* @since 2.7.0
* @param float $weight Weight.
* @return string
*/
function wc_format_weight( $weight ) {
$weight_string = wc_format_localized_decimal( $weight );
if ( ! empty( $weight_string ) ) {
$weight_string .= ' ' . get_option( 'woocommerce_weight_unit' );
} else {
$weight_string = __( 'N/A', 'woocommerce' );
}
return apply_filters( 'woocommerce_format_weight', $weight_string, $weight );
}
/**
* Format dimensions for display.
*
* @since 2.7.0
* @param array $dimensions Array of dimensions.
* @return string
*/
function wc_format_dimensions( $dimensions ) {
$dimension_string = implode( ' x ', array_filter( array_map( 'wc_format_localized_decimal', $dimensions ) ) );
if ( ! empty( $dimension_string ) ) {
$dimension_string .= ' ' . get_option( 'woocommerce_dimension_unit' );
} else {
$dimension_string = __( 'N/A', 'woocommerce' );
}
return apply_filters( 'woocommerce_format_dimensions', $dimension_string, $dimensions );
}

View File

@ -497,7 +497,7 @@ function wc_downloadable_product_permissions( $order_id ) {
$_product = $item->get_product(); $_product = $item->get_product();
if ( $_product && $_product->exists() && $_product->is_downloadable() ) { if ( $_product && $_product->exists() && $_product->is_downloadable() ) {
$downloads = $_product->get_files(); $downloads = $_product->get_downloads();
foreach ( array_keys( $downloads ) as $download_id ) { foreach ( array_keys( $downloads ) as $download_id ) {
wc_downloadable_file_permission( $download_id, $item['variation_id'] > 0 ? $item['variation_id'] : $item['product_id'], $order, $item['qty'] ); wc_downloadable_file_permission( $download_id, $item['variation_id'] > 0 ? $item['variation_id'] : $item['product_id'], $order, $item['qty'] );
@ -674,44 +674,6 @@ function wc_get_order_item_meta( $item_id, $key, $single = true ) {
return get_metadata( 'order_item', $item_id, $key, $single ); return get_metadata( 'order_item', $item_id, $key, $single );
} }
/**
* Cancel all unpaid orders after held duration to prevent stock lock for those products.
*
* @access public
*/
function wc_cancel_unpaid_orders() {
global $wpdb;
$held_duration = get_option( 'woocommerce_hold_stock_minutes' );
if ( $held_duration < 1 || get_option( 'woocommerce_manage_stock' ) != 'yes' )
return;
$date = date( "Y-m-d H:i:s", strtotime( '-' . absint( $held_duration ) . ' MINUTES', current_time( 'timestamp' ) ) );
$unpaid_orders = $wpdb->get_col( $wpdb->prepare( "
SELECT posts.ID
FROM {$wpdb->posts} AS posts
WHERE posts.post_type IN ('" . implode( "','", wc_get_order_types() ) . "')
AND posts.post_status = 'wc-pending'
AND posts.post_modified < %s
", $date ) );
if ( $unpaid_orders ) {
foreach ( $unpaid_orders as $unpaid_order ) {
$order = wc_get_order( $unpaid_order );
if ( apply_filters( 'woocommerce_cancel_unpaid_order', 'checkout' === get_post_meta( $unpaid_order, '_created_via', true ), $order ) ) {
$order->update_status( 'cancelled', __( 'Unpaid order cancelled - time limit reached.', 'woocommerce' ) );
}
}
}
wp_clear_scheduled_hook( 'woocommerce_cancel_unpaid_orders' );
wp_schedule_single_event( time() + ( absint( $held_duration ) * 60 ), 'woocommerce_cancel_unpaid_orders' );
}
add_action( 'woocommerce_cancel_unpaid_orders', 'wc_cancel_unpaid_orders' );
/** /**
* Return the count of processing orders. * Return the count of processing orders.
* *
@ -1120,47 +1082,40 @@ add_action( 'woocommerce_order_status_on-hold', 'wc_update_coupon_usage_counts'
add_action( 'woocommerce_order_status_cancelled', 'wc_update_coupon_usage_counts' ); add_action( 'woocommerce_order_status_cancelled', 'wc_update_coupon_usage_counts' );
/** /**
* When a payment is complete, we can reduce stock levels for items within an order. * Cancel all unpaid orders after held duration to prevent stock lock for those products.
* @since 2.7.0 *
* @param int $order_id * @access public
*/ */
function wc_maybe_reduce_stock_levels( $order_id ) { function wc_cancel_unpaid_orders() {
if ( apply_filters( 'woocommerce_payment_complete_reduce_order_stock', ! get_post_meta( $order_id, '_order_stock_reduced', true ), $order_id ) ) { global $wpdb;
wc_reduce_stock_levels( $order_id );
add_post_meta( $order_id, '_order_stock_reduced', '1', true ); $held_duration = get_option( 'woocommerce_hold_stock_minutes' );
if ( $held_duration < 1 || 'yes' !== get_option( 'woocommerce_manage_stock' ) ) {
return;
} }
}
add_action( 'woocommerce_payment_complete', 'wc_maybe_reduce_stock_levels' );
/** $date = date( "Y-m-d H:i:s", strtotime( '-' . absint( $held_duration ) . ' MINUTES', current_time( 'timestamp' ) ) );
* Reduce stock levels for items within an order.
* @since 2.7.0
* @param int $order_id
*/
function wc_reduce_stock_levels( $order_id ) {
$order = wc_get_order( $order_id );
if ( 'yes' === get_option( 'woocommerce_manage_stock' ) && $order && apply_filters( 'woocommerce_can_reduce_order_stock', true, $order ) && sizeof( $order->get_items() ) > 0 ) { $unpaid_orders = $wpdb->get_col( $wpdb->prepare( "
foreach ( $order->get_items() as $item ) { SELECT posts.ID
if ( $item->is_type( 'line_item' ) && ( $product = $item->get_product() ) && $product->managing_stock() ) { FROM {$wpdb->posts} AS posts
$qty = apply_filters( 'woocommerce_order_item_quantity', $item->get_quantity(), $order, $item ); WHERE posts.post_type IN ('" . implode( "','", wc_get_order_types() ) . "')
$new_stock = $product->reduce_stock( $qty ); AND posts.post_status = 'wc-pending'
$item_name = $product->get_sku() ? $product->get_sku(): $item['product_id']; AND posts.post_modified < %s
", $date ) );
if ( ! empty( $item['variation_id'] ) ) { if ( $unpaid_orders ) {
/* translators: 1: item name 2: variation id 3: old stock quantity 4: new stock quantity */ foreach ( $unpaid_orders as $unpaid_order ) {
$order->add_order_note( sprintf( __( 'Item %1$s variation #%2$s stock reduced from %3$s to %4$s.', 'woocommerce' ), $item_name, $item['variation_id'], $new_stock + $qty, $new_stock ) ); $order = wc_get_order( $unpaid_order );
} else {
/* translators: 1: item name 2: old stock quantity 3: new stock quantity */
$order->add_order_note( sprintf( __( 'Item %1$s stock reduced from %2$s to %3$s.', 'woocommerce' ), $item_name, $new_stock + $qty, $new_stock ) );
}
if ( $new_stock < 0 ) { if ( apply_filters( 'woocommerce_cancel_unpaid_order', 'checkout' === get_post_meta( $unpaid_order, '_created_via', true ), $order ) ) {
do_action( 'woocommerce_product_on_backorder', array( 'product' => $product, 'order_id' => $order_id, 'quantity' => $qty_ordered ) ); $order->update_status( 'cancelled', __( 'Unpaid order cancelled - time limit reached.', 'woocommerce' ) );
}
} }
} }
do_action( 'woocommerce_reduce_order_stock', $order );
} }
wp_clear_scheduled_hook( 'woocommerce_cancel_unpaid_orders' );
wp_schedule_single_event( time() + ( absint( $held_duration ) * 60 ), 'woocommerce_cancel_unpaid_orders' );
} }
add_action( 'woocommerce_cancel_unpaid_orders', 'wc_cancel_unpaid_orders' );

View File

@ -1,4 +1,8 @@
<?php <?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/** /**
* WooCommerce Product Functions * WooCommerce Product Functions
* *
@ -7,11 +11,168 @@
* @author WooThemes * @author WooThemes
* @category Core * @category Core
* @package WooCommerce/Functions * @package WooCommerce/Functions
* @version 2.3.0 * @version 2.7.0
*/ */
if ( ! defined( 'ABSPATH' ) ) { /**
exit; // Exit if accessed directly * Products wrapper for get_posts.
*
* This function should be used for product retrieval so that we have a data agnostic
* way to get a list of products.
*
* Args:
* status array|string List of statuses to find. Default: any. Options: any, draft, pending, private and publish.
* type array|string Product type, e.g. Default: all. Options: all, simple, external, variable, variation, grouped.
* parent int post/product parent
* skus array Limit result set to products with specific SKUs.
* categories array Limit result set to products assigned a specific category, e.g. 9,14.
* tags array Limit result set to products assigned a specific tag, e.g. 9,14.
* limit int Maximum of products to retrieve.
* offset int Offset of products to retrieve.
* page int Page of products to retrieve. Ignored when using the 'offset' arg.
* exclude array Product IDs to exclude from the query.
* orderby string Order by date, title, id, modified, rand etc
* order string ASC or DESC
* return string Type of data to return. Allowed values:
* ids array of Product ids
* objects array of product objects (default)
* paginate bool If true, the return value will be an array with values:
* 'products' => array of data (return value above),
* 'total' => total number of products matching the query
* 'max_num_pages' => max number of pages found
*
* @since 2.7.0
* @param array $args Array of args (above)
* @return array|stdClass Number of pages and an array of product objects if
* paginate is true, or just an array of values.
*/
function wc_get_products( $args ) {
$args = wp_parse_args( $args, array(
'status' => array( 'draft', 'pending', 'private', 'publish' ),
'type' => array_merge( array_keys( wc_get_product_types() ) ),
'parent' => null,
'sku' => '',
'category' => array(),
'tag' => array(),
'limit' => get_option( 'posts_per_page' ),
'offset' => null,
'page' => 1,
'exclude' => array(),
'orderby' => 'date',
'order' => 'DESC',
'return' => 'objects',
'paginate' => false,
'shipping_class' => array(),
) );
// Handle some BW compatibility arg names where wp_query args differ in naming.
$map_legacy = array(
'numberposts' => 'limit',
'post_status' => 'status',
'post_parent' => 'parent',
'posts_per_page' => 'limit',
'paged' => 'page',
);
foreach ( $map_legacy as $from => $to ) {
if ( isset( $args[ $from ] ) ) {
$args[ $to ] = $args[ $from ];
}
}
/**
* Generate WP_Query args.
*/
$wp_query_args = array(
'post_type' => 'variation' === $args['type'] ? 'product_variation' : 'product',
'post_status' => $args['status'],
'posts_per_page' => $args['limit'],
'meta_query' => array(),
'orderby' => $args['orderby'],
'order' => $args['order'],
'tax_query' => array(),
);
// Do not load unneccessary post data if the user only wants IDs.
if ( 'ids' === $args['return'] ) {
$wp_query_args['fields'] = 'ids';
}
if ( 'variation' !== $args['type'] ) {
$wp_query_args['tax_query'][] = array(
'taxonomy' => 'product_type',
'field' => 'slug',
'terms' => $args['type'],
);
}
if ( ! empty( $args['sku'] ) ) {
$wp_query_args['meta_query'][] = array(
'key' => '_sku',
'value' => $args['sku'],
'compare' => 'LIKE',
);
}
if ( ! empty( $args['category'] ) ) {
$wp_query_args['tax_query'][] = array(
'taxonomy' => 'product_cat',
'field' => 'slug',
'terms' => $args['category'],
);
}
if ( ! empty( $args['tag'] ) ) {
$wp_query_args['tax_query'][] = array(
'taxonomy' => 'product_tag',
'field' => 'slug',
'terms' => $args['tag'],
);
}
if ( ! empty( $args['shipping_class'] ) ) {
$wp_query_args['tax_query'][] = array(
'taxonomy' => 'product_shipping_class',
'field' => 'slug',
'terms' => $args['shipping_class'],
);
}
if ( ! is_null( $args['parent'] ) ) {
$wp_query_args['post_parent'] = absint( $args['parent'] );
}
if ( ! is_null( $args['offset'] ) ) {
$wp_query_args['offset'] = absint( $args['offset'] );
} else {
$wp_query_args['paged'] = absint( $args['page'] );
}
if ( ! empty( $args['exclude'] ) ) {
$wp_query_args['post__not_in'] = array_map( 'absint', $args['exclude'] );
}
if ( ! $args['paginate'] ) {
$wp_query_args['no_found_rows'] = true;
}
// Get results.
$products = new WP_Query( $wp_query_args );
if ( 'objects' === $args['return'] ) {
$return = array_map( 'wc_get_product', $products->posts );
} else {
$return = $products->posts;
}
if ( $args['paginate'] ) {
return (object) array(
'products' => $return,
'total' => $products->found_posts,
'max_num_pages' => $products->max_num_pages,
);
} else {
return $return;
}
} }
/** /**
@ -20,42 +181,15 @@ if ( ! defined( 'ABSPATH' ) ) {
* @since 2.2.0 * @since 2.2.0
* *
* @param mixed $the_product Post object or post ID of the product. * @param mixed $the_product Post object or post ID of the product.
* @param array $args (default: array()) Contains all arguments to be used to get this product. * @param array $deprecated
* @return WC_Product * @return WC_Product|null
*/ */
function wc_get_product( $the_product = false, $args = array() ) { function wc_get_product( $the_product = false, $deprecated = array() ) {
if ( ! did_action( 'woocommerce_init' ) ) { if ( ! did_action( 'woocommerce_init' ) ) {
_doing_it_wrong( __FUNCTION__, __( 'wc_get_product should not be called before the woocommerce_init action.', 'woocommerce' ), '2.5' ); _doing_it_wrong( __FUNCTION__, __( 'wc_get_product should not be called before the woocommerce_init action.', 'woocommerce' ), '2.5' );
return false; return false;
} }
return WC()->product_factory->get_product( $the_product, $args ); return WC()->product_factory->get_product( $the_product );
}
/**
* Update a product's stock amount.
*
* @param int $product_id
* @param int $new_stock_level
*/
function wc_update_product_stock( $product_id, $new_stock_level ) {
$product = wc_get_product( $product_id );
if ( ! metadata_exists( 'post', $product_id, '_stock' ) || $product->get_stock_quantity() !== $new_stock_level ) {
$product->set_stock( $new_stock_level );
}
}
/**
* Update a product's stock status.
*
* @param int $product_id
* @param int $status
*/
function wc_update_product_stock_status( $product_id, $status ) {
$product = wc_get_product( $product_id );
if ( $product ) {
$product->set_stock_status( $status );
}
} }
/** /**
@ -99,9 +233,11 @@ function wc_delete_product_transients( $post_id = 0 ) {
// Transient names that include an ID // Transient names that include an ID
$post_transient_names = array( $post_transient_names = array(
'wc_product_children_', 'wc_product_children_',
'wc_product_total_stock_',
'wc_var_prices_', 'wc_var_prices_',
'wc_related_', 'wc_related_',
'wc_child_has_weight_',
'wc_child_has_dimensions_',
'wc_child_is_in_stock_',
); );
if ( $post_id > 0 ) { if ( $post_id > 0 ) {
@ -110,8 +246,10 @@ function wc_delete_product_transients( $post_id = 0 ) {
} }
// Does this product have a parent? // Does this product have a parent?
if ( $parent_id = wp_get_post_parent_id( $post_id ) ) { $product = wc_get_product( $post_id );
wc_delete_product_transients( $parent_id );
if ( $product->get_parent_id() > 0 ) {
wc_delete_product_transients( $product->get_parent_id() );
} }
} }
@ -130,12 +268,9 @@ function wc_delete_product_transients( $post_id = 0 ) {
* Function that returns an array containing the IDs of the products that are on sale. * Function that returns an array containing the IDs of the products that are on sale.
* *
* @since 2.0 * @since 2.0
* @access public
* @return array * @return array
*/ */
function wc_get_product_ids_on_sale() { function wc_get_product_ids_on_sale() {
global $wpdb;
// Load from cache // Load from cache
$product_ids_on_sale = get_transient( 'wc_products_onsale' ); $product_ids_on_sale = get_transient( 'wc_products_onsale' );
@ -144,21 +279,9 @@ function wc_get_product_ids_on_sale() {
return $product_ids_on_sale; return $product_ids_on_sale;
} }
$on_sale_posts = $wpdb->get_results( " $data_store = WC_Data_Store::load( 'product' );
SELECT post.ID, post.post_parent FROM `$wpdb->posts` AS post $on_sale_products = $data_store->get_on_sale_products();
LEFT JOIN `$wpdb->postmeta` AS meta ON post.ID = meta.post_id $product_ids_on_sale = wp_parse_id_list( array_merge( wp_list_pluck( $on_sale_products, 'id' ), array_diff( wp_list_pluck( $on_sale_products, 'parent_id' ), array( 0 ) ) ) );
LEFT JOIN `$wpdb->postmeta` AS meta2 ON post.ID = meta2.post_id
WHERE post.post_type IN ( 'product', 'product_variation' )
AND post.post_status = 'publish'
AND meta.meta_key = '_sale_price'
AND meta2.meta_key = '_price'
AND CAST( meta.meta_value AS DECIMAL ) >= 0
AND CAST( meta.meta_value AS CHAR ) != ''
AND CAST( meta.meta_value AS DECIMAL ) = CAST( meta2.meta_value AS DECIMAL )
GROUP BY post.ID;
" );
$product_ids_on_sale = array_unique( array_map( 'absint', array_merge( wp_list_pluck( $on_sale_posts, 'ID' ), array_diff( wp_list_pluck( $on_sale_posts, 'post_parent' ), array( 0 ) ) ) ) );
set_transient( 'wc_products_onsale', $product_ids_on_sale, DAY_IN_SECONDS * 30 ); set_transient( 'wc_products_onsale', $product_ids_on_sale, DAY_IN_SECONDS * 30 );
@ -169,11 +292,9 @@ function wc_get_product_ids_on_sale() {
* Function that returns an array containing the IDs of the featured products. * Function that returns an array containing the IDs of the featured products.
* *
* @since 2.1 * @since 2.1
* @access public
* @return array * @return array
*/ */
function wc_get_featured_product_ids() { function wc_get_featured_product_ids() {
// Load from cache // Load from cache
$featured_product_ids = get_transient( 'wc_featured_products' ); $featured_product_ids = get_transient( 'wc_featured_products' );
@ -181,24 +302,8 @@ function wc_get_featured_product_ids() {
if ( false !== $featured_product_ids ) if ( false !== $featured_product_ids )
return $featured_product_ids; return $featured_product_ids;
$featured = get_posts( array( $data_store = WC_Data_Store::load( 'product' );
'post_type' => array( 'product', 'product_variation' ), $featured = $data_store->get_featured_product_ids();
'posts_per_page' => -1,
'post_status' => 'publish',
'meta_query' => array(
array(
'key' => '_visibility',
'value' => array( 'catalog', 'visible' ),
'compare' => 'IN',
),
array(
'key' => '_featured',
'value' => 'yes',
),
),
'fields' => 'id=>parent',
) );
$product_ids = array_keys( $featured ); $product_ids = array_keys( $featured );
$parent_ids = array_values( array_filter( $featured ) ); $parent_ids = array_values( array_filter( $featured ) );
$featured_product_ids = array_unique( array_merge( $product_ids, $parent_ids ) ); $featured_product_ids = array_unique( array_merge( $product_ids, $parent_ids ) );
@ -306,7 +411,6 @@ function wc_placeholder_img( $size = 'shop_thumbnail' ) {
* *
* Gets a formatted version of variation data or item meta. * Gets a formatted version of variation data or item meta.
* *
* @access public
* @param string $variation * @param string $variation
* @param bool $flat (default: false) * @param bool $flat (default: false)
* @return string * @return string
@ -358,91 +462,41 @@ function wc_get_formatted_variation( $variation, $flat = false ) {
/** /**
* Function which handles the start and end of scheduled sales via cron. * Function which handles the start and end of scheduled sales via cron.
*
* @access public
*/ */
function wc_scheduled_sales() { function wc_scheduled_sales() {
global $wpdb; $data_store = WC_Data_Store::load( 'product' );
// Sales which are due to start // Sales which are due to start
$product_ids = $wpdb->get_col( $wpdb->prepare( " $product_ids = $data_store->get_starting_sales();
SELECT postmeta.post_id FROM {$wpdb->postmeta} as postmeta
LEFT JOIN {$wpdb->postmeta} as postmeta_2 ON postmeta.post_id = postmeta_2.post_id
LEFT JOIN {$wpdb->postmeta} as postmeta_3 ON postmeta.post_id = postmeta_3.post_id
WHERE postmeta.meta_key = '_sale_price_dates_from'
AND postmeta_2.meta_key = '_price'
AND postmeta_3.meta_key = '_sale_price'
AND postmeta.meta_value > 0
AND postmeta.meta_value < %s
AND postmeta_2.meta_value != postmeta_3.meta_value
", current_time( 'timestamp' ) ) );
if ( $product_ids ) { if ( $product_ids ) {
foreach ( $product_ids as $product_id ) { foreach ( $product_ids as $product_id ) {
$sale_price = get_post_meta( $product_id, '_sale_price', true ); $product = wc_get_product( $product_id );
$sale_price = $product->get_sale_price();
if ( $sale_price ) { if ( $sale_price ) {
update_post_meta( $product_id, '_price', $sale_price ); $product->set_price( $sale_price );
} else { } else {
// No sale price! $product->set_date_on_sale_to( '' );
update_post_meta( $product_id, '_sale_price_dates_from', '' ); $product->set_date_on_sale_from( '' );
update_post_meta( $product_id, '_sale_price_dates_to', '' );
} }
$parent = wp_get_post_parent_id( $product_id ); $product->save();
// Sync parent
if ( $parent ) {
// Clear prices transient for variable products.
delete_transient( 'wc_var_prices_' . $parent );
// Grouped products need syncing via a function
$this_product = wc_get_product( $product_id );
if ( $this_product->is_type( 'simple' ) ) {
$this_product->grouped_product_sync();
}
}
} }
delete_transient( 'wc_products_onsale' ); delete_transient( 'wc_products_onsale' );
} }
// Sales which are due to end // Sales which are due to end
$product_ids = $wpdb->get_col( $wpdb->prepare( " $product_ids = $data_store->get_ending_sales();
SELECT postmeta.post_id FROM {$wpdb->postmeta} as postmeta
LEFT JOIN {$wpdb->postmeta} as postmeta_2 ON postmeta.post_id = postmeta_2.post_id
LEFT JOIN {$wpdb->postmeta} as postmeta_3 ON postmeta.post_id = postmeta_3.post_id
WHERE postmeta.meta_key = '_sale_price_dates_to'
AND postmeta_2.meta_key = '_price'
AND postmeta_3.meta_key = '_regular_price'
AND postmeta.meta_value > 0
AND postmeta.meta_value < %s
AND postmeta_2.meta_value != postmeta_3.meta_value
", current_time( 'timestamp' ) ) );
if ( $product_ids ) { if ( $product_ids ) {
foreach ( $product_ids as $product_id ) { foreach ( $product_ids as $product_id ) {
$regular_price = get_post_meta( $product_id, '_regular_price', true ); $product = wc_get_product( $product_id );
$regular_price = $product->get_regular_price();
update_post_meta( $product_id, '_price', $regular_price ); $product->set_price( $regular_price );
update_post_meta( $product_id, '_sale_price', '' ); $product->set_sale_price( '' );
update_post_meta( $product_id, '_sale_price_dates_from', '' ); $product->set_date_on_sale_to( '' );
update_post_meta( $product_id, '_sale_price_dates_to', '' ); $product->set_date_on_sale_from( '' );
$product->save();
$parent = wp_get_post_parent_id( $product_id );
// Sync parent
if ( $parent ) {
// Clear prices transient for variable products.
delete_transient( 'wc_var_prices_' . $parent );
// Grouped products need syncing via a function
$this_product = wc_get_product( $product_id );
if ( $this_product->is_type( 'simple' ) ) {
$this_product->grouped_product_sync();
}
}
} }
WC_Cache_Helper::get_transient_version( 'product', true ); WC_Cache_Helper::get_transient_version( 'product', true );
@ -459,10 +513,9 @@ add_action( 'woocommerce_scheduled_sales', 'wc_scheduled_sales' );
* @return array * @return array
*/ */
function wc_get_attachment_image_attributes( $attr ) { function wc_get_attachment_image_attributes( $attr ) {
if ( strstr( $attr['src'], 'woocommerce_uploads/' ) ) { if ( strstr( $attr['src'][0], 'woocommerce_uploads/' ) ) {
$attr['src'] = wc_placeholder_img_src(); $attr['src'][0] = wc_placeholder_img_src();
} }
return $attr; return $attr;
} }
add_filter( 'wp_get_attachment_image_attributes', 'wc_get_attachment_image_attributes' ); add_filter( 'wp_get_attachment_image_attributes', 'wc_get_attachment_image_attributes' );
@ -539,21 +592,14 @@ function wc_get_product_types() {
* *
* @since 2.2 * @since 2.2
* @param int $product_id * @param int $product_id
* @param string $sku Will be slashed to work around https://core.trac.wordpress.org/ticket/27421 * @param string $sku
* @return bool * @return bool
*/ */
function wc_product_has_unique_sku( $product_id, $sku ) { function wc_product_has_unique_sku( $product_id, $sku ) {
global $wpdb; global $wpdb;
$sku_found = $wpdb->get_var( $wpdb->prepare( " $data_store = WC_Data_Store::load( 'product' );
SELECT $wpdb->posts.ID $sku_found = $data_store->is_existing_sku( $product_id, $sku );
FROM $wpdb->posts
LEFT JOIN $wpdb->postmeta ON ( $wpdb->posts.ID = $wpdb->postmeta.post_id )
WHERE $wpdb->posts.post_type IN ( 'product', 'product_variation' )
AND $wpdb->posts.post_status = 'publish'
AND $wpdb->postmeta.meta_key = '_sku' AND $wpdb->postmeta.meta_value = '%s'
AND $wpdb->postmeta.post_id <> %d LIMIT 1
", wp_slash( $sku ), $product_id ) );
if ( apply_filters( 'wc_product_has_unique_sku', $sku_found, $product_id, $sku ) ) { if ( apply_filters( 'wc_product_has_unique_sku', $sku_found, $product_id, $sku ) ) {
return false; return false;
@ -572,71 +618,15 @@ function wc_product_has_unique_sku( $product_id, $sku ) {
function wc_get_product_id_by_sku( $sku ) { function wc_get_product_id_by_sku( $sku ) {
global $wpdb; global $wpdb;
$product_id = $wpdb->get_var( $wpdb->prepare( " $data_store = WC_Data_Store::load( 'product' );
SELECT posts.ID $product_id = $data_store->get_product_id_by_sku( $sku );
FROM $wpdb->posts AS posts
LEFT JOIN $wpdb->postmeta AS postmeta ON ( posts.ID = postmeta.post_id )
WHERE posts.post_type IN ( 'product', 'product_variation' )
AND postmeta.meta_key = '_sku' AND postmeta.meta_value = '%s'
LIMIT 1
", $sku ) );
return ( $product_id ) ? intval( $product_id ) : 0; return ( $product_id ) ? intval( $product_id ) : 0;
} }
/**
* Save product price.
*
* This is a private function (internal use ONLY) used until a data manipulation api is built.
*
* @since 2.4.0
* @todo look into Data manipulation API
*
* @param int $product_id
* @param float $regular_price
* @param float $sale_price
* @param string $date_from
* @param string $date_to
*/
function _wc_save_product_price( $product_id, $regular_price, $sale_price = '', $date_from = '', $date_to = '' ) {
$product_id = absint( $product_id );
$regular_price = wc_format_decimal( $regular_price );
$sale_price = '' === $sale_price ? '' : wc_format_decimal( $sale_price );
$date_from = wc_clean( $date_from );
$date_to = wc_clean( $date_to );
update_post_meta( $product_id, '_regular_price', $regular_price );
update_post_meta( $product_id, '_sale_price', $sale_price );
// Save Dates
update_post_meta( $product_id, '_sale_price_dates_from', $date_from ? strtotime( $date_from ) : '' );
update_post_meta( $product_id, '_sale_price_dates_to', $date_to ? strtotime( $date_to ) : '' );
if ( $date_to && ! $date_from ) {
$date_from = strtotime( 'NOW', current_time( 'timestamp' ) );
update_post_meta( $product_id, '_sale_price_dates_from', $date_from );
}
// Update price if on sale
if ( '' !== $sale_price && '' === $date_to && '' === $date_from ) {
update_post_meta( $product_id, '_price', $sale_price );
} else {
update_post_meta( $product_id, '_price', $regular_price );
}
if ( '' !== $sale_price && $date_from && strtotime( $date_from ) < strtotime( 'NOW', current_time( 'timestamp' ) ) ) {
update_post_meta( $product_id, '_price', $sale_price );
}
if ( $date_to && strtotime( $date_to ) < strtotime( 'NOW', current_time( 'timestamp' ) ) ) {
update_post_meta( $product_id, '_price', $regular_price );
update_post_meta( $product_id, '_sale_price_dates_from', '' );
update_post_meta( $product_id, '_sale_price_dates_to', '' );
}
}
/** /**
* Get attibutes/data for an individual variation from the database and maintain it's integrity. * Get attibutes/data for an individual variation from the database and maintain it's integrity.
* @param revisit?
* @since 2.4.0 * @since 2.4.0
* @param int $variation_id * @param int $variation_id
* @return array * @return array
@ -700,7 +690,7 @@ function wc_get_product_variation_attributes( $variation_id ) {
* @return array * @return array
*/ */
function wc_get_product_cat_ids( $product_id ) { function wc_get_product_cat_ids( $product_id ) {
$product_cats = wp_get_post_terms( $product_id, 'product_cat', array( "fields" => "ids" ) ); $product_cats = wc_get_product_term_ids( $product_id, 'product_cat' );
foreach ( $product_cats as $product_cat ) { foreach ( $product_cats as $product_cat ) {
$product_cats = array_merge( $product_cats, get_ancestors( $product_cat, 'product_cat' ) ); $product_cats = array_merge( $product_cats, get_ancestors( $product_cat, 'product_cat' ) );
@ -721,6 +711,9 @@ function wc_get_product_attachment_props( $attachment_id, $product = false ) {
'caption' => '', 'caption' => '',
'url' => '', 'url' => '',
'alt' => '', 'alt' => '',
'src' => '',
'srcset' => false,
'sizes' => false,
); );
if ( $attachment_id ) { if ( $attachment_id ) {
$attachment = get_post( $attachment_id ); $attachment = get_post( $attachment_id );
@ -729,10 +722,24 @@ function wc_get_product_attachment_props( $attachment_id, $product = false ) {
$props['url'] = wp_get_attachment_url( $attachment_id ); $props['url'] = wp_get_attachment_url( $attachment_id );
$props['alt'] = trim( strip_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) ); $props['alt'] = trim( strip_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) );
// Large version.
$src = wp_get_attachment_image_src( $attachment_id, 'large' );
$props['full_src'] = $src[0];
$props['full_src_w'] = $src[1];
$props['full_src_h'] = $src[2];
// Image source.
$src = wp_get_attachment_image_src( $attachment_id, 'shop_single' );
$props['src'] = $src[0];
$props['src_w'] = $src[1];
$props['src_h'] = $src[2];
$props['srcset'] = function_exists( 'wp_get_attachment_image_srcset' ) ? wp_get_attachment_image_srcset( $attachment_id, 'shop_single' ) : false;
$props['sizes'] = function_exists( 'wp_get_attachment_image_sizes' ) ? wp_get_attachment_image_sizes( $attachment_id, 'shop_single' ) : false;
// Alt text fallbacks // Alt text fallbacks
$props['alt'] = empty( $props['alt'] ) ? $props['caption'] : $props['alt']; $props['alt'] = empty( $props['alt'] ) ? $props['caption'] : $props['alt'];
$props['alt'] = empty( $props['alt'] ) ? trim( strip_tags( $attachment->post_title ) ) : $props['alt']; $props['alt'] = empty( $props['alt'] ) ? trim( strip_tags( $attachment->post_title ) ) : $props['alt'];
$props['alt'] = empty( $props['alt'] ) && $product ? trim( strip_tags( get_the_title( $product->ID ) ) ) : $props['alt']; $props['alt'] = empty( $props['alt'] ) && $product ? trim( strip_tags( get_the_title( $product->ID ) ) ) : $props['alt'];
} }
return $props; return $props;
} }
@ -788,3 +795,340 @@ function wc_get_min_max_price_meta_query( $args ) {
'type' => 'DECIMAL', 'type' => 'DECIMAL',
); );
} }
/**
* Get product tax class options.
*
* @since 2.7.0
* @return array
*/
function wc_get_product_tax_class_options() {
$tax_classes = WC_Tax::get_tax_classes();
$tax_class_options = array();
$tax_class_options[''] = __( 'Standard', 'woocommerce' );
if ( ! empty( $tax_classes ) ) {
foreach ( $tax_classes as $class ) {
$tax_class_options[ sanitize_title( $class ) ] = $class;
}
}
return $tax_class_options;
}
/**
* Get stock status options.
*
* @since 2.7.0
* @return array
*/
function wc_get_product_stock_status_options() {
return array(
'instock' => __( 'In stock', 'woocommerce' ),
'outofstock' => __( 'Out of stock', 'woocommerce' ),
);
}
/**
* Get backorder options.
*
* @since 2.7.0
* @return array
*/
function wc_get_product_backorder_options() {
return array(
'no' => __( 'Do not allow', 'woocommerce' ),
'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
'yes' => __( 'Allow', 'woocommerce' ),
);
}
/**
* Get related products based on product category and tags.
*
* @since 2.7.0
* @param int $product_id Product ID.
* @param int $limit Limit of results.
* @param array $exclude_ids Exclude IDs from the results.
* @return array
*/
function wc_get_related_products( $product_id, $limit = 5, $exclude_ids = array() ) {
global $wpdb;
$product_id = absint( $product_id );
$exclude_ids = array_merge( array( 0, $product_id ), $exclude_ids );
$transient_name = 'wc_related_' . $product_id;
$related_posts = get_transient( $transient_name );
$limit = $limit > 0 ? $limit : 5;
// We want to query related posts if they are not cached, or we don't have enough.
if ( false === $related_posts || count( $related_posts ) < $limit ) {
$cats_array = apply_filters( 'woocommerce_product_related_posts_relate_by_category', true, $product_id ) ? apply_filters( 'woocommerce_get_related_product_cat_terms', wc_get_product_term_ids( $product_id, 'product_cat' ), $product_id ) : array();
$tags_array = apply_filters( 'woocommerce_product_related_posts_relate_by_tag', true, $product_id ) ? apply_filters( 'woocommerce_get_related_product_tag_terms', wc_get_product_term_ids( $product_id, 'product_tag' ), $product_id ) : array();
// Don't bother if none are set, unless woocommerce_product_related_posts_force_display is set to true in which case all products are related.
if ( empty( $cats_array ) && empty( $tags_array ) && ! apply_filters( 'woocommerce_product_related_posts_force_display', false, $product_id ) ) {
$related_posts = array();
} else {
$data_store = WC_Data_Store::load( 'product' );
$related_posts = $data_store->get_related_products( $cats_array, $tags_array, $exclude_ids, $limit + 10, $product_id );
}
set_transient( $transient_name, $related_posts, DAY_IN_SECONDS );
}
shuffle( $related_posts );
return array_slice( $related_posts, 0, $limit );
}
/**
* Retrieves product term ids for a taxonomy.
*
* @since 2.7.0
* @param int $product_id Product ID.
* @param string $taxonomy Taxonomy slug.
* @return array
*/
function wc_get_product_term_ids( $product_id, $taxonomy ) {
$terms = get_the_terms( $product_id, $taxonomy );
return ! empty( $terms ) ? wp_list_pluck( $terms, 'term_id' ) : array();
}
/**
* For a given product, and optionally price/qty, work out the price with tax included, based on store settings.
* @since 2.7.0
* @param WC_Product $product
* @param array $args
* @return float
*/
function wc_get_price_including_tax( $product, $args = array() ) {
$args = wp_parse_args( $args, array(
'qty' => '',
'price' => '',
) );
$price = $args['price'] ? $args['price'] : $product->get_price();
$qty = $args['qty'] ? $args['qty'] : 1;
if ( ! $product->is_taxable() ) {
$price = $price * $qty;
} elseif ( wc_prices_include_tax() ) {
$tax_rates = WC_Tax::get_rates( $product->get_tax_class() );
$taxes = WC_Tax::calc_tax( $price * $qty, $tax_rates, false );
$tax_amount = WC_Tax::get_tax_total( $taxes );
$price = round( $price * $qty + $tax_amount, wc_get_price_decimals() );
} else {
$tax_rates = WC_Tax::get_rates( $product->get_tax_class() );
$base_tax_rates = WC_Tax::get_base_tax_rates( $product->get_tax_class( true ) );
if ( ! empty( WC()->customer ) && WC()->customer->get_is_vat_exempt() ) {
$base_taxes = WC_Tax::calc_tax( $price * $qty, $base_tax_rates, true );
$base_tax_amount = array_sum( $base_taxes );
$price = round( $price * $qty - $base_tax_amount, wc_get_price_decimals() );
/**
* The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing with out of base locations.
* e.g. If a product costs 10 including tax, all users will pay 10 regardless of location and taxes.
* This feature is experimental @since 2.4.7 and may change in the future. Use at your risk.
*/
} elseif ( $tax_rates !== $base_tax_rates && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) {
$base_taxes = WC_Tax::calc_tax( $price * $qty, $base_tax_rates, true );
$modded_taxes = WC_Tax::calc_tax( ( $price * $qty ) - array_sum( $base_taxes ), $tax_rates, false );
$price = round( ( $price * $qty ) - array_sum( $base_taxes ) + array_sum( $modded_taxes ), wc_get_price_decimals() );
} else {
$price = $price * $qty;
}
}
return apply_filters( 'woocommerce_get_price_including_tax', $price, $qty, $product );
}
/**
* For a given product, and optionally price/qty, work out the price with tax excluded, based on store settings.
* @since 2.7.0
* @param WC_Product $product
* @param array $args
* @return float
*/
function wc_get_price_excluding_tax( $product, $args = array() ) {
$args = wp_parse_args( $args, array(
'qty' => '',
'price' => '',
) );
$price = $args['price'] ? $args['price'] : $product->get_price();
$qty = $args['qty'] ? $args['qty'] : 1;
if ( $product->is_taxable() && wc_prices_include_tax() ) {
$tax_rates = WC_Tax::get_base_tax_rates( $product->get_tax_class( true ) );
$taxes = WC_Tax::calc_tax( $price * $qty, $tax_rates, true );
$price = WC_Tax::round( $price * $qty - array_sum( $taxes ) );
} else {
$price = $price * $qty;
}
return apply_filters( 'woocommerce_get_price_excluding_tax', $price, $qty, $product );
}
/**
* Returns the price including or excluding tax, based on the 'woocommerce_tax_display_shop' setting.
* @since 2.7.0
* @param WC_Product $product
* @param array $args
* @return float
*/
function wc_get_price_to_display( $product, $args = array() ) {
$args = wp_parse_args( $args, array(
'qty' => 1,
'price' => $product->get_price(),
) );
$price = $args['price'];
$qty = $args['qty'];
return 'incl' === get_option( 'woocommerce_tax_display_shop' ) ? wc_get_price_including_tax( $product, array( 'qty' => $qty, 'price' => $price ) ) : wc_get_price_excluding_tax( $product, array( 'qty' => $qty, 'price' => $price ) );
}
/**
* Returns the product categories in a list.
*
* @param int $product_id
* @param string $sep (default: ', ').
* @param string $before (default: '').
* @param string $after (default: '').
* @return string
*/
function wc_get_product_category_list( $product_id, $sep = ', ', $before = '', $after = '' ) {
return get_the_term_list( $product_id, 'product_cat', $before, $sep, $after );
}
/**
* Returns the product tags in a list.
*
* @param int $product_id
* @param string $sep (default: ', ').
* @param string $before (default: '').
* @param string $after (default: '').
* @return string
*/
function wc_get_product_tag_list( $product_id, $sep = ', ', $before = '', $after = '' ) {
return get_the_term_list( $product_id, 'product_tag', $before, $sep, $after );
}
/**
* Callback for array filter to get visible only.
* @since 2.7.0
* @param WC_Product $product
* @return bool
*/
function wc_products_array_filter_visible( $product ) {
return $product && is_a( $product, 'WC_Product' ) && $product->is_visible();
}
/**
* Sort an array of products by a value.
* @since 2.7.0
* @param array $products
* @param string $orderby
* @return array
*/
function wc_products_array_orderby( $products, $orderby = 'date', $order = 'desc' ) {
$orderby = strtolower( $orderby );
$order = strtolower( $order );
switch ( $orderby ) {
case 'title' :
case 'id' :
case 'date' :
case 'modified' :
case 'menu_order' :
case 'price' :
usort( $products, 'wc_products_array_orderby_' . $orderby );
break;
default :
shuffle( $products );
break;
}
if ( 'desc' === $order ) {
$products = array_reverse( $products );
}
return $products;
}
/**
* Sort by title.
* @since 2.7.0
* @param WC_Product object $a
* @param WC_Product object $b
* @return int
*/
function wc_products_array_orderby_title( $a, $b ) {
return strcasecmp( $a->get_name(), $b->get_name() );
}
/**
* Sort by id.
* @since 2.7.0
* @param WC_Product object $a
* @param WC_Product object $b
* @return int
*/
function wc_products_array_orderby_id( $a, $b ) {
if ( $a->get_id() === $b->get_id() ) {
return 0;
}
return ( $a->get_id() < $b->get_id() ) ? -1 : 1;
}
/**
* Sort by date.
* @since 2.7.0
* @param WC_Product object $a
* @param WC_Product object $b
* @return int
*/
function wc_products_array_orderby_date( $a, $b ) {
if ( $a->get_date_created() === $b->get_date_created() ) {
return 0;
}
return ( $a->get_date_created() < $b->get_date_created() ) ? -1 : 1;
}
/**
* Sort by modified.
* @since 2.7.0
* @param WC_Product object $a
* @param WC_Product object $b
* @return int
*/
function wc_products_array_orderby_modified( $a, $b ) {
if ( $a->get_date_modified() === $b->get_date_modified() ) {
return 0;
}
return ( $a->get_date_modified() < $b->get_date_modified() ) ? -1 : 1;
}
/**
* Sort by menu order.
* @since 2.7.0
* @param WC_Product object $a
* @param WC_Product object $b
* @return int
*/
function wc_products_array_orderby_menu_order( $a, $b ) {
if ( $a->get_menu_order() === $b->get_menu_order() ) {
return 0;
}
return ( $a->get_menu_order() < $b->get_menu_order() ) ? -1 : 1;
}
/**
* Sort by price low to high.
* @since 2.7.0
* @param WC_Product object $a
* @param WC_Product object $b
* @return int
*/
function wc_products_array_orderby_price( $a, $b ) {
if ( $a->get_price() === $b->get_price() ) {
return 0;
}
return ( $a->get_price() < $b->get_price() ) ? -1 : 1;
}

View File

@ -25,6 +25,10 @@ if ( ! defined( 'ABSPATH' ) ) {
* @return string|null ISO8601/RFC3339 formatted datetime. * @return string|null ISO8601/RFC3339 formatted datetime.
*/ */
function wc_rest_prepare_date_response( $date ) { function wc_rest_prepare_date_response( $date ) {
if ( false === strpos( $date, '-' ) ) {
$date = date( 'Y-m-d H:i:s', $date );
}
// Check if mysql_to_rfc3339 exists first! // Check if mysql_to_rfc3339 exists first!
if ( ! function_exists( 'mysql_to_rfc3339' ) ) { if ( ! function_exists( 'mysql_to_rfc3339' ) ) {
return null; return null;

View File

@ -0,0 +1,112 @@
<?php
/**
* WooCommerce Stock Functions
*
* Functions used to manage product stock levels.
*
* @author WooThemes
* @category Core
* @package WooCommerce/Functions
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Update a product's stock amount.
*
* Uses queries rather than update_post_meta so we can do this in one query (to avoid stock issues).
*
* @since 2.7.0 this supports set, increase and decrease.
* @param int|WC_Product $product
* @param int|null $stock_quantity
* @param string $operation set, increase and decrease.
*/
function wc_update_product_stock( $product, $stock_quantity = null, $operation = 'set' ) {
$product = wc_get_product( $product );
if ( ! is_null( $stock_quantity ) && $product->managing_stock() ) {
// Some products (variations) can have their stock managed by their parent. Get the correct ID to reduce here.
$product_id_with_stock = $product->get_stock_managed_by_id();
$product_with_stock = $product->get_id() !== $product_id_with_stock ? wc_get_product( $product_id_with_stock ) : $product;
$data_store = WC_Data_Store::load( 'product' );
$data_store->update_product_stock( $product_id_with_stock, $stock_quantity, $operation );
delete_transient( 'wc_low_stock_count' );
delete_transient( 'wc_outofstock_count' );
// Re-read product data after updating stock, then have stock status calculated and saved.
$product_with_stock = wc_get_product( $product_with_stock->get_id() );
$product_with_stock->set_stock_status();
$product_with_stock->save();
do_action( $product_with_stock->is_type( 'variation' ) ? 'woocommerce_variation_set_stock' : 'woocommerce_product_set_stock', $product_with_stock );
return $product_with_stock->get_stock_quantity();
}
return $product->get_stock_quantity();
}
/**
* Update a product's stock status.
*
* @param int $product_id
* @param int $status
*/
function wc_update_product_stock_status( $product_id, $status ) {
$product = wc_get_product( $product_id );
if ( $product ) {
$product->set_stock_status( $status );
$product->save();
}
}
/**
* When a payment is complete, we can reduce stock levels for items within an order.
* @since 2.7.0
* @param int $order_id
*/
function wc_maybe_reduce_stock_levels( $order_id ) {
if ( apply_filters( 'woocommerce_payment_complete_reduce_order_stock', ! get_post_meta( $order_id, '_order_stock_reduced', true ), $order_id ) ) {
wc_reduce_stock_levels( $order_id );
add_post_meta( $order_id, '_order_stock_reduced', '1', true );
}
}
add_action( 'woocommerce_payment_complete', 'wc_maybe_reduce_stock_levels' );
/**
* Reduce stock levels for items within an order.
* @since 2.7.0
* @param int $order_id
*/
function wc_reduce_stock_levels( $order_id ) {
$order = wc_get_order( $order_id );
if ( 'yes' === get_option( 'woocommerce_manage_stock' ) && $order && apply_filters( 'woocommerce_can_reduce_order_stock', true, $order ) && sizeof( $order->get_items() ) > 0 ) {
foreach ( $order->get_items() as $item ) {
if ( $item->is_type( 'line_item' ) && ( $product = $item->get_product() ) && $product->managing_stock() ) {
$qty = apply_filters( 'woocommerce_order_item_quantity', $item->get_quantity(), $order, $item );
$item_name = $product->get_formatted_name();
$new_stock = wc_update_product_stock( $product, $qty, 'subtract' );
if ( ! is_wp_error( $new_stock ) ) {
/* translators: 1: item name 2: old stock quantity 3: new stock quantity */
$order->add_order_note( sprintf( __( '%1$s stock reduced from %2$s to %3$s.', 'woocommerce' ), $item_name, $new_stock + $qty, $new_stock ) );
if ( '' !== get_option( 'woocommerce_notify_no_stock_amount' ) && $new_stock <= get_option( 'woocommerce_notify_no_stock_amount' ) ) {
do_action( 'woocommerce_no_stock', $product );
} elseif ( '' !== get_option( 'woocommerce_notify_low_stock_amount' ) && $new_stock <= get_option( 'woocommerce_notify_low_stock_amount' ) ) {
do_action( 'woocommerce_low_stock', $product );
}
if ( $new_stock < 0 ) {
do_action( 'woocommerce_product_on_backorder', array( 'product' => $product, 'order_id' => $order_id, 'quantity' => $qty_ordered ) );
}
}
}
}
do_action( 'woocommerce_reduce_order_stock', $order );
}
}

View File

@ -45,7 +45,7 @@ function wc_template_redirect() {
$product = wc_get_product( $wp_query->post ); $product = wc_get_product( $wp_query->post );
if ( $product && $product->is_visible() ) { if ( $product && $product->is_visible() ) {
wp_safe_redirect( get_permalink( $product->id ), 302 ); wp_safe_redirect( get_permalink( $product->get_id() ), 302 );
exit; exit;
} }
} elseif ( is_add_payment_method_page() ) { } elseif ( is_add_payment_method_page() ) {
@ -303,7 +303,7 @@ function wc_product_post_class( $classes, $class = '', $post_id = '' ) {
if ( $product ) { if ( $product ) {
$classes[] = wc_get_loop_class(); $classes[] = wc_get_loop_class();
$classes[] = $product->stock_status; $classes[] = $product->get_stock_status();
if ( $product->is_on_sale() ) { if ( $product->is_on_sale() ) {
$classes[] = 'sale'; $classes[] = 'sale';
@ -333,7 +333,7 @@ function wc_product_post_class( $classes, $class = '', $post_id = '' ) {
$classes[] = "product-type-" . $product->get_type(); $classes[] = "product-type-" . $product->get_type();
} }
if ( $product->is_type( 'variable' ) ) { if ( $product->is_type( 'variable' ) ) {
if ( $product->has_default_attributes() ) { if ( ! $product->get_default_attributes() ) {
$classes[] = 'has-default-attributes'; $classes[] = 'has-default-attributes';
} }
if ( $product->has_child() ) { if ( $product->has_child() ) {
@ -682,7 +682,7 @@ if ( ! function_exists( 'woocommerce_template_loop_add_to_cart' ) ) {
'quantity' => 1, 'quantity' => 1,
'class' => implode( ' ', array_filter( array( 'class' => implode( ' ', array_filter( array(
'button', 'button',
'product_type_' . $product->product_type, 'product_type_' . $product->get_type(),
$product->is_purchasable() && $product->is_in_stock() ? 'add_to_cart_button' : '', $product->is_purchasable() && $product->is_in_stock() ? 'add_to_cart_button' : '',
$product->supports( 'ajax_add_to_cart' ) ? 'ajax_add_to_cart' : '', $product->supports( 'ajax_add_to_cart' ) ? 'ajax_add_to_cart' : '',
) ) ), ) ) ),
@ -949,7 +949,7 @@ if ( ! function_exists( 'woocommerce_template_single_add_to_cart' ) ) {
*/ */
function woocommerce_template_single_add_to_cart() { function woocommerce_template_single_add_to_cart() {
global $product; global $product;
do_action( 'woocommerce_' . $product->product_type . '_add_to_cart' ); do_action( 'woocommerce_' . $product->get_type() . '_add_to_cart' );
} }
} }
if ( ! function_exists( 'woocommerce_simple_add_to_cart' ) ) { if ( ! function_exists( 'woocommerce_simple_add_to_cart' ) ) {
@ -975,7 +975,7 @@ if ( ! function_exists( 'woocommerce_grouped_add_to_cart' ) ) {
wc_get_template( 'single-product/add-to-cart/grouped.php', array( wc_get_template( 'single-product/add-to-cart/grouped.php', array(
'grouped_product' => $product, 'grouped_product' => $product,
'grouped_products' => $product->get_children(), 'grouped_products' => array_filter( array_map( 'wc_get_product', $product->get_children() ), 'wc_products_array_filter_visible' ),
'quantites_required' => false, 'quantites_required' => false,
) ); ) );
} }
@ -1000,7 +1000,7 @@ if ( ! function_exists( 'woocommerce_variable_add_to_cart' ) ) {
wc_get_template( 'single-product/add-to-cart/variable.php', array( wc_get_template( 'single-product/add-to-cart/variable.php', array(
'available_variations' => $get_variations ? $product->get_available_variations() : false, 'available_variations' => $get_variations ? $product->get_available_variations() : false,
'attributes' => $product->get_variation_attributes(), 'attributes' => $product->get_variation_attributes(),
'selected_attributes' => $product->get_variation_default_attributes(), 'selected_attributes' => $product->get_default_attributes(),
) ); ) );
} }
} }
@ -1133,7 +1133,7 @@ if ( ! function_exists( 'woocommerce_default_product_tabs' ) ) {
} }
// Additional information tab - shows attributes // Additional information tab - shows attributes
if ( $product && ( $product->has_attributes() || $product->enable_dimensions_display() ) ) { if ( $product && ( $product->has_attributes() || apply_filters( 'wc_product_enable_dimensions_display', $product->has_weight() || $product->has_dimensions() ) ) ) {
$tabs['additional_information'] = array( $tabs['additional_information'] = array(
'title' => __( 'Additional information', 'woocommerce' ), 'title' => __( 'Additional information', 'woocommerce' ),
'priority' => 20, 'priority' => 20,
@ -1272,30 +1272,29 @@ if ( ! function_exists( 'woocommerce_related_products' ) ) {
* Output the related products. * Output the related products.
* *
* @param array Provided arguments * @param array Provided arguments
* @param bool Columns argument for backwards compat
* @param bool Order by argument for backwards compat
*/ */
function woocommerce_related_products( $args = array(), $columns = false, $orderby = false ) { function woocommerce_related_products( $args = array() ) {
if ( ! is_array( $args ) ) { global $product, $woocommerce_loop;
_deprecated_argument( __FUNCTION__, '2.1', __( 'Use $args argument as an array instead. Deprecated argument will be removed in WC 2.2.', 'woocommerce' ) );
$argsvalue = $args;
$args = array(
'posts_per_page' => $argsvalue,
'columns' => $columns,
'orderby' => $orderby,
);
}
$defaults = array( $defaults = array(
'posts_per_page' => 2, 'posts_per_page' => 2,
'columns' => 2, 'columns' => 2,
'orderby' => 'rand', 'orderby' => 'rand',
'order' => 'desc',
); );
$args = wp_parse_args( $args, $defaults ); $args = wp_parse_args( $args, $defaults );
// Get visble related products then sort them at random.
$args['related_products'] = array_filter( array_map( 'wc_get_product', wc_get_related_products( $product->get_id(), $args['posts_per_page'], $product->get_upsell_ids() ) ), 'wc_products_array_filter_visible' );
// Handle orderby.
$args['related_products'] = wc_products_array_orderby( $args['related_products'], $args['orderby'], $args['order'] );
// Set global loop values.
$woocommerce_loop['name'] = 'related';
$woocommerce_loop['columns'] = apply_filters( 'woocommerce_related_products_columns', $args['columns'] );
wc_get_template( 'single-product/related.php', $args ); wc_get_template( 'single-product/related.php', $args );
} }
} }
@ -1305,18 +1304,34 @@ if ( ! function_exists( 'woocommerce_upsell_display' ) ) {
/** /**
* Output product up sells. * Output product up sells.
* *
* @param int $posts_per_page (default: -1) * @param int $limit (default: -1)
* @param int $columns (default: 4) * @param int $columns (default: 4)
* @param string $orderby (default: 'rand') * @param string $orderby Supported values - rand, title, ID, date, modified, menu_order, price.
* @param string $order Sort direction.
*/ */
function woocommerce_upsell_display( $posts_per_page = '-1', $columns = 4, $orderby = 'rand' ) { function woocommerce_upsell_display( $limit = '-1', $columns = 4, $orderby = 'rand', $order = 'desc' ) {
$args = apply_filters( 'woocommerce_upsell_display_args', array( global $product, $woocommerce_loop;
'posts_per_page' => $posts_per_page,
'orderby' => apply_filters( 'woocommerce_upsells_orderby', $orderby ),
'columns' => $columns,
) );
wc_get_template( 'single-product/up-sells.php', $args ); // Get visble upsells then sort them at random.
$upsells = array_filter( array_map( 'wc_get_product', $product->get_upsell_ids() ), 'wc_products_array_filter_visible' );
// Handle orderby and limit results.
$orderby = apply_filters( 'woocommerce_upsells_orderby', $orderby );
$upsells = wc_products_array_orderby( $upsells, $orderby, $order );
$upsells = $limit > 0 ? array_slice( $upsells, 0, $limit ) : $upsells;
// Set global loop values.
$woocommerce_loop['name'] = 'up-sells';
$woocommerce_loop['columns'] = apply_filters( 'woocommerce_up_sells_columns', $columns );
wc_get_template( 'single-product/up-sells.php', apply_filters( 'woocommerce_upsell_display_args', array(
'upsells' => $upsells,
// Not used now, but used in previous version of up-sells.php.
'posts_per_page' => $limit,
'orderby' => $orderby,
'columns' => $columns,
) ) );
} }
} }
@ -1354,18 +1369,31 @@ if ( ! function_exists( 'woocommerce_cross_sell_display' ) ) {
/** /**
* Output the cart cross-sells. * Output the cart cross-sells.
* *
* @param int $posts_per_page (default: 2) * @param int $limit (default: 2)
* @param int $columns (default: 2) * @param int $columns (default: 2)
* @param string $orderby (default: 'rand') * @param string $orderby (default: 'rand')
* @param string $order (default: 'desc')
*/ */
function woocommerce_cross_sell_display( $posts_per_page = 2, $columns = 2, $orderby = 'rand' ) { function woocommerce_cross_sell_display( $limit = 2, $columns = 2, $orderby = 'rand', $order = 'desc' ) {
if ( is_checkout() ) { if ( is_checkout() ) {
return; return;
} }
// Get visble cross sells then sort them at random.
$cross_sells = array_filter( array_map( 'wc_get_product', WC()->cart->get_cross_sells() ), 'wc_products_array_filter_visible' );
// Handle orderby and limit results.
$orderby = apply_filters( 'woocommerce_cross_sells_orderby', $orderby );
$cross_sells = wc_products_array_orderby( $cross_sells, $orderby, $order );
$limit = apply_filters( 'woocommerce_cross_sells_total', $limit );
$cross_sells = $limit > 0 ? array_slice( $cross_sells, 0, $limit ) : $cross_sells;
wc_get_template( 'cart/cross-sells.php', array( wc_get_template( 'cart/cross-sells.php', array(
'posts_per_page' => $posts_per_page, 'cross_sells' => $cross_sells,
'orderby' => $orderby,
'columns' => $columns, // Not used now, but used in previous version of up-sells.php.
'posts_per_page' => $limit,
'orderby' => $orderby,
'columns' => $columns,
) ); ) );
} }
} }
@ -1817,7 +1845,6 @@ if ( ! function_exists( 'woocommerce_form_field' ) ) {
* @param string $key * @param string $key
* @param mixed $args * @param mixed $args
* @param string $value (default: null) * @param string $value (default: null)
* @todo This function needs to be broken up in smaller pieces
*/ */
function woocommerce_form_field( $key, $args, $value = null ) { function woocommerce_form_field( $key, $args, $value = null ) {
$defaults = array( $defaults = array(
@ -2157,7 +2184,7 @@ if ( ! function_exists( 'wc_dropdown_variation_attribute_options' ) ) {
if ( ! empty( $options ) ) { if ( ! empty( $options ) ) {
if ( $product && taxonomy_exists( $attribute ) ) { if ( $product && taxonomy_exists( $attribute ) ) {
// Get terms if this is a taxonomy - ordered. We need the names too. // Get terms if this is a taxonomy - ordered. We need the names too.
$terms = wc_get_product_terms( $product->id, $attribute, array( 'fields' => 'all' ) ); $terms = wc_get_product_terms( $product->get_id(), $attribute, array( 'fields' => 'all' ) );
foreach ( $terms as $term ) { foreach ( $terms as $term ) {
if ( in_array( $term->slug, $options ) ) { if ( in_array( $term->slug, $options ) ) {
@ -2450,3 +2477,87 @@ if ( ! function_exists( 'woocommerce_photoswipe' ) ) {
wc_get_template( 'single-product/photoswipe.php' ); wc_get_template( 'single-product/photoswipe.php' );
} }
} }
/**
* Outputs a list of product attributes for a product.
* @since 2.7.0
* @param WC_Product $product
*/
function wc_display_product_attributes( $product ) {
wc_get_template( 'single-product/product-attributes.php', array(
'product' => $product,
'attributes' => array_filter( $product->get_attributes(), 'wc_attributes_array_filter_visible' ),
'display_dimensions' => apply_filters( 'wc_product_enable_dimensions_display', $product->has_weight() || $product->has_dimensions() ),
) );
}
/**
* Get HTML to show product stock.
* @since 2.7.0
* @param WC_Product $product
* @return string
*/
function wc_get_stock_html( $product ) {
ob_start();
wc_get_template( 'single-product/stock.php', array(
'product' => $product,
) );
$html = ob_get_clean();
if ( has_filter( 'woocommerce_stock_html' ) ) {
_deprecated_function( 'The woocommerce_stock_html filter', '', 'woocommerce_get_stock_html' );
$html = apply_filters( 'woocommerce_stock_html', $html, $product->get_availability_text(), $product );
}
return apply_filters( 'woocommerce_get_stock_html', $html, $product );
}
/**
* Get the price suffix for a product if needed.
* @since 2.7.0
* @param WC_Product $product
* @param string $price
* @param integer $qty
* @return string
*/
function wc_get_price_suffix( $product, $price = '', $qty = 1 ) {
if ( ( $price_display_suffix = get_option( 'woocommerce_price_display_suffix' ) ) && wc_tax_enabled() ) {
$price = '' === $price ? $product->get_price() : $price;
$price_display_suffix = ' <small class="woocommerce-price-suffix">' . wp_kses_post( $price_display_suffix ) . '</small>';
$find = array(
'{price_including_tax}',
'{price_excluding_tax}',
);
$replace = array(
wc_price( wc_get_price_including_tax( $product, array( 'qty' => $qty, 'price' => $price ) ) ),
wc_price( wc_get_price_excluding_tax( $product, array( 'qty' => $qty, 'price' => $price ) ) ),
);
$price_display_suffix = str_replace( $find, $replace, $price_display_suffix );
} else {
$price_display_suffix = '';
}
return apply_filters( 'woocommerce_get_price_suffix', $price_display_suffix, $product );
}
/**
* Get HTML for ratings.
*
* @since 2.7.0
* @param float $rating Rating being shown.
* @return string
*/
function wc_get_rating_html( $rating ) {
if ( $rating > 0 ) {
$rating_html = '<div class="star-rating" title="' . sprintf( __( 'Rated %s out of 5', 'woocommerce' ), $rating ) . '">';
$rating_html .= '<span style="width:' . ( ( $rating / 5 ) * 100 ) . '%"><strong class="rating">' . $rating . '</strong> ' . __( 'out of 5', 'woocommerce' ) . '</span>';
$rating_html .= '</div>';
} else {
$rating_html = '';
}
return apply_filters( 'woocommerce_product_get_rating_html', $rating_html, $rating );
}

View File

@ -253,8 +253,6 @@ add_action( 'wp_upgrade', 'wc_taxonomy_metadata_migrate_data', 10, 2 );
* WC tables for storing term meta are @deprecated from WordPress 4.4 since 4.4 has its own table. * WC tables for storing term meta are @deprecated from WordPress 4.4 since 4.4 has its own table.
* This function serves as a wrapper, using the new table if present, or falling back to the WC table. * This function serves as a wrapper, using the new table if present, or falling back to the WC table.
* *
* @todo These functions should be deprecated with notices in a future WC version, allowing users a chance to upgrade WordPress.
*
* @param mixed $term_id * @param mixed $term_id
* @param string $meta_key * @param string $meta_key
* @param mixed $meta_value * @param mixed $meta_value
@ -271,7 +269,6 @@ function update_woocommerce_term_meta( $term_id, $meta_key, $meta_value, $prev_v
* WC tables for storing term meta are @deprecated from WordPress 4.4 since 4.4 has its own table. * WC tables for storing term meta are @deprecated from WordPress 4.4 since 4.4 has its own table.
* This function serves as a wrapper, using the new table if present, or falling back to the WC table. * This function serves as a wrapper, using the new table if present, or falling back to the WC table.
* *
* @todo These functions should be deprecated with notices in a future WC version, allowing users a chance to upgrade WordPress.
* @param mixed $term_id * @param mixed $term_id
* @param mixed $meta_key * @param mixed $meta_key
* @param mixed $meta_value * @param mixed $meta_value
@ -288,7 +285,6 @@ function add_woocommerce_term_meta( $term_id, $meta_key, $meta_value, $unique =
* WC tables for storing term meta are @deprecated from WordPress 4.4 since 4.4 has its own table. * WC tables for storing term meta are @deprecated from WordPress 4.4 since 4.4 has its own table.
* This function serves as a wrapper, using the new table if present, or falling back to the WC table. * This function serves as a wrapper, using the new table if present, or falling back to the WC table.
* *
* @todo These functions should be deprecated with notices in a future WC version, allowing users a chance to upgrade WordPress.
* @param mixed $term_id * @param mixed $term_id
* @param string $meta_key * @param string $meta_key
* @param string $meta_value (default: '') * @param string $meta_value (default: '')
@ -305,7 +301,6 @@ function delete_woocommerce_term_meta( $term_id, $meta_key, $meta_value = '', $d
* WC tables for storing term meta are @deprecated from WordPress 4.4 since 4.4 has its own table. * WC tables for storing term meta are @deprecated from WordPress 4.4 since 4.4 has its own table.
* This function serves as a wrapper, using the new table if present, or falling back to the WC table. * This function serves as a wrapper, using the new table if present, or falling back to the WC table.
* *
* @todo These functions should be deprecated with notices in a future WC version, allowing users a chance to upgrade WordPress.
* @param mixed $term_id * @param mixed $term_id
* @param string $key * @param string $key
* @param bool $single (default: true) * @param bool $single (default: true)
@ -485,14 +480,18 @@ add_filter( 'terms_clauses', 'wc_terms_clauses', 10, 3 );
/** /**
* Function for recounting product terms, ignoring hidden products. * Function for recounting product terms, ignoring hidden products.
*
* @param array $terms * @param array $terms
* @param string $taxonomy * @param string $taxonomy
* @param bool $callback * @param bool $callback
* @param bool $terms_are_term_taxonomy_ids * @param bool $terms_are_term_taxonomy_ids
*/ */
function _wc_term_recount( $terms, $taxonomy, $callback = true, $terms_are_term_taxonomy_ids = true ) { function _wc_term_recount( $terms, $taxonomy, $callback = true, $terms_are_term_taxonomy_ids = true ) {
global $wpdb; global $wpdb, $wc_allow_term_recount;
// Don't recount unless CRUD is calling this.
if ( empty( $wc_allow_term_recount ) ) {
return;
}
// Standard callback // Standard callback
if ( $callback ) { if ( $callback ) {
@ -500,7 +499,7 @@ function _wc_term_recount( $terms, $taxonomy, $callback = true, $terms_are_term_
} }
// Stock query // Stock query
if ( get_option( 'woocommerce_hide_out_of_stock_items' ) == 'yes' ) { if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
$stock_join = "LEFT JOIN {$wpdb->postmeta} AS meta_stock ON posts.ID = meta_stock.post_id"; $stock_join = "LEFT JOIN {$wpdb->postmeta} AS meta_stock ON posts.ID = meta_stock.post_id";
$stock_query = " $stock_query = "
AND meta_stock.meta_key = '_stock_status' AND meta_stock.meta_key = '_stock_status'

View File

@ -1001,3 +1001,29 @@ function wc_update_270_comment_type_index() {
$wpdb->query( "ALTER TABLE {$wpdb->comments} ADD INDEX woo_idx_comment_type (comment_type)" ); $wpdb->query( "ALTER TABLE {$wpdb->comments} ADD INDEX woo_idx_comment_type (comment_type)" );
} }
function wc_update_270_grouped_products() {
global $wpdb;
$parents = $wpdb->get_col( "SELECT DISTINCT( post_parent ) FROM {$wpdb->posts} WHERE post_parent > 0 AND post_type = 'product';" );
foreach ( $parents as $parent_id ) {
$parent = wc_get_product( $parent_id );
if ( $parent && $parent->is_type( 'grouped' ) ) {
$children_ids = get_posts( array(
'post_parent' => $parent_id,
'posts_per_page' => -1,
'post_type' => 'product',
'fields' => 'ids',
) );
add_post_meta( $parent_id, '_children', $children_ids, true );
}
}
}
function wc_update_270_settings() {
$woocommerce_shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
if ( '' === $woocommerce_shipping_tax_class ) {
update_option( 'woocommerce_shipping_tax_class', 'inherit' );
} elseif ( 'standard' === $woocommerce_shipping_tax_class ) {
update_option( 'woocommerce_shipping_tax_class', '' );
}
}

View File

@ -421,7 +421,7 @@ function wc_get_customer_available_downloads( $customer_id ) {
$product_id = intval( $result->product_id ); $product_id = intval( $result->product_id );
if ( ! $_product || $_product->id != $product_id ) { if ( ! $_product || $_product->get_id() != $product_id ) {
// new product // new product
$file_number = 0; $file_number = 0;
$_product = wc_get_product( $product_id ); $_product = wc_get_product( $product_id );
@ -437,7 +437,7 @@ function wc_get_customer_available_downloads( $customer_id ) {
// Download name will be 'Product Name' for products with a single downloadable file, and 'Product Name - File X' for products with multiple files // Download name will be 'Product Name' for products with a single downloadable file, and 'Product Name - File X' for products with multiple files
$download_name = apply_filters( $download_name = apply_filters(
'woocommerce_downloadable_product_name', 'woocommerce_downloadable_product_name',
$_product->get_title() . ' &ndash; ' . $download_file['name'], $_product->get_name() . ' &ndash; ' . $download_file['name'],
$_product, $_product,
$result->download_id, $result->download_id,
$file_number $file_number
@ -454,8 +454,8 @@ function wc_get_customer_available_downloads( $customer_id ) {
home_url( '/' ) home_url( '/' )
), ),
'download_id' => $result->download_id, 'download_id' => $result->download_id,
'product_id' => $_product->id, 'product_id' => $_product->get_id(),
'product_name' => $_product->get_title(), 'product_name' => $_product->get_name(),
'download_name' => $download_name, 'download_name' => $download_name,
'order_id' => $order->get_id(), 'order_id' => $order->get_id(),
'order_key' => $order->get_order_key(), 'order_key' => $order->get_order_key(),

View File

@ -73,13 +73,13 @@ class WC_Widget_Recent_Reviews extends WC_Widget {
$rating = intval( get_comment_meta( $comment->comment_ID, 'rating', true ) ); $rating = intval( get_comment_meta( $comment->comment_ID, 'rating', true ) );
$rating_html = $_product->get_rating_html( $rating ); $rating_html = wc_get_rating_html( $rating );
echo '<li><a href="' . esc_url( get_comment_link( $comment->comment_ID ) ) . '">'; echo '<li><a href="' . esc_url( get_comment_link( $comment->comment_ID ) ) . '">';
echo $_product->get_image(); echo $_product->get_image();
echo $_product->get_title() . '</a>'; echo $_product->get_name() . '</a>';
echo $rating_html; echo $rating_html;

View File

@ -186,6 +186,12 @@ Yes you can! Join in on our [GitHub repository](http://github.com/woocommerce/wo
* Removed last order from customers part of the API due to performance concerns - use orders endpoint instead. Other order data on the endpoint is now transient cached. * Removed last order from customers part of the API due to performance concerns - use orders endpoint instead. Other order data on the endpoint is now transient cached.
* Use all paid statuses in $customer->get_total_spent(). * Use all paid statuses in $customer->get_total_spent().
* CRUD - Optimised variable product sync. Upper/lower price meta is no longer stored, just the main prices, if a child has weight, and if a child has dimensions.
* CRUD - Removed WP_Query from up-sells.php and related.php and replaced with PHP foreach loop (since we already have the product IDs).
* CRUD - Optimised variable product sync. Upper/lower price meta is no longer stored, just the main prices, if a child has weight, and if a child has dimensions.
* CRUD - Removed WP_Query from up-sells.php and related.php and replaced with PHP foreach loop (since we already have the product IDs).
[See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce/master/CHANGELOG.txt). [See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce/master/CHANGELOG.txt).
== Upgrade Notice == == Upgrade Notice ==

View File

@ -78,9 +78,9 @@ do_action( 'woocommerce_before_cart' ); ?>
<td class="product-name" data-title="<?php _e( 'Product', 'woocommerce' ); ?>"> <td class="product-name" data-title="<?php _e( 'Product', 'woocommerce' ); ?>">
<?php <?php
if ( ! $product_permalink ) { if ( ! $product_permalink ) {
echo apply_filters( 'woocommerce_cart_item_name', $_product->get_title(), $cart_item, $cart_item_key ) . '&nbsp;'; echo apply_filters( 'woocommerce_cart_item_name', $_product->get_name(), $cart_item, $cart_item_key ) . '&nbsp;';
} else { } else {
echo apply_filters( 'woocommerce_cart_item_name', sprintf( '<a href="%s">%s</a>', esc_url( $product_permalink ), $_product->get_title() ), $cart_item, $cart_item_key ); echo apply_filters( 'woocommerce_cart_item_name', sprintf( '<a href="%s">%s</a>', esc_url( $product_permalink ), $_product->get_name() ), $cart_item, $cart_item_key );
} }
// Meta data // Meta data

View File

@ -13,34 +13,14 @@
* @see https://docs.woocommerce.com/document/template-structure/ * @see https://docs.woocommerce.com/document/template-structure/
* @author WooThemes * @author WooThemes
* @package WooCommerce/Templates * @package WooCommerce/Templates
* @version 1.6.4 * @version 2.7.0
*/ */
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
global $product, $woocommerce_loop; if ( $cross_sells ) : ?>
if ( ! $crosssells = WC()->cart->get_cross_sells() ) {
return;
}
$args = array(
'post_type' => 'product',
'ignore_sticky_posts' => 1,
'no_found_rows' => 1,
'posts_per_page' => apply_filters( 'woocommerce_cross_sells_total', $posts_per_page ),
'orderby' => $orderby,
'post__in' => $crosssells,
'meta_query' => WC()->query->get_meta_query(),
);
$products = new WP_Query( $args );
$woocommerce_loop['name'] = 'cross-sells';
$woocommerce_loop['columns'] = apply_filters( 'woocommerce_cross_sells_columns', $columns );
if ( $products->have_posts() ) : ?>
<div class="cross-sells"> <div class="cross-sells">
@ -48,11 +28,16 @@ if ( $products->have_posts() ) : ?>
<?php woocommerce_product_loop_start(); ?> <?php woocommerce_product_loop_start(); ?>
<?php while ( $products->have_posts() ) : $products->the_post(); ?> <?php foreach ( $cross_sells as $cross_sell ) : ?>
<?php wc_get_template_part( 'content', 'product' ); ?> <?php
$post_object = get_post( $cross_sell->get_id() );
<?php endwhile; // end of the loop. ?> setup_postdata( $GLOBALS['post'] =& $post_object );
wc_get_template_part( 'content', 'product' ); ?>
<?php endforeach; ?>
<?php woocommerce_product_loop_end(); ?> <?php woocommerce_product_loop_end(); ?>
@ -60,4 +45,4 @@ if ( $products->have_posts() ) : ?>
<?php endif; <?php endif;
wp_reset_query(); wp_reset_postdata();

View File

@ -38,7 +38,7 @@ if ( ! defined( 'ABSPATH' ) ) {
$product_id = apply_filters( 'woocommerce_cart_item_product_id', $cart_item['product_id'], $cart_item, $cart_item_key ); $product_id = apply_filters( 'woocommerce_cart_item_product_id', $cart_item['product_id'], $cart_item, $cart_item_key );
if ( $_product && $_product->exists() && $cart_item['quantity'] > 0 && apply_filters( 'woocommerce_widget_cart_item_visible', true, $cart_item, $cart_item_key ) ) { if ( $_product && $_product->exists() && $cart_item['quantity'] > 0 && apply_filters( 'woocommerce_widget_cart_item_visible', true, $cart_item, $cart_item_key ) ) {
$product_name = apply_filters( 'woocommerce_cart_item_name', $_product->get_title(), $cart_item, $cart_item_key ); $product_name = apply_filters( 'woocommerce_cart_item_name', $_product->get_name(), $cart_item, $cart_item_key );
$thumbnail = apply_filters( 'woocommerce_cart_item_thumbnail', $_product->get_image(), $cart_item, $cart_item_key ); $thumbnail = apply_filters( 'woocommerce_cart_item_thumbnail', $_product->get_image(), $cart_item, $cart_item_key );
$product_price = apply_filters( 'woocommerce_cart_item_price', WC()->cart->get_product_price( $_product ), $cart_item, $cart_item_key ); $product_price = apply_filters( 'woocommerce_cart_item_price', WC()->cart->get_product_price( $_product ), $cart_item, $cart_item_key );
$product_permalink = apply_filters( 'woocommerce_cart_item_permalink', $_product->is_visible() ? $_product->get_permalink( $cart_item ) : '', $cart_item, $cart_item_key ); $product_permalink = apply_filters( 'woocommerce_cart_item_permalink', $_product->is_visible() ? $_product->get_permalink( $cart_item ) : '', $cart_item, $cart_item_key );

Some files were not shown because too many files have changed in this diff Show More