feat: implement WooCommerce storefront foundation
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
/**
|
||||
* XShop Product Card — reusable for shop/category/home/related/upsells/search.
|
||||
*
|
||||
* Expects: $args['product'] = WC_Product|int (ID)
|
||||
* Optional: $args['show_hooks'] bool (wishlist/compare placeholders)
|
||||
*
|
||||
* @package XShop
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
$product = $args['product'] ?? null;
|
||||
if (is_int($product)) {
|
||||
$product = wc_get_product($product);
|
||||
}
|
||||
if (!$product instanceof WC_Product) {
|
||||
global $product;
|
||||
if (!isset($product) || !$product instanceof WC_Product) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$pid = $product->get_id();
|
||||
$title = $product->get_name();
|
||||
if ($title === '') {
|
||||
$title = esc_html__('Untitled', 'xshop');
|
||||
}
|
||||
$permalink = get_permalink($pid);
|
||||
$priceHtml = $product->get_price_html();
|
||||
$rating = $product->get_average_rating();
|
||||
$count = $product->get_rating_count();
|
||||
$stock = $product->get_stock_status();
|
||||
$isOnSale = $product->is_on_sale();
|
||||
$featured = $product->is_featured();
|
||||
$type = $product->get_type();
|
||||
$showHooks = !empty($args['show_hooks']);
|
||||
?>
|
||||
<article <?php wc_product_class('xshop-product-card', $product); ?>>
|
||||
<a href="<?php echo esc_url($permalink); ?>" class="xshop-product-card__media" aria-label="<?php echo esc_attr($title); ?>">
|
||||
<?php
|
||||
$imageId = $product->get_image_id();
|
||||
if ($imageId) {
|
||||
echo wp_get_attachment_image($imageId, 'xshop-card', false, ['loading' => 'lazy', 'decoding' => 'async', 'alt' => esc_attr($title)]);
|
||||
} elseif (has_post_thumbnail($pid)) {
|
||||
echo get_the_post_thumbnail($pid, 'xshop-card', ['loading' => 'lazy', 'decoding' => 'async', 'alt' => esc_attr($title)]);
|
||||
} else {
|
||||
echo '<span class="xshop-card__media--placeholder" style="display:flex;inline-size:100%;block-size:100%;align-items:center;justify-content:center;">' . esc_html__('No image', 'xshop') . '</span>';
|
||||
}
|
||||
?>
|
||||
<span class="xshop-product-card__badges" aria-hidden="true">
|
||||
<?php if ($isOnSale): ?>
|
||||
<span class="xshop-badge xshop-badge--sale">
|
||||
<?php
|
||||
if ($product->is_type('variable')) {
|
||||
esc_html_e('Sale', 'xshop');
|
||||
} else {
|
||||
$regular = (float) $product->get_regular_price();
|
||||
$sale = (float) $product->get_sale_price();
|
||||
if ($regular > 0 && $sale > 0) {
|
||||
$pct = (int) round(100 - ($sale / $regular * 100));
|
||||
echo esc_html(sprintf('-%d%%', $pct));
|
||||
} else {
|
||||
esc_html_e('Sale', 'xshop');
|
||||
}
|
||||
}
|
||||
?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<?php if ($stock === 'outofstock'): ?>
|
||||
<span class="xshop-badge xshop-badge--outofstock"><?php esc_html_e('Out of stock', 'xshop'); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if ($featured): ?>
|
||||
<span class="xshop-badge xshop-badge--featured"><?php esc_html_e('Featured', 'xshop'); ?></span>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<div class="xshop-product-card__body">
|
||||
<h3 class="xshop-product-card__title">
|
||||
<a href="<?php echo esc_url($permalink); ?>"><?php echo esc_html($title); ?></a>
|
||||
</h3>
|
||||
|
||||
<?php if ($priceHtml !== ''): ?>
|
||||
<div class="xshop-product-card__price"><?php echo wp_kses_post($priceHtml); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="xshop-product-card__meta">
|
||||
<?php if ($rating > 0): ?>
|
||||
<span class="xshop-product-card__rating" aria-label="<?php echo esc_attr(sprintf(esc_html__('Rated %s out of 5', 'xshop'), $rating)); ?>">
|
||||
<?php
|
||||
$stars = str_repeat('★', (int) round($rating)) . str_repeat('☆', 5 - (int) round($rating));
|
||||
echo esc_html($stars) . ' <span style="color:var(--xshop-muted)">(' . esc_html((string) $count) . ')</span>';
|
||||
?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<?php if ($type === 'variable'): ?>
|
||||
<span><?php esc_html_e('Variable', 'xshop'); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if ($stock === 'outofstock'): ?>
|
||||
<span><?php esc_html_e('Out of stock', 'xshop'); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="xshop-product-card__actions">
|
||||
<?php
|
||||
// Use Woo's add-to-cart template (handles simple/variable correctly)
|
||||
woocommerce_template_loop_add_to_cart();
|
||||
?>
|
||||
<?php if ($showHooks): ?>
|
||||
<span class="xshop-product-card__hook" aria-hidden="true"><?php esc_html_e('Wishlist', 'xshop'); ?></span>
|
||||
<span class="xshop-product-card__hook" aria-hidden="true"><?php esc_html_e('Compare', 'xshop'); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
// Hook placeholders for future wishlist/compare (no fake functionality)
|
||||
do_action('xshop_product_card_after_actions', $product);
|
||||
?>
|
||||
</div>
|
||||
</article>
|
||||
Reference in New Issue
Block a user