From 75ca794bba4ac827a5a134d46b9494dcb54b7af8 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 21 Nov 2012 18:07:45 +0000 Subject: [PATCH] Separate classes per product type, with new get_product function and hooks to change the classes which get loaded. #1497 --- admin/post-types/product.php | 6 +- .../writepanel-order_downloads.php | 2 +- .../writepanels/writepanel-product_data.php | 2 +- classes/class-wc-cart.php | 10 +- classes/class-wc-customer.php | 2 +- classes/class-wc-order.php | 9 +- classes/class-wc-product-external.php | 72 ++ classes/class-wc-product-grouped.php | 290 +++++++ classes/class-wc-product-variable.php | 524 ++++++++++++ classes/class-wc-product-variation.php | 10 +- classes/class-wc-product.php | 754 ++++-------------- .../class.shareyourcart-wp-woocommerce.php | 2 +- shortcodes/shortcode-init.php | 6 +- templates/loop/add-to-cart.php | 2 +- templates/order/order-details.php | 6 +- widgets/widget-recent_reviews.php | 2 +- woocommerce-ajax.php | 11 +- woocommerce-core-functions.php | 46 ++ woocommerce-functions.php | 6 +- woocommerce.php | 7 +- 20 files changed, 1128 insertions(+), 641 deletions(-) create mode 100644 classes/class-wc-product-external.php create mode 100644 classes/class-wc-product-grouped.php create mode 100644 classes/class-wc-product-variable.php diff --git a/admin/post-types/product.php b/admin/post-types/product.php index e246c2abc0b..59c75c4c432 100644 --- a/admin/post-types/product.php +++ b/admin/post-types/product.php @@ -115,7 +115,7 @@ add_filter('manage_edit-product_columns', 'woocommerce_edit_product_columns'); */ function woocommerce_custom_product_columns( $column ) { global $post, $woocommerce; - $product = new WC_Product($post->ID); + $product = get_product($post); switch ($column) { case "thumb" : @@ -712,7 +712,7 @@ function woocommerce_admin_product_quick_edit_save( $post_id, $post ) { global $woocommerce, $wpdb; - $product = new WC_Product( $post_id ); + $product = get_product( $post ); // Save fields if(isset($_POST['_sku'])) update_post_meta($post_id, '_sku', esc_html(stripslashes($_POST['_sku']))); @@ -1003,7 +1003,7 @@ function woocommerce_admin_product_bulk_edit_save( $post_id, $post ) { global $woocommerce, $wpdb; - $product = new WC_Product( $post_id ); + $product = get_product( $post ); // Save fields if ( ! empty( $_REQUEST['change_weight'] ) && isset( $_REQUEST['_weight'] ) ) diff --git a/admin/post-types/writepanels/writepanel-order_downloads.php b/admin/post-types/writepanels/writepanel-order_downloads.php index a4009629aee..e443f2fe9bb 100644 --- a/admin/post-types/writepanels/writepanel-order_downloads.php +++ b/admin/post-types/writepanels/writepanel-order_downloads.php @@ -36,7 +36,7 @@ function woocommerce_order_downloads_meta_box() { if ( $download_permissions && sizeof( $download_permissions ) > 0 ) foreach ( $download_permissions as $download ) { if ( ! $product || $product->id != $download->product_id ) { - $product = new WC_Product( absint( $download->product_id ) ); + $product = get_product( absint( $download->product_id ) ); $file_count = $loop = 0; } diff --git a/admin/post-types/writepanels/writepanel-product_data.php b/admin/post-types/writepanels/writepanel-product_data.php index c6874cc541d..94fddb17c14 100644 --- a/admin/post-types/writepanels/writepanel-product_data.php +++ b/admin/post-types/writepanels/writepanel-product_data.php @@ -820,7 +820,7 @@ function woocommerce_process_product_meta( $post_id, $post ) { else update_post_meta( $post_id, '_price', stripslashes( $_POST['_regular_price'] ) ); - if ( $date_from && strtotime( $date_from ) < strtotime( 'NOW', current_time( 'timestamp' ) ) ) + if ( $_POST['_sale_price'] != '' && $date_from && strtotime( $date_from ) < strtotime( 'NOW', current_time( 'timestamp' ) ) ) update_post_meta( $post_id, '_price', stripslashes($_POST['_sale_price']) ); if ( $date_to && strtotime( $date_to ) < strtotime( 'NOW', current_time( 'timestamp' ) ) ) { diff --git a/classes/class-wc-cart.php b/classes/class-wc-cart.php index a29a06c693a..4a8cf029606 100644 --- a/classes/class-wc-cart.php +++ b/classes/class-wc-cart.php @@ -125,10 +125,7 @@ class WC_Cart { foreach ( $cart as $key => $values ) { - if ( $values['variation_id'] > 0 ) - $_product = new WC_Product_Variation( $values['variation_id'] ); - else - $_product = new WC_Product( $values['product_id'] ); + $_product = get_product( $values['variation_id'] ? $values['variation_id'] : $values['product_id'] ); if ( $_product->exists() && $values['quantity'] > 0 ) { @@ -738,10 +735,7 @@ class WC_Cart { // See if this product and its options is already in the cart $cart_item_key = $this->find_product_in_cart( $cart_id ); - if ( $variation_id > 0 ) - $product_data = new WC_Product_Variation( $variation_id ); - else - $product_data = new WC_Product( $product_id ); + $product_data = get_product( $variation_id ? $variation_id : $product_id ); // Force quantity to 1 if sold individually if ( $product_data->is_sold_individually() ) diff --git a/classes/class-wc-customer.php b/classes/class-wc-customer.php index 087ce880bd8..1780edbd9fe 100644 --- a/classes/class-wc-customer.php +++ b/classes/class-wc-customer.php @@ -488,7 +488,7 @@ class WC_Customer { if ( ! $_product || $_product->id != $result->product_id ) : // new product $file_number = 0; - $_product = new WC_Product( $result->product_id ); + $_product = get_product( $result->product_id ); endif; if ( ! $_product->exists() ) continue; diff --git a/classes/class-wc-order.php b/classes/class-wc-order.php index 257bcb710b5..66bb87e20df 100644 --- a/classes/class-wc-order.php +++ b/classes/class-wc-order.php @@ -850,12 +850,7 @@ class WC_Order { * @return WC_Product */ function get_product_from_item( $item ) { - - if (isset($item['variation_id']) && $item['variation_id']>0) : - $_product = new WC_Product_Variation( $item['variation_id'] ); - else : - $_product = new WC_Product( $item['product_id'] ); - endif; + $_product = get_product( $item['variation_id'] ? $item['variation_id'] : $item['product_id'] ); return $_product; @@ -1126,7 +1121,7 @@ class WC_Order { global $wpdb; $download_file = $variation_id > 0 ? $variation_id : $product_id; - $_product = new WC_Product( $download_file ); + $_product = get_product( $download_file ); $user_email = $this->billing_email; diff --git a/classes/class-wc-product-external.php b/classes/class-wc-product-external.php new file mode 100644 index 00000000000..3704cc9a3f7 --- /dev/null +++ b/classes/class-wc-product-external.php @@ -0,0 +1,72 @@ +id = absint( $product->ID ); + $this->post = $product; + } else { + $this->id = absint( $product ); + } + + $this->product_type = 'external'; + $this->product_custom_fields = get_post_custom( $this->id ); + + // Load data from custom fields + $this->load_product_data( array( + 'sku' => '', + 'downloadable' => 'no', + 'virtual' => 'no', + 'price' => '', + 'visibility' => 'hidden', + 'stock' => 0, + 'stock_status' => 'instock', + 'backorders' => 'no', + 'manage_stock' => 'no', + 'sale_price' => '', + 'regular_price' => '', + 'weight' => '', + 'length' => '', + 'width' => '', + 'height' => '', + 'tax_status' => 'taxable', + 'tax_class' => '', + 'upsell_ids' => array(), + 'crosssell_ids' => array(), + 'sale_price_dates_from' => '', + 'sale_price_dates_to' => '', + 'featured' => 'no' + ) ); + + $this->check_sale_price(); + } + + /** + * Returns false if the product cannot be bought. + * + * @access public + * @return cool + */ + function is_purchasable() { + return apply_filters( 'woocommerce_is_purchasable', false, $this ); + } +} \ No newline at end of file diff --git a/classes/class-wc-product-grouped.php b/classes/class-wc-product-grouped.php new file mode 100644 index 00000000000..a98c11d36f6 --- /dev/null +++ b/classes/class-wc-product-grouped.php @@ -0,0 +1,290 @@ +id = absint( $product->ID ); + $this->post = $product; + } else { + $this->id = absint( $product ); + } + + $this->product_type = 'grouped'; + $this->product_custom_fields = get_post_custom( $this->id ); + + // Load data from custom fields + $this->load_product_data( array( + 'sku' => '', + 'downloadable' => 'no', + 'virtual' => 'no', + 'price' => '', + 'visibility' => 'hidden', + 'stock' => 0, + 'stock_status' => 'instock', + 'backorders' => 'no', + 'manage_stock' => 'no', + 'sale_price' => '', + 'regular_price' => '', + 'weight' => '', + 'length' => '', + 'width' => '', + 'height' => '', + 'tax_status' => 'taxable', + 'tax_class' => '', + 'upsell_ids' => array(), + 'crosssell_ids' => array(), + 'sale_price_dates_from' => '', + 'sale_price_dates_to' => '', + 'featured' => 'no' + ) ); + + $this->check_sale_price(); + } + + /** + * Get total stock. + * + * This is the stock of parent and children combined. + * + * @access public + * @return int + */ + function get_total_stock() { + + if ( is_null( $this->total_stock ) ) { + + $transient_name = 'wc_product_total_stock_' . $this->id; + + if ( false === ( $this->total_stock = get_transient( $transient_name ) ) ) { + $this->total_stock = $this->stock; + + if ( sizeof( $this->get_children() ) > 0 ) { + foreach ($this->get_children() as $child_id) { + $stock = get_post_meta( $child_id, '_stock', true ); + + if ( $stock != '' ) { + $this->total_stock += intval( $stock ); + } + } + } + + set_transient( $transient_name, $this->total_stock ); + } + } + + return apply_filters( 'woocommerce_stock_amount', $this->total_stock ); + } + + /** + * Return the products children posts. + * + * @access public + * @return array + */ + function get_children() { + + if ( ! is_array( $this->children ) ) { + + $this->children = array(); + + $transient_name = 'wc_product_children_ids_' . $this->id; + + if ( false === ( $this->children = get_transient( $transient_name ) ) ) { + + $this->children = get_posts( 'post_parent=' . $this->id . '&post_type=product&orderby=menu_order&order=ASC&fields=ids&post_status=any&numberposts=-1' ); + + set_transient( $transient_name, $this->children ); + + } + } + + return (array) $this->children; + } + + + /** + * get_child function. + * + * @access public + * @param mixed $child_id + * @return object WC_Product or WC_Product_variation + */ + function get_child( $child_id ) { + return get_product( $child_id ); + } + + + /** + * Returns whether or not the product has any child product. + * + * @access public + * @return bool + */ + function has_child() { + return sizeof( $this->get_children() ) ? true : false; + } + + + /** + * Returns whether or not the product is on sale. + * + * @access public + * @return bool + */ + function is_on_sale() { + if ($this->has_child()) : + + foreach ($this->get_children() as $child_id) : + $sale_price = get_post_meta( $child_id, '_sale_price', true ); + if ( $sale_price!=="" && $sale_price >= 0 ) return true; + endforeach; + + else : + + if ( $this->sale_price && $this->sale_price==$this->price ) return true; + + endif; + return false; + } + + + /** + * Returns false if the product cannot be bought. + * + * @access public + * @return cool + */ + function is_purchasable() { + return apply_filters( 'woocommerce_is_purchasable', false, $this ); + } + + + /** + * Returns the price in html format. + * + * @access public + * @param string $price (default: '') + * @return string + */ + function get_price_html( $price = '' ) { + + $child_prices = array(); + + foreach ( $this->get_children() as $child_id ) $child_prices[] = get_post_meta( $child_id, '_price', true ); + + $child_prices = array_unique( $child_prices ); + + if ( ! empty( $child_prices ) ) { + $min_price = min( $child_prices ); + } else { + $min_price = ''; + } + + if ( sizeof( $child_prices ) > 1 ) $price .= $this->get_price_html_from_text(); + + $price .= woocommerce_price( $min_price ); + + $price = apply_filters( 'woocommerce_grouped_price_html', $price, $this ); + + return apply_filters( 'woocommerce_get_price_html', $price, $this ); + } + + + /** + * Checks sale data to see if the product is due to go on sale/sale has expired, and updates the main price. + * + * @access public + * @return void + */ + function check_sale_price() { + + if ( $this->sale_price_dates_from && $this->sale_price_dates_from < current_time('timestamp') ) { + + if ( $this->sale_price && $this->price !== $this->sale_price ) { + + // Update price + $this->price = $this->sale_price; + update_post_meta( $this->id, '_price', $this->price ); + + // Grouped product prices and sale status are affected by children + $this->grouped_product_sync(); + } + + } + + if ( $this->sale_price_dates_to && $this->sale_price_dates_to < current_time('timestamp') ) { + + if ( $this->regular_price && $this->price !== $this->regular_price ) { + + $this->price = $this->regular_price; + update_post_meta( $this->id, '_price', $this->price ); + + // Sale has expired - clear the schedule boxes + update_post_meta( $this->id, '_sale_price', '' ); + update_post_meta( $this->id, '_sale_price_dates_from', '' ); + update_post_meta( $this->id, '_sale_price_dates_to', '' ); + + // Grouped product prices and sale status are affected by children + $this->grouped_product_sync(); + } + + } + } + + + /** + * Sync grouped products with the childs lowest price (so they can be sorted by price accurately). + * + * @access public + * @return void + */ + function grouped_product_sync() { + global $wpdb, $woocommerce; + $post_parent = $wpdb->get_var( $wpdb->prepare( "SELECT post_parent FROM $wpdb->posts WHERE ID = %d;"), $this->id ); + + if (!$post_parent) return; + + $children_by_price = get_posts( array( + 'post_parent' => $post_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( $post_parent, '_price', $child_price ); + endforeach; + endif; + + $woocommerce->clear_product_transients( $this->id ); + } +} \ No newline at end of file diff --git a/classes/class-wc-product-variable.php b/classes/class-wc-product-variable.php new file mode 100644 index 00000000000..346cb3dcfae --- /dev/null +++ b/classes/class-wc-product-variable.php @@ -0,0 +1,524 @@ +id = absint( $product->ID ); + $this->post = $product; + } else { + $this->id = absint( $product ); + } + + $this->product_type = 'variable'; + $this->product_custom_fields = get_post_custom( $this->id ); + + // Load data from custom fields + $this->load_product_data( array( + 'sku' => '', + 'downloadable' => 'no', + 'virtual' => 'no', + 'price' => '', + 'visibility' => 'hidden', + 'stock' => 0, + 'stock_status' => 'instock', + 'backorders' => 'no', + 'manage_stock' => 'no', + 'sale_price' => '', + 'regular_price' => '', + 'weight' => '', + 'length' => '', + 'width' => '', + 'height' => '', + 'tax_status' => 'taxable', + 'tax_class' => '', + 'upsell_ids' => array(), + 'crosssell_ids' => array(), + 'sale_price_dates_from' => '', + 'sale_price_dates_to' => '', + 'featured' => 'no', + 'min_variation_price' => '', + 'max_variation_price' => '', + 'min_variation_regular_price' => '', + 'max_variation_regular_price' => '', + 'min_variation_sale_price' => '', + 'max_variation_sale_price' => '', + ) ); + + $this->check_sale_price(); + } + + /** + * Get total stock. + * + * This is the stock of parent and children combined. + * + * @access public + * @return int + */ + function get_total_stock() { + + if ( is_null( $this->total_stock ) ) { + + $transient_name = 'wc_product_total_stock_' . $this->id; + + if ( false === ( $this->total_stock = get_transient( $transient_name ) ) ) { + $this->total_stock = $this->stock; + + if ( sizeof( $this->get_children() ) > 0 ) { + foreach ($this->get_children() as $child_id) { + $stock = get_post_meta( $child_id, '_stock', true ); + + if ( $stock != '' ) { + $this->total_stock += intval( $stock ); + } + } + } + + set_transient( $transient_name, $this->total_stock ); + } + } + + return apply_filters( 'woocommerce_stock_amount', $this->total_stock ); + } + + /** + * Reduce stock level of the product. + * + * @access public + * @param int $by (default: 1) Amount to reduce by. + * @return int Stock + */ + function reduce_stock( $by = 1 ) { + global $woocommerce; + + if ( $this->managing_stock() ) { + $this->stock = $this->stock - $by; + $this->total_stock = $this->get_total_stock() - $by; + update_post_meta($this->id, '_stock', $this->stock); + + // Out of stock attribute + if ($this->managing_stock() && !$this->backorders_allowed() && $this->get_total_stock()<=0) : + update_post_meta($this->id, '_stock_status', 'outofstock'); + endif; + + $woocommerce->clear_product_transients( $this->id ); // Clear transient + + return apply_filters( 'woocommerce_stock_amount', $this->stock ); + } + } + + + /** + * Increase stock level of the product. + * + * @access public + * @param int $by (default: 1) Amount to increase by + * @return int Stock + */ + function increase_stock( $by = 1 ) { + global $woocommerce; + + if ($this->managing_stock()) : + $this->stock = $this->stock + $by; + $this->total_stock = $this->get_total_stock() + $by; + update_post_meta($this->id, '_stock', $this->stock); + + // Out of stock attribute + if ($this->managing_stock() && ($this->backorders_allowed() || $this->get_total_stock()>0)) : + update_post_meta($this->id, '_stock_status', 'instock'); + endif; + + $woocommerce->clear_product_transients( $this->id ); // Clear transient + + return apply_filters( 'woocommerce_stock_amount', $this->stock ); + endif; + } + + + /** + * Return the products children posts. + * + * @access public + * @return array + */ + function get_children() { + + if (!is_array($this->children)) : + + $this->children = array(); + + $transient_name = 'wc_product_children_ids_' . $this->id; + + if ( false === ( $this->children = get_transient( $transient_name ) ) ) : + + $this->children = get_posts( 'post_parent=' . $this->id . '&post_type=product_variation&orderby=menu_order&order=ASC&fields=ids&post_status=any&numberposts=-1' ); + + set_transient( $transient_name, $this->children ); + + endif; + + endif; + + return (array) $this->children; + } + + + /** + * get_child function. + * + * @access public + * @param mixed $child_id + * @return object WC_Product or WC_Product_variation + */ + function get_child( $child_id ) { + return get_product( $child_id, $this->id, $this->product_custom_fields ); + } + + + /** + * Returns whether or not the product has any child product. + * + * @access public + * @return bool + */ + function has_child() { + return sizeof( $this->get_children() ) ? true : false; + } + + + /** + * Returns whether or not the product is on sale. + * + * @access public + * @return bool + */ + function is_on_sale() { + if ($this->has_child()) : + + foreach ($this->get_children() as $child_id) : + $sale_price = get_post_meta( $child_id, '_sale_price', true ); + if ( $sale_price!=="" && $sale_price >= 0 ) return true; + endforeach; + + else : + + if ( $this->sale_price && $this->sale_price==$this->price ) return true; + + endif; + return false; + } + + /** + * Returns the price in html format. + * + * @access public + * @param string $price (default: '') + * @return string + */ + function get_price_html( $price = '' ) { + + // Ensure variation prices are synced with variations + if ( $this->min_variation_price === '' || $this->min_variation_regular_price === '' ) { + $this->variable_product_sync(); + $this->price = $this->min_variation_price; + } + + // Get the price + if ($this->price > 0) { + if ( $this->is_on_sale() && isset( $this->min_variation_price ) && $this->min_variation_regular_price !== $this->get_price() ) { + + if ( ! $this->min_variation_price || $this->min_variation_price !== $this->max_variation_price ) + $price .= $this->get_price_html_from_text(); + + $price .= $this->get_price_html_from_to( $this->min_variation_regular_price, $this->get_price() ); + + $price = apply_filters( 'woocommerce_variable_sale_price_html', $price, $this ); + + } else { + + if ( ! $this->min_variation_price || $this->min_variation_price !== $this->max_variation_price ) + $price .= $this->get_price_html_from_text(); + + $price .= woocommerce_price( $this->get_price() ); + + $price = apply_filters('woocommerce_variable_price_html', $price, $this); + + } + } elseif ($this->price === '' ) { + + $price = apply_filters('woocommerce_variable_empty_price_html', '', $this); + + } elseif ($this->price == 0 ) { + + if ( $this->is_on_sale() && isset( $this->min_variation_regular_price ) && $this->min_variation_regular_price !== $this->get_price() ) { + + if ( ! $this->min_variation_price || $this->min_variation_price !== $this->max_variation_price ) + $price .= $this->get_price_html_from_text(); + + $price .= $this->get_price_html_from_to( $this->min_variation_regular_price, __( 'Free!', 'woocommerce' ) ); + + $price = apply_filters( 'woocommerce_variable_free_sale_price_html', $price, $this ); + + } else { + + if ( ! $this->min_variation_price || $this->min_variation_price !== $this->max_variation_price ) + $price .= $this->get_price_html_from_text(); + + $price .= __( 'Free!', 'woocommerce' ); + + $price = apply_filters( 'woocommerce_variable_free_price_html', $price, $this ); + + } + + } + + return apply_filters( 'woocommerce_get_price_html', $price, $this ); + } + + + /** + * Return an array of attributes used for variations, as well as their possible values. + * + * @access public + * @return array of attributes and their available values + */ + function get_variation_attributes() { + + $variation_attributes = array(); + + if ( ! $this->has_child() ) + return $variation_attributes; + + $attributes = $this->get_attributes(); + + foreach ( $attributes as $attribute ) { + if ( ! $attribute['is_variation'] ) + continue; + + $values = array(); + $attribute_field_name = 'attribute_' . sanitize_title( $attribute['name'] ); + + foreach ( $this->get_children() as $child_id ) { + + if ( get_post_status( $child_id ) != 'publish' ) + continue; // Disabled + + $child = $this->get_child( $child_id ); + + $child_variation_attributes = $child->get_variation_attributes(); + + foreach ( $child_variation_attributes as $name => $value ) + if ( $name == $attribute_field_name ) + $values[] = $value; + } + + // empty value indicates that all options for given attribute are available + if ( in_array( '', $values ) ) { + + $values = array(); + + // Get all options + if ( $attribute['is_taxonomy'] ) { + $post_terms = wp_get_post_terms( $this->id, $attribute['name'] ); + foreach ( $post_terms as $term ) + $values[] = $term->slug; + } else { + $values = explode( '|', $attribute['value'] ); + } + + $values = array_unique( array_map( 'trim', $values ) ); + + // Order custom attributes (non taxonomy) as defined + } else { + + if ( ! $attribute['is_taxonomy'] ) { + $options = array_map( 'trim', explode( '|', $attribute['value'] ) ); + $values = array_intersect( $options, $values ); + } + + } + + $variation_attributes[ $attribute['name'] ] = array_unique( $values ); + } + + return $variation_attributes; + } + + /** + * If set, get the default attributes for a variable product. + * + * @access public + * @return array + */ + function get_variation_default_attributes() { + + $default = isset( $this->product_custom_fields['_default_attributes'][0] ) ? $this->product_custom_fields['_default_attributes'][0] : ''; + + return apply_filters( 'woocommerce_product_default_attributes', (array) maybe_unserialize( $default ), $this ); + } + + /** + * Get an array of available variations for the current product. + * + * @access public + * @return array + */ + function get_available_variations() { + + $available_variations = array(); + + foreach ( $this->get_children() as $child_id ) { + + $variation = $this->get_child( $child_id ); + + if ( $variation instanceof WC_Product_Variation ) { + + if ( get_post_status( $variation->get_variation_id() ) != 'publish' || ! $variation->is_visible() ) + continue; // Disabled or hidden + + $variation_attributes = $variation->get_variation_attributes(); + $availability = $variation->get_availability(); + $availability_html = empty( $availability['availability'] ) ? '' : apply_filters( 'woocommerce_stock_html', '

