(no `products` class: WooCommerce/theme loop floats stay out) *
  • * Sale! * img

    …

    *
    stars
    … *
    WooCommerce loop add-to-cart button (AJAX for simple products)
    *
  • * * * The add-to-cart button is WooCommerce's own (`woocommerce_template_loop_add_to_cart`), so its AJAX add to cart, * `added_to_cart` event and cart fragments work as on the shop page. Parts are toggled by the settings * show_image, show_title, show_rating, show_price, show_button, show_badge. */ final class Cards { /** * @param iterable<\WC_Product> $products * @param array $settings */ public static function render(iterable $products, array $settings, string $block = 'zx-products'): string { $columns = max(1, min(8, (int) (is_array($settings['columns'] ?? null) ? ($settings['columns']['size'] ?? 4) : ($settings['columns'] ?? 4)))); $items = ''; foreach ($products as $product) { if (Product::is_product($product)) { $items .= self::card($product, $settings); } } if ($items === '') { return ''; } return ''; } /** @param \WC_Product $product */ public static function card(object $product, array $settings): string { $on = static fn(string $key): bool => !array_key_exists($key, $settings) || !empty($settings[$key]); $tag = in_array($settings['title_tag'] ?? '', ['h2', 'h3', 'h4', 'h5', 'h6', 'p', 'div'], true) ? $settings['title_tag'] : 'h3'; $size = is_string($settings['image_size'] ?? null) && $settings['image_size'] !== '' ? sanitize_key($settings['image_size']) : 'woocommerce_thumbnail'; $classes = function_exists('wc_get_product_class') ? (array) wc_get_product_class('zx-product-card', $product) : ['zx-product-card', 'product']; $url = (string) $product->get_permalink(); $html = '
  • '; $html .= ''; if ($on('show_badge') && $product->is_on_sale()) { $text = is_string($settings['badge_text'] ?? null) && $settings['badge_text'] !== '' ? $settings['badge_text'] : __('Sale!', 'zadex'); $html .= '' . esc_html($text) . ''; } if ($on('show_image')) { $html .= '' . $product->get_image($size) . ''; } if ($on('show_title')) { $html .= '<' . $tag . ' class="zx-product-card__title woocommerce-loop-product__title">' . esc_html($product->get_name()) . ''; } $html .= ''; if ($on('show_rating') && function_exists('wc_get_rating_html')) { $rating = wc_get_rating_html((float) $product->get_average_rating(), (int) $product->get_rating_count()); if ($rating !== '') { $html .= '
    ' . $rating . '
    '; } } if ($on('show_price')) { $price = (string) $product->get_price_html(); if ($price !== '') { $html .= '' . $price . ''; } } if ($on('show_button') && function_exists('woocommerce_template_loop_add_to_cart')) { $button = Product::with($product, static function (object $p): void { $buyable = $p->is_purchasable() && $p->is_in_stock(); woocommerce_template_loop_add_to_cart(['class' => implode(' ', array_filter([ 'button', 'zx-product-card__add', 'wp-element-button', 'has-text-color', 'has-background', 'product_type_' . $p->get_type(), $buyable ? 'add_to_cart_button' : '', $buyable && $p->supports('ajax_add_to_cart') ? 'ajax_add_to_cart' : '', ]))]); }); $html .= '
    ' . $button . '
    '; } return $html . '
  • '; } /** Pagination links for a query (`?product-page=N`). */ public static function pagination(int $total_pages, int $current, bool $archive = false): string { if ($total_pages < 2 || !function_exists('paginate_links')) { return ''; } $args = [ 'total' => $total_pages, 'current' => max(1, $current), 'type' => 'list', 'prev_text' => is_rtl() ? '→' : '←', 'next_text' => is_rtl() ? '←' : '→', ]; if (!$archive) { $args['format'] = '?' . ProductsQuery::PAGE_VAR . '=%#%'; $args['base'] = esc_url_raw(add_query_arg(ProductsQuery::PAGE_VAR, '%#%', false)); } $links = (string) paginate_links($args); return $links !== '' ? '' : ''; } }