diff --git a/includes/class-wc-breadcrumb.php b/includes/class-wc-breadcrumb.php index 1a3aa6dc909..6bc2d104792 100644 --- a/includes/class-wc-breadcrumb.php +++ b/includes/class-wc-breadcrumb.php @@ -8,6 +8,8 @@ defined( 'ABSPATH' ) || exit; +use Automattic\WooCommerce\Loop; + /** * Breadcrumb class. */ @@ -20,6 +22,20 @@ class WC_Breadcrumb { */ protected $crumbs = array(); + /** + * @var Loop Reference to the instance of the Loop class. + */ + protected $loop; + + /** + * Create an instance of the class. + * + * @param string $loop Usually null, a mocked instance of Loop can be passed for unit testing. + */ + public function __construct( $loop = null ) { + $this->loop = is_null( $loop ) ? Loop::get_instance() : $loop; + } + /** * Add a crumb so we don't get lost. * @@ -371,7 +387,7 @@ class WC_Breadcrumb { * Add a breadcrumb for pagination. */ protected function paged_trail() { - if ( get_query_var( 'paged' ) && 'subcategories' !== woocommerce_get_loop_display_mode() ) { + if ( get_query_var( 'paged' ) && 'subcategories' !== $this->loop->get_loop_display_mode() ) { /* translators: %d: page number */ $this->add_crumb( sprintf( __( 'Page %d', 'woocommerce' ), get_query_var( 'paged' ) ) ); } diff --git a/includes/class-wc-shortcodes.php b/includes/class-wc-shortcodes.php index a26419ae89e..f5590142c5e 100644 --- a/includes/class-wc-shortcodes.php +++ b/includes/class-wc-shortcodes.php @@ -150,6 +150,8 @@ class WC_Shortcodes { * @return string */ public static function product_categories( $atts ) { + $loop = Loop::get_instance(); + if ( isset( $atts['number'] ) ) { $atts['limit'] = $atts['number']; } @@ -210,13 +212,13 @@ class WC_Shortcodes { $columns = absint( $atts['columns'] ); - wc_set_loop_prop( 'columns', $columns ); - wc_set_loop_prop( 'is_shortcode', true ); + $loop->set_loop_prop( 'columns', $columns ); + $loop->set_loop_prop( 'is_shortcode', true ); ob_start(); if ( $product_categories ) { - woocommerce_product_loop_start(); + $loop->product_loop_start(); foreach ( $product_categories as $category ) { wc_get_template( @@ -227,10 +229,10 @@ class WC_Shortcodes { ); } - woocommerce_product_loop_end(); + $loop->product_loop_end(); } - woocommerce_reset_loop(); + $loop->reset_loop(); return '
' . ob_get_clean() . '
'; } diff --git a/includes/class-woocommerce.php b/includes/class-woocommerce.php index f5acc2122c2..961647c829d 100644 --- a/includes/class-woocommerce.php +++ b/includes/class-woocommerce.php @@ -8,6 +8,8 @@ defined( 'ABSPATH' ) || exit; +use Automattic\WooCommerce\Loop; + /** * Main WooCommerce Class. * @@ -154,6 +156,7 @@ final class WooCommerce { $this->define_constants(); $this->define_tables(); $this->includes(); + $this->init_singletons(); $this->init_hooks(); } @@ -472,6 +475,10 @@ final class WooCommerce { $this->api->init(); } + private function init_singletons() { + Loop::get_instance(); + } + /** * Include classes for theme support. * diff --git a/includes/shortcodes/class-wc-shortcode-products.php b/includes/shortcodes/class-wc-shortcode-products.php index 4e31f8418ef..d71382129a4 100644 --- a/includes/shortcodes/class-wc-shortcode-products.php +++ b/includes/shortcodes/class-wc-shortcode-products.php @@ -6,6 +6,8 @@ * @version 3.2.4 */ +use Automattic\WooCommerce\Loop; + if ( ! defined( 'ABSPATH' ) ) { exit; } @@ -47,17 +49,25 @@ class WC_Shortcode_Products { */ protected $custom_visibility = false; + /** + * @var Loop Reference to the instance of the Loop class. + */ + protected $loop; + /** * Initialize shortcode. * * @since 3.2.0 - * @param array $attributes Shortcode attributes. - * @param string $type Shortcode type. + * @param array $attributes Shortcode attributes. + * @param string $type Shortcode type. + * @param Loop|null $loop Usually null, a mocked instance of Loop can be passed for unit testing. */ - public function __construct( $attributes = array(), $type = 'products' ) { + public function __construct( $attributes = array(), $type = 'products', $loop = null ) { $this->type = $type; $this->attributes = $this->parse_attributes( $attributes ); $this->query_args = $this->parse_query_args(); + + $this->loop = is_null( $loop ) ? Loop::get_instance() : $loop; } /** @@ -623,7 +633,7 @@ class WC_Shortcode_Products { } // Setup the loop. - wc_setup_loop( + $this->loop->setup_loop( array( 'columns' => $columns, 'name' => $this->type, @@ -646,9 +656,9 @@ class WC_Shortcode_Products { do_action( 'woocommerce_before_shop_loop' ); } - woocommerce_product_loop_start(); + $this->loop->product_loop_start(); - if ( wc_get_loop_prop( 'total' ) ) { + if ( $this->loop->get_loop_prop( 'total' ) ) { foreach ( $products->ids as $product_id ) { $GLOBALS['post'] = get_post( $product_id ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited setup_postdata( $GLOBALS['post'] ); @@ -665,7 +675,7 @@ class WC_Shortcode_Products { } $GLOBALS['post'] = $original_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited - woocommerce_product_loop_end(); + $this->loop->product_loop_end(); // Fire standard shop loop hooks when paginating results so we can show result counts and so on. if ( wc_string_to_bool( $this->attributes['paginate'] ) ) { @@ -675,7 +685,7 @@ class WC_Shortcode_Products { do_action( "woocommerce_shortcode_after_{$this->type}_loop", $this->attributes ); wp_reset_postdata(); - wc_reset_loop(); + $this->loop->reset_loop(); } else { do_action( "woocommerce_shortcode_{$this->type}_loop_no_results", $this->attributes ); } diff --git a/includes/wc-template-functions.php b/includes/wc-template-functions.php index 26a2f26c45a..c57bdcd8a1a 100644 --- a/includes/wc-template-functions.php +++ b/includes/wc-template-functions.php @@ -9,6 +9,7 @@ */ use Automattic\Jetpack\Constants; +use Automattic\WooCommerce\Loop; defined( 'ABSPATH' ) || exit; @@ -154,55 +155,21 @@ add_action( 'the_post', 'wc_setup_product_data' ); * * @since 3.3.0 * @param array $args Args to pass into the global. + * @deprecated Use setup_loop in Loop class instead. */ function wc_setup_loop( $args = array() ) { - $default_args = array( - 'loop' => 0, - 'columns' => wc_get_default_products_per_row(), - 'name' => '', - 'is_shortcode' => false, - 'is_paginated' => true, - 'is_search' => false, - 'is_filtered' => false, - 'total' => 0, - 'total_pages' => 0, - 'per_page' => 0, - 'current_page' => 1, - ); - - // If this is a main WC query, use global args as defaults. - if ( $GLOBALS['wp_query']->get( 'wc_query' ) ) { - $default_args = array_merge( - $default_args, - array( - 'is_search' => $GLOBALS['wp_query']->is_search(), - 'is_filtered' => is_filtered(), - 'total' => $GLOBALS['wp_query']->found_posts, - 'total_pages' => $GLOBALS['wp_query']->max_num_pages, - 'per_page' => $GLOBALS['wp_query']->get( 'posts_per_page' ), - 'current_page' => max( 1, $GLOBALS['wp_query']->get( 'paged', 1 ) ), - ) - ); - } - - // Merge any existing values. - if ( isset( $GLOBALS['woocommerce_loop'] ) ) { - $default_args = array_merge( $default_args, $GLOBALS['woocommerce_loop'] ); - } - - $GLOBALS['woocommerce_loop'] = wp_parse_args( $args, $default_args ); + Loop::get_instance()->setup_loop( $args ); } -add_action( 'woocommerce_before_shop_loop', 'wc_setup_loop' ); /** * Resets the woocommerce_loop global. * * @since 3.3.0 + * @deprecated Use reset_loop in Loop class instead. */ function wc_reset_loop() { - unset( $GLOBALS['woocommerce_loop'] ); + Loop::get_instance()->reset_loop(); } -add_action( 'woocommerce_after_shop_loop', 'woocommerce_reset_loop', 999 ); /** * Gets a property from the woocommerce_loop global. @@ -211,11 +178,10 @@ add_action( 'woocommerce_after_shop_loop', 'woocommerce_reset_loop', 999 ); * @param string $prop Prop to get. * @param string $default Default if the prop does not exist. * @return mixed + * @deprecated Use get_loop_prop in Loop class instead. */ function wc_get_loop_prop( $prop, $default = '' ) { - wc_setup_loop(); // Ensure shop loop is setup. - - return isset( $GLOBALS['woocommerce_loop'], $GLOBALS['woocommerce_loop'][ $prop ] ) ? $GLOBALS['woocommerce_loop'][ $prop ] : $default; + return Loop::get_instance()->get_loop_prop( $prop, $default ); } /** @@ -224,12 +190,10 @@ function wc_get_loop_prop( $prop, $default = '' ) { * @since 3.3.0 * @param string $prop Prop to set. * @param string $value Value to set. + * @deprecated Use get_loop_prop in Loop class instead. */ function wc_set_loop_prop( $prop, $value = '' ) { - if ( ! isset( $GLOBALS['woocommerce_loop'] ) ) { - wc_setup_loop(); - } - $GLOBALS['woocommerce_loop'][ $prop ] = $value; + Loop::get_instance()->set_loop_prop( $prop, $value ); } /** @@ -239,9 +203,10 @@ function wc_set_loop_prop( $prop, $value = '' ) { * * @since 3.4.0 * @return bool + * @deprecated Use in_product_loop in Loop class instead. */ function woocommerce_product_loop() { - return have_posts() || 'products' !== woocommerce_get_loop_display_mode(); + return Loop::get_instance()->in_product_loop(); } /** @@ -417,23 +382,10 @@ add_action( 'after_switch_theme', 'wc_reset_product_grid_settings' ); * * @since 2.6.0 * @return string + * @deprecated Use get_loop_class in Loop class instead. */ function wc_get_loop_class() { - $loop_index = wc_get_loop_prop( 'loop', 0 ); - $columns = absint( max( 1, wc_get_loop_prop( 'columns', wc_get_default_products_per_row() ) ) ); - - $loop_index ++; - wc_set_loop_prop( 'loop', $loop_index ); - - if ( 0 === ( $loop_index - 1 ) % $columns || 1 === $columns ) { - return 'first'; - } - - if ( 0 === $loop_index % $columns ) { - return 'last'; - } - - return ''; + return Loop::get_instance()->get_loop_class(); } @@ -448,10 +400,12 @@ function wc_get_loop_class() { * @return array */ function wc_get_product_cat_class( $class = '', $category = null ) { + $loop = Loop::get_instance(); + $classes = is_array( $class ) ? $class : array_map( 'trim', explode( ' ', $class ) ); $classes[] = 'product-category'; $classes[] = 'product'; - $classes[] = wc_get_loop_class(); + $classes[] = $loop->get_loop_class(); $classes = apply_filters( 'product_cat_class', $classes, $class, $category ); return array_unique( array_filter( $classes ) ); @@ -479,8 +433,10 @@ function wc_product_post_class( $classes, $class = '', $post_id = 0 ) { return $classes; } + $loop = Loop::get_instance(); + $classes[] = 'product'; - $classes[] = wc_get_loop_class(); + $classes[] = $loop->get_loop_class(); $classes[] = $product->get_stock_status(); if ( $product->is_on_sale() ) { @@ -927,6 +883,7 @@ if ( ! function_exists( 'woocommerce_content' ) ) { * without hooks or modifying core templates. */ function woocommerce_content() { + $loop = Loop::get_instance(); if ( is_singular( 'product' ) ) { @@ -946,20 +903,20 @@ if ( ! function_exists( 'woocommerce_content' ) ) { - + in_product_loop() ) : ?> - + product_loop_start(); ?> - + get_loop_prop( 'total' ) ) : ?> - + product_loop_end(); ?> @@ -1076,21 +1033,10 @@ if ( ! function_exists( 'woocommerce_product_loop_start' ) ) { * * @param bool $echo Should echo?. * @return string + * @deprecated Use product_loop_start in Loop class instead. */ function woocommerce_product_loop_start( $echo = true ) { - ob_start(); - - wc_set_loop_prop( 'loop', 0 ); - - wc_get_template( 'loop/loop-start.php' ); - - $loop_start = apply_filters( 'woocommerce_product_loop_start', ob_get_clean() ); - - if ( $echo ) { - echo $loop_start; // WPCS: XSS ok. - } else { - return $loop_start; - } + Loop::get_instance()->product_loop_start( $echo ); } } @@ -1101,19 +1047,10 @@ if ( ! function_exists( 'woocommerce_product_loop_end' ) ) { * * @param bool $echo Should echo?. * @return string + * @deprecated Use product_loop_end in Loop class instead. */ function woocommerce_product_loop_end( $echo = true ) { - ob_start(); - - wc_get_template( 'loop/loop-end.php' ); - - $loop_end = apply_filters( 'woocommerce_product_loop_end', ob_get_clean() ); - - if ( $echo ) { - echo $loop_end; // WPCS: XSS ok. - } else { - return $loop_end; - } + Loop::get_instance()->product_loop_end( $echo ); } } if ( ! function_exists( 'woocommerce_template_loop_product_title' ) ) { @@ -1332,13 +1269,15 @@ if ( ! function_exists( 'woocommerce_result_count' ) ) { * Output the result count text (Showing x - x of x results). */ function woocommerce_result_count() { - if ( ! wc_get_loop_prop( 'is_paginated' ) || ! woocommerce_products_will_display() ) { + $loop = Loop::get_instance(); + + if ( ! $loop->get_loop_prop( 'is_paginated' ) || ! woocommerce_products_will_display() ) { return; } $args = array( - 'total' => wc_get_loop_prop( 'total' ), - 'per_page' => wc_get_loop_prop( 'per_page' ), - 'current' => wc_get_loop_prop( 'current_page' ), + 'total' => $loop->get_loop_prop( 'total' ), + 'per_page' => $loop->get_loop_prop( 'per_page' ), + 'current' => $loop->get_loop_prop( 'current_page' ), ); wc_get_template( 'loop/result-count.php', $args ); @@ -1351,7 +1290,9 @@ if ( ! function_exists( 'woocommerce_catalog_ordering' ) ) { * Output the product sorting options. */ function woocommerce_catalog_ordering() { - if ( ! wc_get_loop_prop( 'is_paginated' ) || ! woocommerce_products_will_display() ) { + $loop = Loop::get_instance(); + + if ( ! $loop->get_loop_prop( 'is_paginated' ) || ! woocommerce_products_will_display() ) { return; } $show_default_orderby = 'menu_order' === apply_filters( 'woocommerce_default_catalog_orderby', get_option( 'woocommerce_default_catalog_orderby', 'menu_order' ) ); @@ -1367,10 +1308,10 @@ if ( ! function_exists( 'woocommerce_catalog_ordering' ) ) { ) ); - $default_orderby = wc_get_loop_prop( 'is_search' ) ? 'relevance' : apply_filters( 'woocommerce_default_catalog_orderby', get_option( 'woocommerce_default_catalog_orderby', '' ) ); + $default_orderby = $loop->get_loop_prop( 'is_search' ) ? 'relevance' : apply_filters( 'woocommerce_default_catalog_orderby', get_option( 'woocommerce_default_catalog_orderby', '' ) ); $orderby = isset( $_GET['orderby'] ) ? wc_clean( wp_unslash( $_GET['orderby'] ) ) : $default_orderby; // WPCS: sanitization ok, input var ok, CSRF ok. - if ( wc_get_loop_prop( 'is_search' ) ) { + if ( $loop->get_loop_prop( 'is_search' ) ) { $catalog_orderby_options = array_merge( array( 'relevance' => __( 'Relevance', 'woocommerce' ) ), $catalog_orderby_options ); unset( $catalog_orderby_options['menu_order'] ); @@ -1405,18 +1346,20 @@ if ( ! function_exists( 'woocommerce_pagination' ) ) { * Output the pagination. */ function woocommerce_pagination() { - if ( ! wc_get_loop_prop( 'is_paginated' ) || ! woocommerce_products_will_display() ) { + $loop = Loop::get_instance(); + + if ( ! $loop->get_loop_prop( 'is_paginated' ) || ! woocommerce_products_will_display() ) { return; } $args = array( - 'total' => wc_get_loop_prop( 'total_pages' ), - 'current' => wc_get_loop_prop( 'current_page' ), + 'total' => $loop->get_loop_prop( 'total_pages' ), + 'current' => $loop->get_loop_prop( 'current_page' ), 'base' => esc_url_raw( add_query_arg( 'product-page', '%#%', false ) ), 'format' => '?product-page=%#%', ); - if ( ! wc_get_loop_prop( 'is_shortcode' ) ) { + if ( ! $loop->get_loop_prop( 'is_shortcode' ) ) { $args['format'] = ''; $args['base'] = esc_url_raw( str_replace( 999999999, '%#%', remove_query_arg( 'add-to-cart', get_pagenum_link( 999999999, false ) ) ) ); } @@ -2266,9 +2209,11 @@ if ( ! function_exists( 'woocommerce_products_will_display' ) ) { * @return bool */ function woocommerce_products_will_display() { - $display_type = woocommerce_get_loop_display_mode(); + $loop = Loop::get_instance(); - return 0 < wc_get_loop_prop( 'total', 0 ) && 'subcategories' !== $display_type; + $display_type = $loop->get_loop_display_mode(); + + return 0 < $loop->get_loop_prop( 'total', 0 ) && 'subcategories' !== $display_type; } } @@ -2279,43 +2224,10 @@ if ( ! function_exists( 'woocommerce_get_loop_display_mode' ) ) { * * @since 3.3.0 * @return string Either products, subcategories, or both, based on current page. + * @deprecated Use get_loop_prop in Loop class instead.Use get_loop_prop in Loop class instead. */ function woocommerce_get_loop_display_mode() { - // Only return products when filtering things. - if ( wc_get_loop_prop( 'is_search' ) || wc_get_loop_prop( 'is_filtered' ) ) { - return 'products'; - } - - $parent_id = 0; - $display_type = ''; - - if ( is_shop() ) { - $display_type = get_option( 'woocommerce_shop_page_display', '' ); - } elseif ( is_product_category() ) { - $parent_id = get_queried_object_id(); - $display_type = get_term_meta( $parent_id, 'display_type', true ); - $display_type = '' === $display_type ? get_option( 'woocommerce_category_archive_display', '' ) : $display_type; - } - - if ( ( ! is_shop() || 'subcategories' !== $display_type ) && 1 < wc_get_loop_prop( 'current_page' ) ) { - return 'products'; - } - - // Ensure valid value. - if ( '' === $display_type || ! in_array( $display_type, array( 'products', 'subcategories', 'both' ), true ) ) { - $display_type = 'products'; - } - - // If we're showing categories, ensure we actually have something to show. - if ( in_array( $display_type, array( 'subcategories', 'both' ), true ) ) { - $subcategories = woocommerce_get_product_subcategories( $parent_id ); - - if ( empty( $subcategories ) ) { - $display_type = 'products'; - } - } - - return $display_type; + return Loop::get_instance()->get_loop_display_mode(); } } @@ -2329,11 +2241,13 @@ if ( ! function_exists( 'woocommerce_maybe_show_product_subcategories' ) ) { * @return string */ function woocommerce_maybe_show_product_subcategories( $loop_html = '' ) { - if ( wc_get_loop_prop( 'is_shortcode' ) && ! WC_Template_Loader::in_content_filter() ) { + $loop = Loop::get_instance(); + + if ( $loop->get_loop_prop( 'is_shortcode' ) && ! WC_Template_Loader::in_content_filter() ) { return $loop_html; } - $display_type = woocommerce_get_loop_display_mode(); + $display_type = $loop->get_loop_display_mode(); // If displaying categories, append to the loop. if ( 'subcategories' === $display_type || 'both' === $display_type ) { @@ -2346,9 +2260,9 @@ if ( ! function_exists( 'woocommerce_maybe_show_product_subcategories' ) ) { $loop_html .= ob_get_clean(); if ( 'subcategories' === $display_type ) { - wc_set_loop_prop( 'total', 0 ); + $loop->set_loop_prop( 'total', 0 ); - // This removes pagination and products from display for themes not using wc_get_loop_prop in their product loops. @todo Remove in future major version. + // This removes pagination and products from display for themes not using get_loop_prop in their product loops. @todo Remove in future major version. global $wp_query; if ( $wp_query->is_main_query() ) { @@ -2379,6 +2293,8 @@ if ( ! function_exists( 'woocommerce_product_subcategories' ) ) { * @return null|boolean */ function woocommerce_product_subcategories( $args = array() ) { + $loop = Loop::get_instance(); + $defaults = array( 'before' => '', 'after' => '', @@ -2399,10 +2315,10 @@ if ( ! function_exists( 'woocommerce_product_subcategories' ) ) { return true; } else { // Output nothing. woocommerce_maybe_show_product_subcategories will handle the output of cats. - $display_type = woocommerce_get_loop_display_mode(); + $display_type = $loop->get_loop_display_mode(); if ( 'subcategories' === $display_type ) { - // This removes pagination and products from display for themes not using wc_get_loop_prop in their product loops. @todo Remove in future major version. + // This removes pagination and products from display for themes not using get_loop_prop in their product loops. @todo Remove in future major version. global $wp_query; if ( $wp_query->is_main_query() ) { @@ -3658,7 +3574,7 @@ if ( ! function_exists( 'woocommerce_reset_loop' ) ) { * @deprecated 3.3 */ function woocommerce_reset_loop() { - wc_reset_loop(); + Loop::get_instance()->reset_loop(); } } diff --git a/phpunit.xml b/phpunit.xml index 75b6a007d51..1464f41e098 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -49,4 +49,7 @@ + + + diff --git a/src/Autoloader.php b/src/Autoloader.php index df56062b10e..39bc7209bdc 100644 --- a/src/Autoloader.php +++ b/src/Autoloader.php @@ -9,6 +9,8 @@ namespace Automattic\WooCommerce; defined( 'ABSPATH' ) || exit; +require_once dirname( __DIR__ ) . '/src/Loop.php'; + /** * Autoloader class. * diff --git a/src/Loop.php b/src/Loop.php new file mode 100644 index 00000000000..375f48cc8a2 --- /dev/null +++ b/src/Loop.php @@ -0,0 +1,255 @@ + 0, + 'columns' => wc_get_default_products_per_row(), + 'name' => '', + 'is_shortcode' => false, + 'is_paginated' => true, + 'is_search' => false, + 'is_filtered' => false, + 'total' => 0, + 'total_pages' => 0, + 'per_page' => 0, + 'current_page' => 1, + ); + + // If this is a main WC query, use global args as defaults. + if ( $GLOBALS['wp_query']->get( 'wc_query' ) ) { + $default_args = array_merge( + $default_args, + array( + 'is_search' => $GLOBALS['wp_query']->is_search(), + 'is_filtered' => is_filtered(), + 'total' => $GLOBALS['wp_query']->found_posts, + 'total_pages' => $GLOBALS['wp_query']->max_num_pages, + 'per_page' => $GLOBALS['wp_query']->get( 'posts_per_page' ), + 'current_page' => max( 1, $GLOBALS['wp_query']->get( 'paged', 1 ) ), + ) + ); + } + + // Merge any existing values. + if ( isset( $GLOBALS['woocommerce_loop'] ) ) { + $default_args = array_merge( $default_args, $GLOBALS['woocommerce_loop'] ); + } + + $GLOBALS['woocommerce_loop'] = wp_parse_args( $args, $default_args ); + } + + /** + * Resets the woocommerce_loop global. + * + * @since 3.3.0 + */ + public function reset_loop() { + unset( $GLOBALS['woocommerce_loop'] ); + } + + /** + * Gets a property from the woocommerce_loop global. + * + * @since 3.3.0 + * @param string $prop Prop to get. + * @param string $default Default if the prop does not exist. + * @return mixed + */ + public function get_loop_prop( $prop, $default = '' ) { + $this->setup_loop(); // Ensure shop loop is setup. + + return isset( $GLOBALS['woocommerce_loop'], $GLOBALS['woocommerce_loop'][ $prop ] ) ? $GLOBALS['woocommerce_loop'][ $prop ] : $default; + } + + /** + * Sets a property in the woocommerce_loop global. + * + * @since 3.3.0 + * @param string $prop Prop to set. + * @param string $value Value to set. + */ + public function set_loop_prop( $prop, $value = '' ) { + if ( ! isset( $GLOBALS['woocommerce_loop'] ) ) { + $this->setup_loop(); + } + $GLOBALS['woocommerce_loop'][ $prop ] = $value; + } + + /** + * See what is going to display in the loop. + * + * @since 3.3.0 + * @return string Either products, subcategories, or both, based on current page. + */ + public function get_loop_display_mode() { + // Only return products when filtering things. + if ( $this->get_loop_prop( 'is_search' ) || $this->get_loop_prop( 'is_filtered' ) ) { + return 'products'; + } + + $parent_id = 0; + $display_type = ''; + + if ( is_shop() ) { + $display_type = get_option( 'woocommerce_shop_page_display', '' ); + } elseif ( is_product_category() ) { + $parent_id = get_queried_object_id(); + $display_type = get_term_meta( $parent_id, 'display_type', true ); + $display_type = '' === $display_type ? get_option( 'woocommerce_category_archive_display', '' ) : $display_type; + } + + if ( ( ! is_shop() || 'subcategories' !== $display_type ) && 1 < wc_get_loop_prop( 'current_page' ) ) { + return 'products'; + } + + // Ensure valid value. + if ( '' === $display_type || ! in_array( $display_type, array( 'products', 'subcategories', 'both' ), true ) ) { + $display_type = 'products'; + } + + // If we're showing categories, ensure we actually have something to show. + if ( in_array( $display_type, array( 'subcategories', 'both' ), true ) ) { + $subcategories = woocommerce_get_product_subcategories( $parent_id ); + + if ( empty( $subcategories ) ) { + $display_type = 'products'; + } + } + + return $display_type; + } + + /** + * Should the WooCommerce loop be displayed? + * + * This will return true if we have posts (products) or if we have subcats to display. + * + * @since 3.4.0 + * @return bool + */ + public function in_product_loop() { + return have_posts() || 'products' !== $this->get_loop_display_mode(); + } + + /** + * Get classname for woocommerce loops. + * + * @since 2.6.0 + * @return string + * @deprecated Use get_loop_class in Loop class instead. + */ + public function get_loop_class() { + $loop_index = $this->get_loop_prop( 'loop', 0 ); + $columns = absint( max( 1, wc_get_loop_prop( 'columns', wc_get_default_products_per_row() ) ) ); + + $loop_index ++; + $this->set_loop_prop( 'loop', $loop_index ); + + if ( 0 === ( $loop_index - 1 ) % $columns || 1 === $columns ) { + return 'first'; + } + + if ( 0 === $loop_index % $columns ) { + return 'last'; + } + + return ''; + } + + /** + * Output the start of a product loop. By default this is a UL. + * + * @param bool $echo Should echo?. + * @return string + */ + public function product_loop_start( $echo = true ) { + ob_start(); + + $this->set_loop_prop( 'loop', 0 ); + + wc_get_template( 'loop/loop-start.php' ); + + $loop_start = apply_filters( 'woocommerce_product_loop_start', ob_get_clean() ); + + if ( $echo ) { + echo $loop_start; // WPCS: XSS ok. + } else { + return $loop_start; + } + } + + /** + * Output the end of a product loop. By default this is a UL. + * + * @param bool $echo Should echo?. + * @return string + */ + public function product_loop_end( $echo = true ) { + ob_start(); + + wc_get_template( 'loop/loop-end.php' ); + + $loop_end = apply_filters( 'woocommerce_product_loop_end', ob_get_clean() ); + + if ( $echo ) { + echo $loop_end; // WPCS: XSS ok. + } else { + return $loop_end; + } + } +} diff --git a/templates/archive-product.php b/templates/archive-product.php index e4f3938b3e4..9b9f12a6ce3 100644 --- a/templates/archive-product.php +++ b/templates/archive-product.php @@ -17,6 +17,8 @@ defined( 'ABSPATH' ) || exit; +use Automattic\WooCommerce\Loop; + get_header( 'shop' ); /** @@ -28,6 +30,8 @@ get_header( 'shop' ); */ do_action( 'woocommerce_before_main_content' ); +$loop = Loop::get_instance(); + ?>
@@ -45,7 +49,7 @@ do_action( 'woocommerce_before_main_content' ); ?>
in_product_loop() ) { /** * Hook: woocommerce_before_shop_loop. @@ -56,9 +60,9 @@ if ( woocommerce_product_loop() ) { */ do_action( 'woocommerce_before_shop_loop' ); - woocommerce_product_loop_start(); + $loop->product_loop_start(); - if ( wc_get_loop_prop( 'total' ) ) { + if ( $loop->get_loop_prop( 'total' ) ) { while ( have_posts() ) { the_post(); @@ -71,7 +75,7 @@ if ( woocommerce_product_loop() ) { } } - woocommerce_product_loop_end(); + $loop->product_loop_end(); /** * Hook: woocommerce_after_shop_loop. diff --git a/templates/cart/cross-sells.php b/templates/cart/cross-sells.php index c541a0c9d98..15a51e3cf8f 100644 --- a/templates/cart/cross-sells.php +++ b/templates/cart/cross-sells.php @@ -17,13 +17,17 @@ defined( 'ABSPATH' ) || exit; +use Automattic\WooCommerce\Loop; + if ( $cross_sells ) : ?>

- + + + product_loop_start(); ?> @@ -37,7 +41,7 @@ if ( $cross_sells ) : ?> - + product_loop_end(); ?>
-