'. wp_kses_post( $availability['availability'] ).'

', wp_kses_post( $availability['availability'] ) ); + + if ( has_post_thumbnail( $variation->get_variation_id() ) ) { + $attachment_id = get_post_thumbnail_id( $variation->get_variation_id() ); + + $attachment = wp_get_attachment_image_src( $attachment_id, apply_filters( 'single_product_large_thumbnail_size', 'shop_single' ) ); + $image = $attachment ? current( $attachment ) : ''; + + $attachment = wp_get_attachment_image_src( $attachment_id, 'full' ); + $image_link = $attachment ? current( $attachment ) : ''; + + $image_title = get_the_title( $attachment_id ); + } else { + $image = $image_link = $image_title = ''; + } + + $available_variations[] = apply_filters( 'woocommerce_available_variation', array( + 'variation_id' => $child_id, + 'attributes' => $variation_attributes, + 'image_src' => $image, + 'image_link' => $image_link, + 'image_title' => $image_title, + 'price_html' => $this->min_variation_price != $this->max_variation_price ? '' . $variation->get_price_html() . '' : '', + 'availability_html' => $availability_html, + 'sku' => $variation->get_sku(), + 'weight' => $variation->get_weight() . ' ' . esc_attr( get_option('woocommerce_weight_unit' ) ), + 'dimensions' => $variation->get_dimensions(), + 'min_qty' => 1, + 'max_qty' => $this->backorders_allowed() ? '' : $variation->stock, + 'backorders_allowed' => $this->backorders_allowed(), + 'is_in_stock' => $variation->is_in_stock(), + 'is_downloadable' => $variation->is_downloadable() , + 'is_virtual' => $variation->is_virtual(), + 'is_sold_individually' => $variation->is_sold_individually() ? 'yes' : 'no', + ), $this, $variation ); + } + } + + return $available_variations; + } + + + /** + * Sync variable product prices with the childs lowest/highest prices. + * + * @access public + * @return void + */ + function variable_product_sync() { + global $woocommerce; + + $children = get_posts( array( + 'post_parent' => $this->id, + 'posts_per_page'=> -1, + 'post_type' => 'product_variation', + 'fields' => 'ids', + 'post_status' => 'publish' + )); + + $this->min_variation_price = $this->min_variation_regular_price = $this->min_variation_sale_price = $this->max_variation_price = $this->max_variation_regular_price = $this->max_variation_sale_price = ''; + + if ($children) { + foreach ( $children as $child ) { + + $child_price = get_post_meta( $child, '_price', true ); + $child_regular_price = get_post_meta( $child, '_regular_price', true ); + $child_sale_price = get_post_meta( $child, '_sale_price', true ); + + // Regular prices + if ( ! is_numeric( $this->min_variation_regular_price ) || $child_regular_price < $this->min_variation_regular_price ) + $this->min_variation_regular_price = $child_regular_price; + + if ( ! is_numeric( $this->max_variation_regular_price ) || $child_regular_price > $this->max_variation_regular_price ) + $this->max_variation_regular_price = $child_regular_price; + + // Sale prices + if ( $child_price == $child_sale_price ) { + if ( $child_sale_price !== '' && ( ! is_numeric( $this->min_variation_sale_price ) || $child_sale_price < $this->min_variation_sale_price ) ) + $this->min_variation_sale_price = $child_sale_price; + + if ( $child_sale_price !== '' && ( ! is_numeric( $this->max_variation_sale_price ) || $child_sale_price > $this->max_variation_sale_price ) ) + $this->max_variation_sale_price = $child_sale_price; + } + } + + $this->min_variation_price = $this->min_variation_sale_price === '' || $this->min_variation_regular_price < $this->min_variation_sale_price ? $this->min_variation_regular_price : $this->min_variation_sale_price; + + $this->max_variation_price = $this->max_variation_sale_price === '' || $this->max_variation_regular_price > $this->max_variation_sale_price ? $this->max_variation_regular_price : $this->max_variation_sale_price; + } + + update_post_meta( $this->id, '_price', $this->min_variation_price ); + update_post_meta( $this->id, '_min_variation_price', $this->min_variation_price ); + update_post_meta( $this->id, '_max_variation_price', $this->max_variation_price ); + update_post_meta( $this->id, '_min_variation_regular_price', $this->min_variation_regular_price ); + update_post_meta( $this->id, '_max_variation_regular_price', $this->max_variation_regular_price ); + update_post_meta( $this->id, '_min_variation_sale_price', $this->min_variation_sale_price ); + update_post_meta( $this->id, '_max_variation_sale_price', $this->max_variation_sale_price ); + + $woocommerce->clear_product_transients( $this->id ); + } +} \ No newline at end of file diff --git a/classes/class-wc-product-variation.php b/classes/class-wc-product-variation.php index d9afeac7ed4..b3980af9605 100644 --- a/classes/class-wc-product-variation.php +++ b/classes/class-wc-product-variation.php @@ -65,9 +65,13 @@ class WC_Product_Variation extends WC_Product { * @param array $parent_custom_fields (default: '') Array of the parent products meta data * @return void */ - function __construct( $variation_id, $parent_id = '', $parent_custom_fields = '' ) { + function __construct( $variation, $parent_id = '', $parent_custom_fields = '' ) { - $this->variation_id = intval( $variation_id ); + if ( is_object( $variation ) ) { + $this->variation_id = absint( $variation->ID ); + } else { + $this->variation_id = absint( $variation ); + } $product_custom_fields = get_post_custom( $this->variation_id ); @@ -324,7 +328,7 @@ class WC_Product_Variation extends WC_Product { if ( ! $this->is_in_stock() ) { // Check parent - $parent_product = new WC_Product( $this->id ); + $parent_product = get_product( $this->id ); // Only continue if the parent has backorders off if ( ! $parent_product->backorders_allowed() && $parent_product->get_total_stock() <= 0 ) { diff --git a/classes/class-wc-product.php b/classes/class-wc-product.php index 5c431521fe4..900f121dcf5 100644 --- a/classes/class-wc-product.php +++ b/classes/class-wc-product.php @@ -9,6 +9,8 @@ * @package WooCommerce/Classes * @author WooThemes */ +if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly + class WC_Product { /** @var int The product (post) ID. */ @@ -20,9 +22,6 @@ class WC_Product { /** @var array Array of product attributes. */ var $attributes; - /** @var array Array of child products/posts/variations. */ - var $children; - /** @var object The actual post object. */ var $post; @@ -86,33 +85,12 @@ class WC_Product { /** @var string The product's type (simple, variable etc). */ var $product_type; - /** @var string The product's total stock, including that of its children. */ - var $total_stock; - /** @var string Date a sale starts. */ var $sale_price_dates_from; /** @var string Data a sale ends. */ var $sale_price_dates_to; - /** @var string Used for variation prices. */ - var $min_variation_price; - - /** @var string Used for variation prices. */ - var $max_variation_price; - - /** @var string Used for variation prices. */ - var $min_variation_regular_price; - - /** @var string Used for variation prices. */ - var $max_variation_regular_price; - - /** @var string Used for variation prices. */ - var $min_variation_sale_price; - - /** @var string Used for variation prices. */ - var $max_variation_sale_price; - /** @var string "Yes" for featured products. */ var $featured; @@ -125,19 +103,27 @@ class WC_Product { /** @var string Formatted LxWxH. */ var $dimensions; + /** - * Loads all product data from custom fields. - * - * @param int $id ID of the product to load + * __construct function. + * + * @access public + * @param mixed $product */ - function __construct( $id ) { - - $this->id = absint( $id ); - + function __construct( $product ) { + + if ( is_object( $product ) ) { + $this->id = absint( $product->ID ); + $this->post = $product; + } else { + $this->id = absint( $product ); + } + + $this->product_type = 'simple'; $this->product_custom_fields = get_post_custom( $this->id ); - - // Define the data we're going to load: Key => Default value - $load_data = array( + + // Load data from custom fields + $this->load_product_data( array( 'sku' => '', 'downloadable' => 'no', 'virtual' => 'no', @@ -151,7 +137,7 @@ class WC_Product { 'regular_price' => '', 'weight' => '', 'length' => '', - 'width' => '', + 'width' => '', 'height' => '', 'tax_status' => 'taxable', 'tax_class' => '', @@ -159,32 +145,27 @@ class WC_Product { 'crosssell_ids' => array(), 'sale_price_dates_from' => '', 'sale_price_dates_to' => '', - 'min_variation_price' => '', - 'max_variation_price' => '', - 'min_variation_regular_price' => '', - 'max_variation_regular_price' => '', - 'min_variation_sale_price' => '', - 'max_variation_sale_price' => '', 'featured' => 'no' - ); + ) ); - // Load the data from the custom fields - foreach ($load_data as $key => $default) $this->$key = (isset($this->product_custom_fields['_' . $key][0]) && $this->product_custom_fields['_' . $key][0]!=='') ? $this->product_custom_fields['_' . $key][0] : $default; - - // Get product type - $transient_name = 'wc_product_type_' . $this->id; - - if ( false === ( $this->product_type = get_transient( $transient_name ) ) ) : - $terms = wp_get_object_terms( $id, 'product_type', array('fields' => 'names') ); - $this->product_type = (isset($terms[0])) ? sanitize_title($terms[0]) : 'simple'; - set_transient( $transient_name, $this->product_type ); - endif; - - // Check sale dates $this->check_sale_price(); } - - + + + /** + * Load the data from the custom fields + * + * @access public + * @param mixed $fields + * @return void + */ + function load_product_data( $fields ) { + if ( $fields ) + foreach ( $fields as $key => $default ) + $this->$key = isset( $this->product_custom_fields[ '_' . $key ][0] ) && $this->product_custom_fields[ '_' . $key ][0] !== '' ? $this->product_custom_fields[ '_' . $key ][0] : $default; + } + + /** * Get SKU (Stock-keeping unit) - product unique ID. * @@ -196,89 +177,29 @@ class WC_Product { /** - * Get total stock. + * Returns number of items available for sale. * - * This is the stock of parent and children combined. + * @access public + * @return int + */ + function get_stock_quantity() { + if ( ! $this->managing_stock() ) + return ''; + + return apply_filters( 'woocommerce_stock_amount', $this->stock ); + } + + + /** + * Get total stock. * * @access public * @return int */ function get_total_stock() { - - if ( is_null( $this->total_stock ) ) { - - $transient_name = 'wc_product_total_stock_' . $this->id; - - if ( false === ( $this->total_stock = get_transient( $transient_name ) ) ) { - $this->total_stock = $this->stock; - - if ( sizeof( $this->get_children() ) > 0 ) { - foreach ($this->get_children() as $child_id) { - $stock = get_post_meta( $child_id, '_stock', true ); - - if ( $stock != '' ) { - $this->total_stock += intval( $stock ); - } - } - } - - set_transient( $transient_name, $this->total_stock ); - } - } - - return apply_filters( 'woocommerce_stock_amount', $this->total_stock ); + return $this->get_stock_quantity(); } - /** - * Return the products children posts. - * - * @access public - * @return array - */ - function get_children() { - - if (!is_array($this->children)) : - - $this->children = array(); - - if ($this->is_type('variable') || $this->is_type('grouped')) : - - $child_post_type = ($this->is_type('variable')) ? 'product_variation' : 'product'; - - $transient_name = 'wc_product_children_ids_' . $this->id; - - if ( false === ( $this->children = get_transient( $transient_name ) ) ) : - - $this->children = get_posts( 'post_parent=' . $this->id . '&post_type=' . $child_post_type . '&orderby=menu_order&order=ASC&fields=ids&post_status=any&numberposts=-1' ); - - set_transient( $transient_name, $this->children ); - - endif; - - endif; - - endif; - - return (array) $this->children; - } - - - /** - * get_child function. - * - * @access public - * @param mixed $child_id - * @return object WC_Product or WC_Product_variation - */ - function get_child( $child_id ) { - if ($this->is_type('variable')) : - $child = new WC_Product_Variation( $child_id, $this->id, $this->product_custom_fields ); - else : - $child = new WC_Product( $child_id ); - endif; - return $child; - } - /** * Reduce stock level of the product. @@ -290,20 +211,18 @@ class WC_Product { function reduce_stock( $by = 1 ) { global $woocommerce; - if ($this->managing_stock()) : + if ( $this->managing_stock() ) { $this->stock = $this->stock - $by; - $this->total_stock = $this->get_total_stock() - $by; - update_post_meta($this->id, '_stock', $this->stock); + update_post_meta( $this->id, '_stock', $this->stock ); // Out of stock attribute - if ($this->managing_stock() && !$this->backorders_allowed() && $this->get_total_stock()<=0) : - update_post_meta($this->id, '_stock_status', 'outofstock'); - endif; + if ( $this->managing_stock() && ! $this->backorders_allowed() && $this->get_total_stock() <= 0 ) + update_post_meta( $this->id, '_stock_status', 'outofstock' ); $woocommerce->clear_product_transients( $this->id ); // Clear transient - return apply_filters( 'woocommerce_stock_amount', $this->stock ); - endif; + return $this->get_stock_quantity(); + } } @@ -317,20 +236,18 @@ class WC_Product { function increase_stock( $by = 1 ) { global $woocommerce; - if ($this->managing_stock()) : + if ( $this->managing_stock() ) { $this->stock = $this->stock + $by; - $this->total_stock = $this->get_total_stock() + $by; - update_post_meta($this->id, '_stock', $this->stock); + update_post_meta( $this->id, '_stock', $this->stock ); // Out of stock attribute - if ($this->managing_stock() && ($this->backorders_allowed() || $this->get_total_stock()>0)) : + if ( $this->managing_stock() && ( $this->backorders_allowed() || $this->get_total_stock() > 0 ) ) update_post_meta($this->id, '_stock_status', 'instock'); - endif; $woocommerce->clear_product_transients( $this->id ); // Clear transient - return apply_filters( 'woocommerce_stock_amount', $this->stock ); - endif; + return $this->get_stock_quantity(); + } } @@ -344,8 +261,7 @@ class WC_Product { * @return bool */ function is_type( $type ) { - if ( is_array( $type ) && in_array( $this->product_type, $type ) ) return true; - if ( $this->product_type == $type ) return true; + if ( $this->product_type == $type || ( is_array( $type ) && in_array( $this->product_type, $type ) ) ) return true; return false; } @@ -357,7 +273,7 @@ class WC_Product { * @return bool */ function is_downloadable() { - if ( $this->downloadable == 'yes' ) return true; else return false; + return $this->downloadable == 'yes' ? true : false; } @@ -390,14 +306,16 @@ class WC_Product { */ function get_file_download_path( $download_id ) { - $file_path = ''; - $file_paths = apply_filters( 'woocommerce_file_download_paths', get_post_meta( $this->id, '_file_paths', true ), $this->id, null, null ); + $file_paths = isset( $this->product_custom_fields['_file_paths'][0] ) ? $this->product_custom_fields['_file_paths'][0] : ''; + $file_paths = apply_filters( 'woocommerce_file_download_paths', $file_paths, $this->id, null, null ); if ( ! $download_id && count( $file_paths ) == 1 ) { // backwards compatibility for old-style download URLs and template files $file_path = array_shift( $file_paths ); } elseif ( isset( $file_paths[ $download_id ] ) ) { $file_path = $file_paths[ $download_id ]; + } else { + $file_path = ''; } // allow overriding based on the particular file being requested @@ -412,7 +330,7 @@ class WC_Product { * @return bool */ function is_virtual() { - if ( $this->virtual == 'yes' ) return true; else return false; + return $this->virtual == 'yes' ? true : false; } @@ -423,9 +341,10 @@ class WC_Product { * @return bool */ function needs_shipping() { - if ( $this->is_virtual() ) return false; else return true; + return $this->is_virtual() ? false : true; } - + + /** * Check if a product is sold individually (no quantities) * @@ -443,7 +362,17 @@ class WC_Product { return apply_filters( 'woocommerce_is_sold_individually', $return, $this ); } - + /** + * get_children function. + * + * @access public + * @return bool + */ + function get_children() { + return array(); + } + + /** * Returns whether or not the product has any child product. * @@ -451,7 +380,7 @@ class WC_Product { * @return bool */ function has_child() { - return sizeof( $this->get_children() ) ? true : false; + return false; } @@ -463,8 +392,8 @@ class WC_Product { */ function exists() { global $wpdb; - if ( $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE ID = %d LIMIT 1;", $this->id ) ) > 0 ) return true; - return false; + + return $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE ID = %d LIMIT 1;", $this->id ) ) > 0 ? true : false; } @@ -475,8 +404,7 @@ class WC_Product { * @return bool */ function is_taxable() { - if ($this->tax_status=='taxable' && get_option('woocommerce_calc_taxes')=='yes') return true; - return false; + return $this->tax_status == 'taxable' && get_option('woocommerce_calc_taxes') == 'yes' ? true : false; } @@ -487,8 +415,7 @@ class WC_Product { * @return bool */ function is_shipping_taxable() { - if ($this->tax_status=='taxable' || $this->tax_status=='shipping') return true; - return false; + return $this->tax_status=='taxable' || $this->tax_status=='shipping' ? true : false; } @@ -584,8 +511,7 @@ class WC_Product { * @return bool */ function backorders_allowed() { - if ($this->backorders=='yes' || $this->backorders=='notify') return true; - return false; + return $this->backorders=='yes' || $this->backorders=='notify' ? true : false; } @@ -596,8 +522,7 @@ class WC_Product { * @return bool */ function backorders_require_notification() { - if ($this->managing_stock() && $this->backorders=='notify') return true; - return false; + return $this->managing_stock() && $this->backorders=='notify' ? true : false; } @@ -609,25 +534,10 @@ class WC_Product { * @return bool */ function is_on_backorder( $qty_in_cart = 0 ) { - if ( $this->managing_stock() && $this->backorders_allowed() && ( $this->get_total_stock() - $qty_in_cart ) < 0 ) return true; - return false; + return $this->managing_stock() && $this->backorders_allowed() && ( $this->get_total_stock() - $qty_in_cart ) < 0 ? true : false; } - /** - * Returns number of items available for sale. - * - * @access public - * @return int - */ - function get_stock_quantity() { - if ( get_option( 'woocommerce_manage_stock' ) == 'no' || ! $this->managing_stock() ) - return ''; - - return apply_filters( 'woocommerce_stock_amount', $this->stock ); - } - - /** * Returns whether or not the product has enough stock for the order. * @@ -636,17 +546,7 @@ class WC_Product { * @return bool */ function has_enough_stock( $quantity ) { - - if (!$this->managing_stock()) return true; - - if ($this->backorders_allowed()) return true; - - if ($this->stock >= $quantity) : - return true; - endif; - - return false; - + return ! $this->managing_stock() || $this->backorders_allowed() || $this->stock >= $quantity ? true : false; } @@ -718,7 +618,7 @@ class WC_Product { endif; endif; - return apply_filters( 'woocommerce_get_availability', array( 'availability' => $availability, 'class' => $class), $this ); + return apply_filters( 'woocommerce_get_availability', array( 'availability' => $availability, 'class' => $class ), $this ); } @@ -729,7 +629,7 @@ class WC_Product { * @return bool */ function is_featured() { - if ($this->featured=='yes') return true; else return false; + return $this->featured == 'yes' ? true : false; } @@ -744,19 +644,19 @@ class WC_Product { $visible = true; // Out of stock visibility - if (get_option('woocommerce_hide_out_of_stock_items')=='yes' && !$this->is_in_stock()) $visible = false; + if ( get_option( 'woocommerce_hide_out_of_stock_items' ) == 'yes' && ! $this->is_in_stock() ) $visible = false; // visibility setting - elseif ($this->visibility=='hidden') $visible = false; - elseif ($this->visibility=='visible') $visible = true; + elseif ( $this->visibility == 'hidden' ) $visible = false; + elseif ( $this->visibility == 'visible' ) $visible = true; // Visibility in loop - elseif ($this->visibility=='search' && is_search()) $visible = true; - elseif ($this->visibility=='search' && !is_search()) $visible = false; - elseif ($this->visibility=='catalog' && is_search()) $visible = false; - elseif ($this->visibility=='catalog' && !is_search()) $visible = true; + elseif ( $this->visibility == 'search' && is_search() ) $visible = true; + elseif ( $this->visibility == 'search' && ! is_search() ) $visible = false; + elseif ( $this->visibility == 'catalog' && is_search() ) $visible = false; + elseif ( $this->visibility == 'catalog' && ! is_search() ) $visible = true; - return apply_filters('woocommerce_product_is_visible', $visible, $this->id); + return apply_filters( 'woocommerce_product_is_visible', $visible, $this->id ); } @@ -767,19 +667,7 @@ class WC_Product { * @return bool */ function is_on_sale() { - if ($this->has_child()) : - - foreach ($this->get_children() as $child_id) : - $sale_price = get_post_meta( $child_id, '_sale_price', true ); - if ( $sale_price!=="" && $sale_price >= 0 ) return true; - endforeach; - - else : - - if ( $this->sale_price && $this->sale_price==$this->price ) return true; - - endif; - return false; + return $this->sale_price && $this->sale_price == $this->price ? true : false; } @@ -790,7 +678,7 @@ class WC_Product { * @return string */ function get_weight() { - if ($this->weight) return $this->weight; + if ( $this->weight ) return $this->weight; } @@ -845,10 +733,6 @@ class WC_Product { if ( ! $this->exists() ) $purchasable = false; - // External and grouped products cannot be bought - elseif ( $this->is_type( array( 'grouped', 'external' ) ) ) - $purchasable = false; - // Other products types need a price to be set elseif ( $this->get_price() === '' ) $purchasable = false; @@ -889,7 +773,7 @@ class WC_Product { * @return string */ function get_tax_class() { - return apply_filters('woocommerce_product_tax_class', $this->tax_class, $this); + return apply_filters( 'woocommerce_product_tax_class', $this->tax_class, $this ); } @@ -912,118 +796,41 @@ class WC_Product { * @return string */ function get_price_html( $price = '' ) { - if ( $this->is_type( 'grouped' ) ) { - $child_prices = array(); + if ( $this->price > 0 ) { + if ( $this->is_on_sale() && isset( $this->regular_price ) ) { - foreach ( $this->get_children() as $child_id ) $child_prices[] = get_post_meta( $child_id, '_price', true ); + $price .= $this->get_price_html_from_to( $this->regular_price, $this->get_price() ); - $child_prices = array_unique( $child_prices ); + $price = apply_filters( 'woocommerce_sale_price_html', $price, $this ); - if ( ! empty( $child_prices ) ) { - $min_price = min( $child_prices ); } else { - $min_price = ''; - } - if ( sizeof( $child_prices ) > 1 ) $price .= $this->get_price_html_from_text(); + $price .= woocommerce_price( $this->get_price() ); - $price .= woocommerce_price( $min_price ); - - $price = apply_filters( 'woocommerce_grouped_price_html', $price, $this ); - - } elseif ( $this->is_type( 'variable' ) ) { - - // Ensure variation prices are synced with variations - if ( $this->min_variation_price === '' || $this->min_variation_regular_price === '' ) { - $this->variable_product_sync(); - $this->price = $this->min_variation_price; - } - - // Get the price - if ($this->price > 0) { - if ( $this->is_on_sale() && isset( $this->min_variation_price ) && $this->min_variation_regular_price !== $this->get_price() ) { - - if ( ! $this->min_variation_price || $this->min_variation_price !== $this->max_variation_price ) - $price .= $this->get_price_html_from_text(); - - $price .= $this->get_price_html_from_to( $this->min_variation_regular_price, $this->get_price() ); - - $price = apply_filters( 'woocommerce_variable_sale_price_html', $price, $this ); - - } else { - - if ( ! $this->min_variation_price || $this->min_variation_price !== $this->max_variation_price ) - $price .= $this->get_price_html_from_text(); - - $price .= woocommerce_price( $this->get_price() ); - - $price = apply_filters('woocommerce_variable_price_html', $price, $this); - - } - } elseif ($this->price === '' ) { - - $price = apply_filters('woocommerce_variable_empty_price_html', '', $this); - - } elseif ($this->price == 0 ) { - - if ( $this->is_on_sale() && isset( $this->min_variation_regular_price ) && $this->min_variation_regular_price !== $this->get_price() ) { - - if ( ! $this->min_variation_price || $this->min_variation_price !== $this->max_variation_price ) - $price .= $this->get_price_html_from_text(); - - $price .= $this->get_price_html_from_to( $this->min_variation_regular_price, __( 'Free!', 'woocommerce' ) ); - - $price = apply_filters( 'woocommerce_variable_free_sale_price_html', $price, $this ); - - } else { - - if ( ! $this->min_variation_price || $this->min_variation_price !== $this->max_variation_price ) - $price .= $this->get_price_html_from_text(); - - $price .= __( 'Free!', 'woocommerce' ); - - $price = apply_filters( 'woocommerce_variable_free_price_html', $price, $this ); - - } + $price = apply_filters( 'woocommerce_price_html', $price, $this ); } - } else { - if ( $this->price > 0 ) { - if ( $this->is_on_sale() && isset( $this->regular_price ) ) { + } elseif ($this->price === '' ) { - $price .= $this->get_price_html_from_to( $this->regular_price, $this->get_price() ); + $price = apply_filters( 'woocommerce_empty_price_html', '', $this ); - $price = apply_filters( 'woocommerce_sale_price_html', $price, $this ); + } elseif ($this->price == 0 ) { - } else { + if ( $this->is_on_sale() && isset( $this->regular_price ) ) { - $price .= woocommerce_price( $this->get_price() ); + $price .= $this->get_price_html_from_to( $this->regular_price, __( 'Free!', 'woocommerce' ) ); - $price = apply_filters( 'woocommerce_price_html', $price, $this ); + $price = apply_filters( 'woocommerce_free_sale_price_html', $price, $this ); - } - } elseif ($this->price === '' ) { + } else { - $price = apply_filters( 'woocommerce_empty_price_html', '', $this ); + $price = __( 'Free!', 'woocommerce' ); - } elseif ($this->price == 0 ) { - - if ( $this->is_on_sale() && isset( $this->regular_price ) ) { - - $price .= $this->get_price_html_from_to( $this->regular_price, __( 'Free!', 'woocommerce' ) ); - - $price = apply_filters( 'woocommerce_free_sale_price_html', $price, $this ); - - } else { - - $price = __( 'Free!', 'woocommerce' ); - - $price = apply_filters( 'woocommerce_free_price_html', $price, $this ); - - } + $price = apply_filters( 'woocommerce_free_price_html', $price, $this ); } + } return apply_filters( 'woocommerce_get_price_html', $price, $this ); @@ -1061,10 +868,12 @@ class WC_Product { */ function get_rating_html( $location = '' ) { - if ($location) $location = '_'.$location; - $star_size = apply_filters('woocommerce_star_rating_size'.$location, 16); + if ( $location ) + $location = '_' . $location; + + $star_size = apply_filters( 'woocommerce_star_rating_size' . $location, 16 ); - if ( false === ( $average_rating = get_transient( 'wc_average_rating_' . $this->id ) ) ) : + if ( false === ( $average_rating = get_transient( 'wc_average_rating_' . $this->id ) ) ) { global $wpdb; @@ -1085,21 +894,18 @@ class WC_Product { AND comment_approved = '1' "), $this->id ); - if ( $count>0 ) : + if ( $count > 0 ) $average_rating = number_format($ratings / $count, 2); - else : + else $average_rating = ''; - endif; set_transient( 'wc_average_rating_' . $this->id, $average_rating ); + } - endif; - - if ( $average_rating>0 ) : - return '
'.$average_rating.' '.__( 'out of 5', 'woocommerce' ).'
'; - else : + if ( $average_rating > 0 ) + return '
' . $average_rating . ' ' . __( 'out of 5', 'woocommerce' ) . '
'; + else return ''; - endif; } @@ -1162,10 +968,13 @@ class WC_Product { * @return string */ function get_shipping_class() { - if ( ! $this->shipping_class ) : + if ( ! $this->shipping_class ) { $classes = get_the_terms( $this->id, 'product_shipping_class' ); - if ($classes && !is_wp_error($classes)) $this->shipping_class = current($classes)->slug; else $this->shipping_class = ''; - endif; + if ( $classes && ! is_wp_error( $classes ) ) + $this->shipping_class = current( $classes )->slug; + else + $this->shipping_class = ''; + } return $this->shipping_class; } @@ -1292,7 +1101,6 @@ class WC_Product { $this->attributes = maybe_unserialize( maybe_unserialize( $this->product_custom_fields['_product_attributes'][0] )); else $this->attributes = array(); - } return (array) $this->attributes; @@ -1306,11 +1114,12 @@ class WC_Product { * @return mixed */ function has_attributes() { - if (sizeof($this->get_attributes())>0) : - foreach ($this->get_attributes() as $attribute) : - if (isset($attribute['is_visible']) && $attribute['is_visible']) return true; - endforeach; - endif; + if ( sizeof( $this->get_attributes() ) > 0 ) { + foreach ( $this->get_attributes() as $attribute ) { + if ( isset( $attribute['is_visible'] ) && $attribute['is_visible'] ) + return true; + } + } return false; } @@ -1322,8 +1131,7 @@ class WC_Product { * @return bool */ function enable_dimensions_display() { - if (get_option('woocommerce_enable_dimension_product_attributes')=='yes') return true; - return false; + return get_option( 'woocommerce_enable_dimension_product_attributes' ) == 'yes' ? true : false; } @@ -1334,8 +1142,7 @@ class WC_Product { * @return bool */ function has_dimensions() { - if ($this->get_dimensions()) return true; - return false; + return $this->get_dimensions() ? true : false; } @@ -1346,8 +1153,7 @@ class WC_Product { * @return bool */ function has_weight() { - if ($this->get_weight()) return true; - return false; + return $this->get_weight() ? true : false; } @@ -1358,18 +1164,18 @@ class WC_Product { * @return string */ function get_dimensions() { - if (!$this->dimensions) : + if ( ! $this->dimensions ) : $this->dimensions = ''; // Show length - if ($this->length) { + if ( $this->length ) { $this->dimensions = $this->length; // Show width also - if ($this->width) { - $this->dimensions .= ' × '.$this->width; + if ( $this->width ) { + $this->dimensions .= ' x ' . $this->width; // Show height also - if ($this->height) { - $this->dimensions .= ' × '.$this->height; + if ( $this->height ) { + $this->dimensions .= ' x ' . $this->height; } } // Append the unit @@ -1387,156 +1193,12 @@ class WC_Product { * @return void */ function list_attributes() { - woocommerce_get_template('single-product/product-attributes.php', array( + woocommerce_get_template( 'single-product/product-attributes.php', array( 'product' => $this - )); + ) ); } - /** - * Return an array of attributes used for variations, as well as their possible values. - * - * @access public - * @return array of attributes and their available values - */ - function get_variation_attributes() { - - $variation_attributes = array(); - - if ( ! $this->is_type('variable') || ! $this->has_child() ) - return $variation_attributes; - - $attributes = $this->get_attributes(); - - foreach ( $attributes as $attribute ) { - if ( ! $attribute['is_variation'] ) - continue; - - $values = array(); - $attribute_field_name = 'attribute_' . sanitize_title( $attribute['name'] ); - - foreach ( $this->get_children() as $child_id ) { - - if ( get_post_status( $child_id ) != 'publish' ) - continue; // Disabled - - $child = $this->get_child( $child_id ); - - $child_variation_attributes = $child->get_variation_attributes(); - - foreach ( $child_variation_attributes as $name => $value ) - if ( $name == $attribute_field_name ) - $values[] = $value; - } - - // empty value indicates that all options for given attribute are available - if ( in_array( '', $values ) ) { - - $values = array(); - - // Get all options - if ( $attribute['is_taxonomy'] ) { - $post_terms = wp_get_post_terms( $this->id, $attribute['name'] ); - foreach ( $post_terms as $term ) - $values[] = $term->slug; - } else { - $values = explode( '|', $attribute['value'] ); - } - - $values = array_unique( array_map( 'trim', $values ) ); - - // Order custom attributes (non taxonomy) as defined - } else { - - if ( ! $attribute['is_taxonomy'] ) { - $options = array_map( 'trim', explode( '|', $attribute['value'] ) ); - $values = array_intersect( $options, $values ); - } - - } - - $variation_attributes[ $attribute['name'] ] = array_unique( $values ); - } - - return $variation_attributes; - } - - /** - * If set, get the default attributes for a variable product. - * - * @access public - * @return array - */ - function get_variation_default_attributes() { - - $default = isset( $this->product_custom_fields['_default_attributes'][0] ) ? $this->product_custom_fields['_default_attributes'][0] : ''; - - return apply_filters( 'woocommerce_product_default_attributes', (array) maybe_unserialize( $default ), $this ); - } - - /** - * Get an array of available variations for the current product. - * - * @access public - * @return array - */ - function get_available_variations() { - - $available_variations = array(); - - foreach ( $this->get_children() as $child_id ) { - - $variation = $this->get_child( $child_id ); - - if ( $variation instanceof WC_Product_Variation ) { - - if ( get_post_status( $variation->get_variation_id() ) != 'publish' || ! $variation->is_visible() ) - continue; // Disabled or hidden - - $variation_attributes = $variation->get_variation_attributes(); - $availability = $variation->get_availability(); - $availability_html = empty( $availability['availability'] ) ? '' : apply_filters( 'woocommerce_stock_html', '

'. wp_kses_post( $availability['availability'] ).'

', wp_kses_post( $availability['availability'] ) ); - - if ( has_post_thumbnail( $variation->get_variation_id() ) ) { - $attachment_id = get_post_thumbnail_id( $variation->get_variation_id() ); - - $attachment = wp_get_attachment_image_src( $attachment_id, apply_filters( 'single_product_large_thumbnail_size', 'shop_single' ) ); - $image = $attachment ? current( $attachment ) : ''; - - $attachment = wp_get_attachment_image_src( $attachment_id, 'full' ); - $image_link = $attachment ? current( $attachment ) : ''; - - $image_title = get_the_title( $attachment_id ); - } else { - $image = $image_link = $image_title = ''; - } - - $available_variations[] = apply_filters( 'woocommerce_available_variation', array( - 'variation_id' => $child_id, - 'attributes' => $variation_attributes, - 'image_src' => $image, - 'image_link' => $image_link, - 'image_title' => $image_title, - 'price_html' => $this->min_variation_price != $this->max_variation_price ? '' . $variation->get_price_html() . '' : '', - 'availability_html' => $availability_html, - 'sku' => $variation->get_sku(), - 'weight' => $variation->get_weight() . ' ' . esc_attr( get_option('woocommerce_weight_unit' ) ), - 'dimensions' => $variation->get_dimensions(), - 'min_qty' => 1, - 'max_qty' => $this->backorders_allowed() ? '' : $variation->stock, - 'backorders_allowed' => $this->backorders_allowed(), - 'is_in_stock' => $variation->is_in_stock(), - 'is_downloadable' => $variation->is_downloadable() , - 'is_virtual' => $variation->is_virtual(), - 'is_sold_individually' => $variation->is_sold_individually() ? 'yes' : 'no', - ), $this, $variation ); - } - } - - return $available_variations; - } - - /** * Returns the main product image * @@ -1568,17 +1230,14 @@ class WC_Product { * @return void */ function check_sale_price() { - + if ( $this->sale_price_dates_from && $this->sale_price_dates_from < current_time('timestamp') ) { if ( $this->sale_price && $this->price !== $this->sale_price ) { - + // Update price $this->price = $this->sale_price; update_post_meta( $this->id, '_price', $this->price ); - - // Grouped product prices and sale status are affected by children - $this->grouped_product_sync(); } } @@ -1594,103 +1253,8 @@ class WC_Product { update_post_meta( $this->id, '_sale_price', '' ); update_post_meta( $this->id, '_sale_price_dates_from', '' ); update_post_meta( $this->id, '_sale_price_dates_to', '' ); - - // Grouped product prices and sale status are affected by children - $this->grouped_product_sync(); } } } - - - /** - * Sync grouped products with the childs lowest price (so they can be sorted by price accurately). - * - * @access public - * @return void - */ - function grouped_product_sync() { - global $wpdb, $woocommerce; - $post_parent = $wpdb->get_var( $wpdb->prepare( "SELECT post_parent FROM $wpdb->posts WHERE ID = %d;"), $this->id ); - - if (!$post_parent) return; - - $children_by_price = get_posts( array( - 'post_parent' => $post_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( $post_parent, '_price', $child_price ); - endforeach; - endif; - - $woocommerce->clear_product_transients( $this->id ); - } - - - /** - * Sync variable product prices with the childs lowest/highest prices. - * - * @access public - * @return void - */ - function variable_product_sync() { - global $woocommerce; - - $children = get_posts( array( - 'post_parent' => $this->id, - 'posts_per_page'=> -1, - 'post_type' => 'product_variation', - 'fields' => 'ids', - 'post_status' => 'publish' - )); - - $this->min_variation_price = $this->min_variation_regular_price = $this->min_variation_sale_price = $this->max_variation_price = $this->max_variation_regular_price = $this->max_variation_sale_price = ''; - - if ($children) { - foreach ( $children as $child ) { - - $child_price = get_post_meta( $child, '_price', true ); - $child_regular_price = get_post_meta( $child, '_regular_price', true ); - $child_sale_price = get_post_meta( $child, '_sale_price', true ); - - // Regular prices - if ( ! is_numeric( $this->min_variation_regular_price ) || $child_regular_price < $this->min_variation_regular_price ) - $this->min_variation_regular_price = $child_regular_price; - - if ( ! is_numeric( $this->max_variation_regular_price ) || $child_regular_price > $this->max_variation_regular_price ) - $this->max_variation_regular_price = $child_regular_price; - - // Sale prices - if ( $child_price == $child_sale_price ) { - if ( $child_sale_price !== '' && ( ! is_numeric( $this->min_variation_sale_price ) || $child_sale_price < $this->min_variation_sale_price ) ) - $this->min_variation_sale_price = $child_sale_price; - - if ( $child_sale_price !== '' && ( ! is_numeric( $this->max_variation_sale_price ) || $child_sale_price > $this->max_variation_sale_price ) ) - $this->max_variation_sale_price = $child_sale_price; - } - } - - $this->min_variation_price = $this->min_variation_sale_price === '' || $this->min_variation_regular_price < $this->min_variation_sale_price ? $this->min_variation_regular_price : $this->min_variation_sale_price; - - $this->max_variation_price = $this->max_variation_sale_price === '' || $this->max_variation_regular_price > $this->max_variation_sale_price ? $this->max_variation_regular_price : $this->max_variation_sale_price; - } - - update_post_meta( $this->id, '_price', $this->min_variation_price ); - update_post_meta( $this->id, '_min_variation_price', $this->min_variation_price ); - update_post_meta( $this->id, '_max_variation_price', $this->max_variation_price ); - update_post_meta( $this->id, '_min_variation_regular_price', $this->min_variation_regular_price ); - update_post_meta( $this->id, '_max_variation_regular_price', $this->max_variation_regular_price ); - update_post_meta( $this->id, '_min_variation_sale_price', $this->min_variation_sale_price ); - update_post_meta( $this->id, '_max_variation_sale_price', $this->max_variation_sale_price ); - - $woocommerce->clear_product_transients( $this->id ); - } } \ No newline at end of file diff --git a/classes/integrations/shareyourcart/class.shareyourcart-wp-woocommerce.php b/classes/integrations/shareyourcart/class.shareyourcart-wp-woocommerce.php index d9b3ae4b1a6..61d6f6d5063 100644 --- a/classes/integrations/shareyourcart/class.shareyourcart-wp-woocommerce.php +++ b/classes/integrations/shareyourcart/class.shareyourcart-wp-woocommerce.php @@ -192,7 +192,7 @@ class ShareYourCartWooCommerce extends ShareYourCartWordpressPlugin{ } private function _getProductDetails($product_id){ - $product = new WC_Product($product_id); + $product = get_product($product_id); //WooCommerce actually echoes the image ob_start(); diff --git a/shortcodes/shortcode-init.php b/shortcodes/shortcode-init.php index 66add6fae3f..b198650117f 100644 --- a/shortcodes/shortcode-init.php +++ b/shortcodes/shortcode-init.php @@ -415,11 +415,11 @@ function woocommerce_product_add_to_cart( $atts ) { } elseif ($product_data->post_type=='product_variation') { - $product = new WC_Product( $product_data->post_parent ); + $product = get_product( $product_data->post_parent ); $GLOBALS['product'] = $product; - $variation = new WC_Product_Variation( $product_data->ID ); + $variation = get_product( $product_data ); ob_start(); ?> @@ -474,7 +474,7 @@ function woocommerce_product_add_to_cart_url( $atts ){ if ($product_data->post_type!=='product') return; - $_product = new WC_Product( $product_data->ID ); + $_product = get_product( $product_data ); return esc_url( $_product->add_to_cart_url() ); } diff --git a/templates/loop/add-to-cart.php b/templates/loop/add-to-cart.php index 265fa8894b5..aed058d1849 100644 --- a/templates/loop/add-to-cart.php +++ b/templates/loop/add-to-cart.php @@ -11,7 +11,7 @@ if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly global $product; -if ( ! $product->is_purchasable() && ! in_array( $product->product_type, array( 'external', 'grouped' ) ) ) return; +if ( ! $product->is_purchasable() ) return; ?> is_in_stock() ) : ?> diff --git a/templates/order/order-details.php b/templates/order/order-details.php index feeee344b94..43c1914b40c 100755 --- a/templates/order/order-details.php +++ b/templates/order/order-details.php @@ -40,11 +40,7 @@ $order = new WC_Order( $order_id ); foreach($order->get_items() as $item) : - if (isset($item['variation_id']) && $item['variation_id'] > 0) : - $_product = new WC_Product_Variation( $item['variation_id'] ); - else : - $_product = new WC_Product( $item['product_id'] ); - endif; + $_product = get_product( $item['variation_id'] ? $item['variation_id'] : $item['product_id'] ); echo ' diff --git a/widgets/widget-recent_reviews.php b/widgets/widget-recent_reviews.php index 57127958b1a..d878580d46f 100644 --- a/widgets/widget-recent_reviews.php +++ b/widgets/widget-recent_reviews.php @@ -80,7 +80,7 @@ class WooCommerce_Widget_Recent_Reviews extends WP_Widget { foreach ( (array) $comments as $comment) { - $_product = new WC_Product( $comment->comment_post_ID ); + $_product = get_product( $comment->comment_post_ID ); $star_size = intval( apply_filters( 'woocommerce_star_rating_size_recent_reviews', 16 ) ); diff --git a/woocommerce-ajax.php b/woocommerce-ajax.php index a5b4123cf76..25236766adb 100644 --- a/woocommerce-ajax.php +++ b/woocommerce-ajax.php @@ -513,7 +513,7 @@ function woocommerce_link_all_variations() { $variations = array(); - $_product = new WC_Product( $post_id ); + $_product = get_product( $post_id ); // Put variation attributes into an array foreach ( $_product->get_attributes() as $attribute ) { @@ -696,7 +696,7 @@ function woocommerce_grant_access_to_download() { $file_count = 0; $order = new WC_Order( $order_id ); - $product = new WC_Product( $product_id ); + $product = get_product( $product_id ); $user_email = sanitize_email( $order->billing_email ); @@ -839,10 +839,7 @@ function woocommerce_ajax_add_order_item() { if ( ! $post || ( $post->post_type !== 'product' && $post->post_type !== 'product_variation' ) ) die(); - if ( $post->post_type != "product" ) - $_product = new WC_Product_Variation( $post->ID ); - else - $_product = new WC_Product( $post->ID ); + $_product = get_product( $post->ID ); $order = new WC_Order( $order_id ); $class = 'new_row'; @@ -1199,7 +1196,7 @@ function woocommerce_calc_line_taxes() { // Get product details if ( get_post_type( $item_id ) == 'product' ) { - $_product = new WC_Product( $item_id ); + $_product = get_product( $item_id ); $item_tax_status = $_product->get_tax_status(); } else { $item_tax_status = 'taxable'; diff --git a/woocommerce-core-functions.php b/woocommerce-core-functions.php index 7261ba2dc56..bb242db64bc 100644 --- a/woocommerce-core-functions.php +++ b/woocommerce-core-functions.php @@ -19,6 +19,52 @@ add_filter( 'woocommerce_coupon_code', 'sanitize_text_field' ); add_filter( 'woocommerce_coupon_code', 'strtolower' ); // Coupons case-insensitive by default add_filter( 'woocommerce_stock_amount', 'absint' ); // Stock amounts are integers by default +/** + * Main function for returning products. + * + * @access public + * @param mixed $the_product Post object or post ID of the product. + * @param string $parent_id (default: '') Used when calling variations + * @param string $meta (default: '') Used when calling variations + * @return void + */ +function get_product( $the_product = false, $parent_id = '', $meta = '' ) { + global $post; + + if ( false === $the_product ) + $the_product = $post; + elseif ( is_numeric( $the_product ) ) + $the_product = get_post( $the_product ); + + $product_id = absint( $the_product->ID ); + $post_type = $the_product->post_type; + + if ( $post_type == 'product_variation' ) { + // Filter classname so that the class can be overridden if extended. + $classname = apply_filters( 'woocommerce_product_variation_class', 'WC_Product_Variation', $product_id ); + + if ( class_exists( $classname ) ) { + return new $classname( $the_product, $parent_id, $meta ); + } else { + // Use simple + return new WC_Product_Variation( $the_product, $parent_id, $meta ); + } + } else { + $terms = get_the_terms( $product_id, 'product_type' ); + $product_type = isset( current( $terms )->name ) ? sanitize_title( current( $terms )->name ) : 'simple'; + + // Filter classname so that the class can be overridden if extended. + $classname = apply_filters( 'woocommerce_product_class', 'WC_Product_' . $product_type, $product_type, $post_type, $product_id ); + + if ( class_exists( $classname ) ) { + return new $classname( $the_product ); + } else { + // Use simple + return new WC_Product( $the_product ); + } + } +} + /** * woocommerce_get_dimension function. * diff --git a/woocommerce-functions.php b/woocommerce-functions.php index 03bf036b07b..3ced0c1299b 100644 --- a/woocommerce-functions.php +++ b/woocommerce-functions.php @@ -46,7 +46,7 @@ function woocommerce_redirects() { // Redirect to the product page if we have a single product if (is_search() && is_post_type_archive('product') && get_option('woocommerce_redirect_on_single_search_result')=='yes') { if ($wp_query->post_count==1) { - $product = new WC_Product($wp_query->post->ID); + $product = get_product( $wp_query->post ); if ($product->is_visible()) wp_safe_redirect( get_permalink($product->id), 302 ); exit; } @@ -246,7 +246,7 @@ function woocommerce_add_to_cart_action( $url = false ) { $product_id = apply_filters('woocommerce_add_to_cart_product_id', absint( $_REQUEST['add-to-cart'] ) ); $was_added_to_cart = false; - $adding_to_cart = new WC_Product( $product_id ); + $adding_to_cart = get_product( $product_id ); // Variable product handling if ( $adding_to_cart->is_type( 'variable' ) ) { @@ -843,7 +843,7 @@ function woocommerce_download_product() { $order_key = urldecode( $_GET['order'] ); $email = sanitize_email( str_replace( ' ', '+', urldecode( $_GET['email'] ) ) ); $download_id = isset( $_GET['key'] ) ? urldecode( $_GET['key'] ) : ''; // backwards compatibility for existing download URLs - $_product = new WC_Product( $product_id ); + $_product = get_product( $product_id ); if ( ! is_email( $email) ) wp_die( __( 'Invalid email address.', 'woocommerce' ) . ' ' . __( 'Go to homepage →', 'woocommerce' ) . '' ); diff --git a/woocommerce.php b/woocommerce.php index 9f0bd8ddbf5..f35d2359e0d 100644 --- a/woocommerce.php +++ b/woocommerce.php @@ -171,8 +171,13 @@ class Woocommerce { include( 'widgets/widget-init.php' ); // Widget classes include( 'classes/class-wc-countries.php' ); // Defines countries and states include( 'classes/class-wc-order.php' ); // Single order class + include( 'classes/class-wc-product.php' ); // Product class + include( 'classes/class-wc-product-external.php' ); // External product type class + include( 'classes/class-wc-product-variable.php' ); // Variable product type class + include( 'classes/class-wc-product-grouped.php' ); // Grouped product type class include( 'classes/class-wc-product-variation.php' ); // Product variation class + include( 'classes/class-wc-tax.php' ); // Tax class include( 'classes/class-wc-settings-api.php' ); // Settings API @@ -499,7 +504,7 @@ class Woocommerce { if ( is_int( $post ) ) $post = get_post( $post ); if ( $post->post_type !== 'product' ) return; unset( $GLOBALS['product'] ); - $GLOBALS['product'] = new WC_Product( $post->ID ); + $GLOBALS['product'] = get_product( $post ); return $GLOBALS['product']; }