diff --git a/includes/abstracts/abstract-wc-product.php b/includes/abstracts/abstract-wc-product.php index d2be0d2e460..6823645cacc 100644 --- a/includes/abstracts/abstract-wc-product.php +++ b/includes/abstracts/abstract-wc-product.php @@ -1386,7 +1386,7 @@ class WC_Product extends WC_Abstract_Legacy_Product { * @return bool */ public function has_child() { - return false; + return 0 < count( $this->get_children() ); } /** diff --git a/includes/class-wc-product-grouped.php b/includes/class-wc-product-grouped.php index e5a3b8f4c14..2739ec46f43 100644 --- a/includes/class-wc-product-grouped.php +++ b/includes/class-wc-product-grouped.php @@ -1,7 +1,6 @@ array(), + ); + + /** + * Merges grouped 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 ); + } + + /* + |-------------------------------------------------------------------------- + | Getters + |-------------------------------------------------------------------------- + | + | Methods for getting data from the product object. + */ /** * Get internal type. @@ -28,6 +50,15 @@ class WC_Product_Grouped extends WC_Product { return 'grouped'; } + /** + * Return the children of this product. + * + * @return array + */ + public function get_children() { + return $this->data['children']; + } + /** * Get the add to cart button text. * @@ -38,79 +69,20 @@ class WC_Product_Grouped extends WC_Product { 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. * - * @access public * @return bool */ public function is_on_sale() { - $is_on_sale = false; - - 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 ) { - $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 ); + global $wpdb; + $on_sale = 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() ) ) . ");" ); + return apply_filters( 'woocommerce_product_is_on_sale', $on_sale, $this ); } - /** * Returns false if the product cannot be bought. * - * @access public * @return bool */ public function is_purchasable() { @@ -118,7 +90,7 @@ class WC_Product_Grouped extends WC_Product { } /** - * Returns the price in html format. + * Returns the price in html format. @todo consider moving to template function * * @access public * @param string $price (default: '') @@ -129,7 +101,7 @@ class WC_Product_Grouped extends WC_Product { $child_prices = array(); foreach ( $this->get_children() as $child_id ) { - $child = wc_get_product( $child_id ); + $child = wc_get_product( $child_id ); if ( '' !== $child->get_price() ) { $child_prices[] = 'incl' === $tax_display_mode ? $child->get_price_including_tax() : $child->get_price_excluding_tax(); } @@ -158,4 +130,51 @@ class WC_Product_Grouped extends WC_Product { return apply_filters( 'woocommerce_get_price_html', $price, $this ); } + + /* + |-------------------------------------------------------------------------- + | Setters + |-------------------------------------------------------------------------- + | + | Methods for getting data from the product object. + */ + + /** + * Return the children of this product. + * + * @param array $children + */ + public function set_children( $children ) { + $this->data['children'] = array_filter( wp_parse_id_list( (array) $children ) ); + } + + /* + |-------------------------------------------------------------------------- + | CRUD methods + |-------------------------------------------------------------------------- + */ + + /** + * Reads a product from the database and sets its data to the class. + * + * @since 2.7.0 + * @param int $id Product ID. + */ + public function read( $id ) { + parent::read( $id ); + + $this->set_props( array( + 'children' => wp_parse_id_list( get_post_meta( $id, '_children', true ) ), + ) ); + do_action( 'woocommerce_product_loaded', $this ); + do_action( 'woocommerce_product_' . $this->get_type() . '_loaded', $this ); + } + + /** + * Helper method that updates all the post meta for a grouped product. + */ + protected function update_post_meta() { + parent::update_post_meta(); + update_post_meta( $this->get_id(), '_children', $this->get_children() ); + } } diff --git a/tests/framework/helpers/class-wc-helper-product.php b/tests/framework/helpers/class-wc-helper-product.php index 04c9bdc016c..0806c799539 100644 --- a/tests/framework/helpers/class-wc-helper-product.php +++ b/tests/framework/helpers/class-wc-helper-product.php @@ -54,6 +54,33 @@ class WC_Helper_Product { return new WC_Product_Simple( $product ); } + /** + * Create grouped product. + * + * @since 2.7.0 + * + * @return WC_Product_Grouped + */ + public static function create_grouped_product() { + // Create the product + $product = wp_insert_post( array( + 'post_title' => 'Dummy Grouped Product', + 'post_type' => 'product', + 'post_status' => 'publish', + ) ); + $simple_product_1 = self::create_simple_product( $product ); + $simple_product_2 = self::create_simple_product( $product ); + update_post_meta( $product, '_children', array( $simple_product_1->id, $simple_product_2->id ) ); + update_post_meta( $product, '_sku', 'DUMMY GROUPED SKU' ); + update_post_meta( $product, '_manage_stock', 'no' ); + update_post_meta( $product, '_tax_status', 'taxable' ); + update_post_meta( $product, '_downloadable', 'no' ); + update_post_meta( $product, '_virtual', 'no' ); + update_post_meta( $product, '_visibility', 'visible' ); + update_post_meta( $product, '_stock_status', 'instock' ); + return new WC_Product_Grouped( $product ); + } + /** * Create external product. * @@ -72,16 +99,8 @@ class WC_Helper_Product { update_post_meta( $product, '_regular_price', '10' ); update_post_meta( $product, '_sale_price', '' ); update_post_meta( $product, '_sku', 'DUMMY EXTERNAL SKU' ); - update_post_meta( $product, '_manage_stock', 'no' ); - update_post_meta( $product, '_tax_status', 'taxable' ); - update_post_meta( $product, '_downloadable', 'no' ); - update_post_meta( $product, '_virtual', 'no' ); - update_post_meta( $product, '_visibility', 'visible' ); - update_post_meta( $product, '_stock_status', 'instock' ); - update_post_meta( $product, '_product_url', 'http://woocommerce.com' ); update_post_meta( $product, '_button_text', 'Buy external product' ); - return new WC_Product_External( $product ); } @@ -287,12 +306,12 @@ class WC_Helper_Product { public static function create_product_review( $product_id, $review_content = 'Review content here' ) { $data = array( 'comment_post_ID' => $product_id, - 'comment_author' => 'admin', - 'comment_author_email' => 'woo@woo.local', - 'comment_author_url' => '', + 'comment_author' => 'admin', + 'comment_author_email' => 'woo@woo.local', + 'comment_author_url' => '', 'comment_date' => '2016-01-01T11:11:11', - 'comment_content' => $review_content, - 'comment_approved' => 1, + 'comment_content' => $review_content, + 'comment_approved' => 1, 'comment_type' => 'review', ); diff --git a/tests/unit-tests/product/crud.php b/tests/unit-tests/product/crud.php index a5755a60f9e..45d1b392dc7 100644 --- a/tests/unit-tests/product/crud.php +++ b/tests/unit-tests/product/crud.php @@ -104,23 +104,86 @@ class WC_Tests_Product_CRUD extends WC_Unit_Test_Case { 'purchase_note' => 'A note', 'menu_order' => 2, ); - $product = new WC_Product; - foreach ( $getters_and_setters as $function => $value ) { - $product->{"set_{$function}"}( $value ); - } - $product->create(); - $product = new WC_Product_Simple( $product->get_id() ); - foreach ( $getters_and_setters as $function => $value ) { + $product = new WC_Product; + foreach ( $getters_and_setters as $function => $value ) { + $product->{"set_{$function}"}( $value ); + } + $product->create(); + $product = new WC_Product_Simple( $product->get_id() ); + foreach ( $getters_and_setters as $function => $value ) { $this->assertEquals( $value, $product->{"get_{$function}"}(), $function ); } } - /** + /** + * Test creating a new grouped product. + * + * @since 2.7.0 + */ + function test_grouped_product_create() { + $simple_product = WC_Helper_Product::create_simple_product(); + $product = new WC_Product_Grouped; + $product->set_children( array( $simple_product->get_id() ) ); + $product->set_name( 'My Grouped Product' ); + $product->create(); + $read_product = new WC_Product_Grouped( $product->get_id() ); + $this->assertEquals( 'My Grouped Product', $read_product->get_name() ); + $this->assertEquals( array( $simple_product->get_id() ), $read_product->get_children() ); + } + + /** + * Test getting / reading an grouped product. + * + * @since 2.7.0 + */ + function test_grouped_product_read() { + $product = WC_Helper_Product::create_grouped_product(); + $read_product = new WC_Product_Grouped( $product->get_id() ); + $this->assertEquals( 'Dummy Grouped Product', $read_product->get_name() ); + $this->assertEquals( 2, count( $read_product->get_children() ) ); + } + /** + * Test updating an grouped product. + * + * @since 2.7.0 + */ + function test_grouped_product_update() { + $product = WC_Helper_Product::create_grouped_product(); + $simple_product = WC_Helper_Product::create_simple_product(); + $this->assertEquals( 'Dummy Grouped Product', $product->get_name() ); + $this->assertEquals( 2, count( $product->get_children() ) ); + $children = $product->get_children(); + $children[] = $simple_product->get_id(); + $product->set_children( $children ); + $product->set_name( 'Dummy Grouped Product 2' ); + $product->save(); + // Reread from database + $product = new WC_Product_Grouped( $product->get_id() ); + $this->assertEquals( 3, count( $product->get_children() ) ); + $this->assertEquals( 'Dummy Grouped Product 2', $product->get_name() ); + } + /** + * Test grouped product setters and getters + * + * @since 2.7.0 + */ + public function test_grouped_product_getters_and_setters() { + $getters_and_setters = array( + 'children' => array( 1, 2, 3 ), + ); + $product = new WC_Product_Grouped; + foreach ( $getters_and_setters as $function => $value ) { + $product->{"set_{$function}"}( $value ); + $this->assertEquals( $value, $product->{"get_{$function}"}(), $function ); + } + } + + /** * Test creating a new external product. * * @since 2.7.0 */ - function test_external_product_create() { + function test_external_product_create() { $product = new WC_Product_External; $product->set_regular_price( 42 ); $product->set_button_text( 'Test CRUD' );