motayeb/single-product.php
Kazem Alghasi c16648678d - install local Tailwind
- fix more problems
2026-09-23 00:32:16 +03:30

427 lines
25 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* صفحه تک محصول - مطابق با UI Kolli.html
* با پشتیبانی از محصولات ساده و متغیر + ترکیبات و خواص سلامت (متافیلد دستی)
*
* @package Motayeb
* @version 1.0.1
*
* تغییرات نسخه ۱.۰.۱:
* - guard کامل WooCommerce
* - رفع سبد خرید: استفاده از فرم استاندارد ووکامرس (سرور-side)
* - data-product-id و data-wishlist-btn برای علاقه‌مندی
* - nonce و name="add-to-cart" برای محصول ساده
* - رفع محصولات مرتبط با wc_get_template_part
* - برچسب "ویژه" به‌جای "ارگانیک"
* - Schema.org Product markup
* - width/height + fetchpriority روی عکس اصلی
* - aria-label روی دکمه‌ها
* - رفع span اضافی دور ستاره‌ها
* - نمایش درصد تخفیف
* - Breadcrumb Schema
*/
get_header();
// guard: اگه ووکامرس غیرفعال باشه
if ( ! class_exists( 'WooCommerce' ) ) {
echo '<main class="pt-16 md:pt-20"><div class="max-w-7xl mx-auto px-4 py-20 text-center">';
echo '<h1 class="text-2xl font-bold mb-4">ووکامرس فعال نیست</h1>';
echo '<p class="text-cream-500">برای مشاهده محصولات، افزونه ووکامرس را فعال کنید.</p>';
echo '</div></main>';
get_footer();
return;
}
while ( have_posts() ) : the_post();
global $product;
// guard دوم: اگه محصول معتبر نبود
if ( ! is_a( $product, 'WC_Product' ) ) {
continue;
}
$product_id = $product->get_id();
$product_name = $product->get_name();
$product_link = $product->get_permalink();
$is_in_stock = $product->is_in_stock();
$is_on_sale = $product->is_on_sale();
$is_featured = $product->is_featured();
$is_variable = $product->is_type( array( 'variable', 'variable-subscription' ) );
// تصویر اصلی
$main_image = wp_get_attachment_image_src( get_post_thumbnail_id(), 'full' );
$main_img_src = $main_image ? $main_image[0] : 'https://picsum.photos/seed/' . $product_id . '/600/600.jpg';
$main_img_w = $main_image ? $main_image[1] : 600;
$main_img_h = $main_image ? $main_image[2] : 600;
$main_img_alt = get_post_meta( get_post_thumbnail_id(), '_wp_attachment_image_alt', true ) ?: $product_name;
$gallery_images = $product->get_gallery_image_ids();
// متافیلدهای دستی
$ingredients = get_post_meta( $product_id, '_ingredients', true );
$benefits = get_post_meta( $product_id, '_health_benefits', true );
$benefits_title = get_post_meta( $product_id, '_health_benefits_title', true );
if ( empty( $benefits_title ) ) {
$benefits_title = 'خواص سلامت';
}
// امتیاز و تعداد نظرات
$rating = $product->get_average_rating();
$review_count = $product->get_rating_count();
// درصد تخفیف
$discount_percent = 0;
if ( $is_on_sale ) {
$regular_price = (float) $product->get_regular_price();
$sale_price = (float) $product->get_sale_price();
if ( $regular_price > 0 && $sale_price < $regular_price ) {
$discount_percent = round( ( ( $regular_price - $sale_price ) / $regular_price ) * 100 );
}
}
// موجودی
$max_purchase = $product->get_max_purchase_quantity();
$max_qty = ( $max_purchase && $max_purchase > 0 ) ? $max_purchase : 9999;
?>
<!-- ===== PRODUCT DETAIL ===== -->
<section class="py-8 md:py-16 bg-cream-100 dark:bg-dark-bg" itemscope itemtype="https://schema.org/Product">
<meta itemprop="productID" content="<?php echo esc_attr( $product_id ); ?>">
<meta itemprop="name" content="<?php echo esc_attr( $product_name ); ?>">
<meta itemprop="url" content="<?php echo esc_attr( $product_link ); ?>">
<meta itemprop="description" content="<?php echo esc_attr( wp_strip_all_tags( $product->get_short_description() ) ); ?>">
<meta itemprop="image" content="<?php echo esc_attr( $main_img_src ); ?>">
<div class="max-w-7xl mx-auto px-4 md:px-6">
<!-- Breadcrumb -->
<div class="mb-6 text-sm text-cream-500 dark:text-dark-muted motayeb-breadcrumb">
<?php woocommerce_breadcrumb(); ?>
</div>
<div class="grid md:grid-cols-2 gap-8 md:gap-12">
<!-- Gallery -->
<div>
<div class="relative rounded-2xl overflow-hidden bg-white dark:bg-dark-card border border-cream-200/50 dark:border-dark-border/50 mb-3">
<img id="main-product-img"
src="<?php echo esc_url( $main_img_src ); ?>"
alt="<?php echo esc_attr( $main_img_alt ); ?>"
width="<?php echo esc_attr( $main_img_w ); ?>"
height="<?php echo esc_attr( $main_img_h ); ?>"
fetchpriority="high"
decoding="async"
class="w-full h-[350px] md:h-[450px] object-cover"
data-large_image="<?php echo esc_url( $main_img_src ); ?>"
data-large_image_width="<?php echo esc_attr( $main_img_w ); ?>"
data-large_image_height="<?php echo esc_attr( $main_img_h ); ?>"
itemprop="image">
<!-- برچسب‌های روی عکس -->
<div class="absolute top-3 right-3 flex flex-col gap-2">
<?php if ( $is_featured ) : ?>
<span class="px-3 py-1 bg-forest-500 text-white text-xs rounded-full font-medium">ویژه</span>
<?php endif; ?>
<?php if ( $discount_percent > 0 ) : ?>
<span class="px-3 py-1 bg-red-500 text-white text-xs rounded-full font-medium">
<?php echo esc_html( number_format_i18n( $discount_percent ) ); ?>٪ تخفیف
</span>
<?php elseif ( $is_on_sale ) : ?>
<span class="px-3 py-1 bg-red-500 text-white text-xs rounded-full font-medium">تخفیف ویژه</span>
<?php endif; ?>
</div>
<!-- دکمه علاقه‌مندی -->
<button onclick="toggleWishlist(this, <?php echo esc_js( $product_id ); ?>)"
data-wishlist-btn
data-product-id="<?php echo esc_attr( $product_id ); ?>"
class="absolute top-3 left-3 w-10 h-10 bg-white/80 dark:bg-dark-card/80 backdrop-blur rounded-full flex items-center justify-center hover:scale-110 transition"
aria-label="افزودن به علاقه‌مندی‌ها">
<iconify-icon icon="lucide:heart" width="20" class="text-cream-500"></iconify-icon>
</button>
</div>
<!-- Thumbnails -->
<?php if ( ! empty( $gallery_images ) ) : ?>
<div class="flex gap-2 overflow-x-auto pb-1">
<button onclick="changeImage('<?php echo esc_js( $main_img_src ); ?>', this)"
class="gallery-thumb active w-20 h-20 rounded-xl overflow-hidden border-2 border-forest-500 flex-shrink-0"
aria-label="نمایش تصویر اصلی">
<img src="<?php echo esc_url( $main_img_src ); ?>" class="w-full h-full object-cover" alt="" loading="lazy">
</button>
<?php foreach ( $gallery_images as $image_id ) :
$image_url = wp_get_attachment_image_src( $image_id, 'thumbnail' );
$image_full = wp_get_attachment_image_src( $image_id, 'full' );
$image_alt = get_post_meta( $image_id, '_wp_attachment_image_alt', true ) ?: $product_name;
if ( ! $image_url ) continue;
?>
<button onclick="changeImage('<?php echo esc_js( $image_full[0] ); ?>', this)"
class="gallery-thumb w-20 h-20 rounded-xl overflow-hidden border-2 border-transparent flex-shrink-0 hover:border-forest-300 transition"
aria-label="نمایش تصویر <?php echo esc_attr( $image_id ); ?>">
<img src="<?php echo esc_url( $image_url[0] ); ?>" class="w-full h-full object-cover" alt="<?php echo esc_attr( $image_alt ); ?>" loading="lazy">
</button>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<!-- Product Info -->
<div>
<!-- عنوان و امتیاز -->
<div class="flex items-center gap-2 mb-3 flex-wrap">
<span class="text-xs text-cream-500 dark:text-dark-muted">برند: مطیب</span>
<span class="w-1 h-1 bg-cream-400 rounded-full" aria-hidden="true"></span>
<?php if ( $rating > 0 ) : ?>
<span class="flex items-center" itemprop="aggregateRating" itemscope itemtype="https://schema.org/AggregateRating">
<?php echo motayeb_get_rating_html( $rating, $review_count ); ?>
<meta itemprop="ratingValue" content="<?php echo esc_attr( $rating ); ?>">
<meta itemprop="reviewCount" content="<?php echo esc_attr( $review_count ); ?>">
</span>
<?php endif; ?>
</div>
<h1 class="text-2xl md:text-3xl font-extrabold mb-3" itemprop="name"><?php echo esc_html( $product_name ); ?></h1>
<?php if ( $product->get_short_description() ) : ?>
<p class="text-cream-500 dark:text-dark-muted text-sm leading-relaxed mb-5"><?php echo wp_kses_post( $product->get_short_description() ); ?></p>
<?php endif; ?>
<!-- Stock -->
<div class="flex items-center gap-2 mb-5">
<?php if ( $is_in_stock ) : ?>
<span class="w-2 h-2 bg-emerald-500 rounded-full animate-pulse-soft" aria-hidden="true"></span>
<span class="text-sm text-emerald-600 dark:text-emerald-400 font-medium">موجود در انبار</span>
<?php else : ?>
<span class="w-2 h-2 bg-red-500 rounded-full" aria-hidden="true"></span>
<span class="text-sm text-red-600 dark:text-red-400 font-medium">ناموجود</span>
<?php endif; ?>
<?php if ( $product->get_sku() ) : ?>
<span class="text-xs text-cream-400 dark:text-dark-muted mr-auto" dir="ltr">SKU: <?php echo esc_html( $product->get_sku() ); ?></span>
<?php endif; ?>
</div>
<!-- Price -->
<div class="flex items-end gap-3 mb-6" itemprop="offers" itemscope itemtype="https://schema.org/Offer">
<meta itemprop="price" content="<?php echo esc_attr( $product->get_price() ); ?>">
<meta itemprop="priceCurrency" content="IRR">
<link itemprop="availability" href="<?php echo $is_in_stock ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock'; ?>">
<span class="text-3xl font-extrabold text-forest-500 dark:text-forest-300"><?php echo wp_kses_post( $product->get_price_html() ); ?></span>
</div>
<!-- ===== ویژگی‌های محصول (Attributes) ===== -->
<?php
$attributes = $product->get_attributes();
if ( ! empty( $attributes ) && $product->is_type( 'simple' ) ) :
?>
<div class="mb-6">
<h3 class="text-sm font-bold mb-3 text-earth-400 dark:text-dark-text">ویژگی‌های محصول</h3>
<div class="grid grid-cols-2 gap-3">
<?php foreach ( $attributes as $attribute ) :
$values = wc_get_product_terms( $product_id, $attribute->get_name(), array( 'fields' => 'names' ) );
if ( empty( $values ) ) continue;
?>
<div class="bg-cream-50 dark:bg-dark-bg rounded-xl p-3 text-center">
<span class="text-xs text-cream-500 dark:text-dark-muted"><?php echo esc_html( wc_attribute_label( $attribute->get_name() ) ); ?></span>
<div class="font-bold text-sm mt-1"><?php echo esc_html( implode( ', ', $values ) ); ?></div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<!-- ===== فرم افزودن به سبد ===== -->
<?php if ( $is_in_stock ) : ?>
<?php if ( $is_variable ) : ?>
<!-- محصول متغیر: فرم استاندارد ووکامرس -->
<div class="mb-6 motayeb-variations">
<?php woocommerce_template_single_add_to_cart(); ?>
</div>
<?php else : ?>
<!-- محصول ساده: فرم استاندارد ووکامرس -->
<form class="cart"
action="<?php echo esc_url( apply_filters( 'woocommerce_add_to_cart_form_action', $product->get_permalink() ) ); ?>"
method="post"
enctype="multipart/form-data">
<?php wp_nonce_field( 'woocommerce-add-to-cart_' . $product_id, 'woocommerce-add-to-cart-nonce' ); ?>
<div class="flex items-center gap-4 mb-6">
<label class="text-sm font-bold" for="product-qty-input">تعداد:</label>
<div class="flex items-center border-2 border-cream-200 dark:border-dark-border rounded-xl overflow-hidden">
<button type="button" onclick="motayebQtyChange(-1)" class="w-10 h-10 flex items-center justify-center hover:bg-cream-100 dark:hover:bg-dark-bg transition" aria-label="کاهش تعداد">
<iconify-icon icon="lucide:minus" width="16"></iconify-icon>
</button>
<input type="number"
id="product-qty-input"
name="quantity"
value="1"
min="1"
max="<?php echo esc_attr( $max_qty ); ?>"
class="w-14 h-10 text-center border-x-2 border-cream-200 dark:border-dark-border font-bold text-sm outline-none bg-white dark:bg-dark-card"
dir="ltr"
inputmode="numeric">
<button type="button" onclick="motayebQtyChange(1)" class="w-10 h-10 flex items-center justify-center hover:bg-cream-100 dark:hover:bg-dark-bg transition" aria-label="افزایش تعداد">
<iconify-icon icon="lucide:plus" width="16"></iconify-icon>
</button>
</div>
</div>
<div class="flex gap-3 mb-6">
<button type="submit"
name="add-to-cart"
value="<?php echo esc_attr( $product_id ); ?>"
class="flex-1 py-3.5 bg-forest-500 text-white rounded-2xl font-bold hover:bg-forest-600 hover:-translate-y-0.5 transition-all shadow-lg shadow-forest-500/25 flex items-center justify-center gap-2"
aria-label="افزودن <?php echo esc_attr( $product_name ); ?> به سبد خرید">
<iconify-icon icon="lucide:shopping-bag" width="20"></iconify-icon>
<span>افزودن به سبد خرید</span>
</button>
<button type="button"
onclick="toggleWishlist(this, <?php echo esc_js( $product_id ); ?>)"
data-wishlist-btn
data-product-id="<?php echo esc_attr( $product_id ); ?>"
class="w-14 h-14 border-2 border-cream-200 dark:border-dark-border rounded-2xl flex items-center justify-center hover:border-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 transition"
aria-label="افزودن به علاقه‌مندی‌ها">
<iconify-icon icon="lucide:heart" width="22" class="text-cream-400"></iconify-icon>
</button>
</div>
</form>
<?php endif; ?>
<?php else : ?>
<div class="flex items-center gap-3 p-4 bg-red-50 dark:bg-red-900/20 rounded-2xl border border-red-200 dark:border-red-800 text-red-500 text-sm font-medium mb-6">
<iconify-icon icon="lucide:x-circle" width="20"></iconify-icon>
<span>این محصول در حال حاضر ناموجود است.</span>
</div>
<?php endif; ?>
<!-- Delivery Info -->
<div class="bg-cream-50 dark:bg-dark-bg rounded-2xl p-4 space-y-3">
<div class="flex items-center gap-3 text-sm">
<iconify-icon icon="lucide:truck" width="18" class="text-forest-500 dark:text-forest-300 flex-shrink-0"></iconify-icon>
<span>ارسال به تمامی نقاط کشور</span>
</div>
<div class="flex items-center gap-3 text-sm">
<iconify-icon icon="lucide:rotate-ccw" width="18" class="text-forest-500 dark:text-forest-300 flex-shrink-0"></iconify-icon>
<span>ضمانت بازگشت ۷ روزه</span>
</div>
<div class="flex items-center gap-3 text-sm">
<iconify-icon icon="lucide:shield-check" width="18" class="text-forest-500 dark:text-forest-300 flex-shrink-0"></iconify-icon>
<span>ضمانت اصالت و کیفیت</span>
</div>
</div>
</div>
</div>
<!-- ===== PRODUCT TABS ===== -->
<div class="mt-12 border-t border-cream-200 dark:border-dark-border pt-8 motayeb-product-tabs">
<div class="flex gap-6 border-b border-cream-200 dark:border-dark-border mb-6 overflow-x-auto" role="tablist">
<button onclick="switchTab('desc', this)" class="tab-btn tab-active pb-3 text-sm font-bold border-b-2 border-transparent whitespace-nowrap transition" role="tab" aria-selected="true">توضیحات</button>
<button onclick="switchTab('additional', this)" class="tab-btn pb-3 text-sm font-bold border-b-2 border-transparent text-cream-500 dark:text-dark-muted whitespace-nowrap transition" role="tab" aria-selected="false">اطلاعات بیشتر</button>
<?php if ( ! empty( $ingredients ) ) : ?>
<button onclick="switchTab('ingredients', this)" class="tab-btn pb-3 text-sm font-bold border-b-2 border-transparent text-cream-500 dark:text-dark-muted whitespace-nowrap transition" role="tab" aria-selected="false">ترکیبات</button>
<?php endif; ?>
<?php if ( ! empty( $benefits ) && is_array( $benefits ) ) : ?>
<button onclick="switchTab('benefits', this)" class="tab-btn pb-3 text-sm font-bold border-b-2 border-transparent text-cream-500 dark:text-dark-muted whitespace-nowrap transition" role="tab" aria-selected="false"><?php echo esc_html( $benefits_title ); ?></button>
<?php endif; ?>
<button onclick="switchTab('reviews', this)" class="tab-btn pb-3 text-sm font-bold border-b-2 border-transparent text-cream-500 dark:text-dark-muted whitespace-nowrap transition" role="tab" aria-selected="false">نظرات (<?php echo esc_html( number_format_i18n( $review_count ) ); ?>)</button>
</div>
<!-- تب توضیحات -->
<div id="tab-desc" class="tab-content" role="tabpanel">
<div class="max-w-3xl motayeb-prose text-cream-500 dark:text-dark-muted leading-relaxed">
<?php the_content(); ?>
</div>
</div>
<!-- تب اطلاعات بیشتر -->
<div id="tab-additional" class="tab-content hidden" role="tabpanel">
<div class="max-w-3xl">
<?php do_action( 'woocommerce_product_additional_information', $product ); ?>
</div>
</div>
<!-- ===== ترکیبات (از متافیلد) ===== -->
<?php if ( ! empty( $ingredients ) ) : ?>
<div id="tab-ingredients" class="tab-content hidden" role="tabpanel">
<div class="max-w-3xl motayeb-prose text-cream-500 dark:text-dark-muted leading-relaxed">
<?php echo wp_kses_post( $ingredients ); ?>
</div>
</div>
<?php endif; ?>
<!-- ===== خواص سلامت (از متافیلد آرایه‌ای با آیکون انتخابی) ===== -->
<?php if ( ! empty( $benefits ) && is_array( $benefits ) ) : ?>
<div id="tab-benefits" class="tab-content hidden" role="tabpanel">
<div class="max-w-3xl grid md:grid-cols-2 gap-4">
<?php foreach ( $benefits as $item ) :
$icon = isset( $item['icon'] ) && ! empty( $item['icon'] ) ? $item['icon'] : 'heart-pulse';
$color = isset( $item['color'] ) && ! empty( $item['color'] ) ? $item['color'] : 'text-red-400';
?>
<div class="flex items-start gap-3 p-4 bg-cream-50 dark:bg-dark-bg rounded-xl">
<iconify-icon icon="lucide:<?php echo esc_attr( $icon ); ?>" width="20" class="<?php echo esc_attr( $color ); ?> mt-0.5 flex-shrink-0"></iconify-icon>
<div>
<div class="font-bold text-sm mb-1"><?php echo esc_html( $item['title'] ); ?></div>
<p class="text-xs text-cream-500 dark:text-dark-muted"><?php echo esc_html( $item['description'] ); ?></p>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<!-- تب نظرات -->
<div id="tab-reviews" class="tab-content hidden" role="tabpanel">
<div class="max-w-3xl">
<?php comments_template(); ?>
</div>
</div>
</div>
<!-- ===== RELATED PRODUCTS ===== -->
<?php
// استفاده از تابع صحیح ووکامرس
$related_ids = $product->get_related( 4 );
if ( ! empty( $related_ids ) ) :
?>
<div class="mt-16">
<h3 class="text-xl font-bold mb-6">محصولات مرتبط</h3>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 motayeb-related">
<?php foreach ( $related_ids as $related_id ) :
$related_post = get_post( $related_id );
if ( ! $related_post || $related_post->post_status !== 'publish' ) continue;
setup_postdata( $related_post );
$related_product = wc_get_product( $related_id );
if ( ! $related_product ) continue;
// استفاده از content-product.php برای سازگاری
global $product;
$original_product = $product;
$product = $related_product;
wc_get_template_part( 'content', 'product' );
$product = $original_product;
wp_reset_postdata();
endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
</section>
<script>
// تابع کمکی تغییر تعداد
function motayebQtyChange(delta) {
var input = document.getElementById('product-qty-input');
if (!input) return;
var current = parseInt(input.value) || 1;
var newVal = current + delta;
var min = parseInt(input.getAttribute('min')) || 1;
var max = parseInt(input.getAttribute('max')) || 9999;
if (newVal < min) newVal = min;
if (newVal > max) newVal = max;
input.value = newVal;
}
</script>
<?php
endwhile;
get_footer();