🎁 فصل ۸: Custom Product Type – نوع محصول سفارشی
ووکامرس بهصورت پیشفرض ۴ نوع محصول دارد (ساده، متغیر، گروهی، خارجی). در این فصل یاد میگیرید چطور نوع محصول کاملاً جدید بسازید — مثلاً «فرش سفارشی» با اندازه دلخواه یا «کارت هدیه».
🤔 چرا Custom Product Type؟
گاهی محصول شما با ساده/متغیر سازگار نیست:
| نیاز | راهحل ووکامرس پیشفرض | راهحل بهینه |
|---|---|---|
| فرش با اندازه دلخواه (طول × عرض) | ندارد! | Custom Product Type |
| کارت هدیه با مبلغ دلخواه | متغیر (محدود) | Custom Product Type |
| محصول قابل پیکربندی (PC build) | افزونه Composite | Custom Product Type |
| اشتراک ماهانه | WC Subscriptions | Custom Product Type |
🏗️ ساخت کلاس محصول
هر نوع محصول جدید باید کلاس WC_Product یا WC_Product_Simple را extend کند:
includes/class-wc-product-custom-carpet.php
<?php
defined('ABSPATH') || exit;
class WC_Product_Custom_Carpet extends WC_Product {
public function __construct($product = 0) {
$this->product_type = 'custom_carpet';
parent::__construct($product);
}
public function get_type() {
return 'custom_carpet';
}
/**
* این محصول قابل خرید است
*/
public function is_purchasable() {
return true;
}
/**
* موجودی محدود ندارد - بر اساس سفارش بافته میشود
*/
public function is_in_stock() {
return true;
}
/**
* قیمت متناسب با اندازه محاسبه میشود
*/
public function get_price($context = 'view') {
$base_price = parent::get_price($context);
return $base_price; // قیمت پایه - محاسبه واقعی در سبد
}
/**
* تنظیمات اختصاصی محصول
*/
public function get_price_per_meter() {
return floatval($this->get_meta('_price_per_meter'));
}
public function get_min_size() {
return floatval($this->get_meta('_min_size_m2') ?: 1);
}
public function get_max_size() {
return floatval($this->get_meta('_max_size_m2') ?: 50);
}
public function get_production_days() {
return absint($this->get_meta('_production_days') ?: 30);
}
}
📝 ثبت در ووکامرس
custom-carpet-product-type.php
<?php
/**
* Plugin Name: ICSD Custom Carpet Product Type
*/
defined('ABSPATH') || exit;
// 1. اضافه کردن به لیست انواع محصول
add_filter('product_type_selector', function($types) {
$types['custom_carpet'] = 'فرش سفارشی';
return $types;
});
// 2. ثبت taxonomy term (برای فیلتر و query)
add_action('init', function() {
if (!term_exists('custom_carpet', 'product_type')) {
wp_insert_term('custom_carpet', 'product_type');
}
});
// 3. ثبت کلاس
add_filter('woocommerce_product_class', function($classname, $product_type) {
if ($product_type === 'custom_carpet') {
require_once __DIR__ . '/includes/class-wc-product-custom-carpet.php';
return 'WC_Product_Custom_Carpet';
}
return $classname;
}, 10, 2);
// 4. لود ساختار در فرانتاند
add_action('woocommerce_single_product_summary', 'render_custom_carpet_form', 25);
function render_custom_carpet_form() {
global $product;
if ($product->get_type() !== 'custom_carpet') return;
require __DIR__ . '/templates/single-product/custom-carpet.php';
}
⚙️ تبهای پنل ادمین
برای اینکه ادمین بتواند تنظیمات این نوع محصول را بدهد، باید تب اختصاصی اضافه کنیم:
// 1. اضافه کردن تب
add_filter('woocommerce_product_data_tabs', function($tabs) {
$tabs['custom_carpet'] = [
'label' => '🧵 تنظیمات فرش سفارشی',
'target' => 'custom_carpet_options',
'class' => ['show_if_custom_carpet'],
'priority' => 25,
];
return $tabs;
});
// 2. محتوای تب
add_action('woocommerce_product_data_panels', function() {
global $post;
?>
get_type() !== 'custom_carpet') return;
$fields = [
'_price_per_meter', '_min_size_m2', '_max_size_m2',
'_production_days', '_design_complexity', '_color_options',
];
foreach ($fields as $field) {
if (isset($_POST[$field])) {
$product->update_meta_data($field, sanitize_text_field($_POST[$field]));
}
}
});
// 4. نمایش/مخفی کردن تبهای پیشفرض با CSS
add_action('admin_footer', function() {
global $post;
if (!$post || $post->post_type !== 'product') return;
?>
🖼️ رفتار در فرانتاند
فرم انتخاب اندازه و رنگ:
templates/single-product/custom-carpet.php
<?php
defined('ABSPATH') || exit;
global $product;
$price_per_meter = $product->get_price_per_meter();
$min_size = $product->get_min_size();
$max_size = $product->get_max_size();
$colors = explode("n", $product->get_meta('_color_options'));
?>
🛒 سبد و پرداخت
وقتی محصول به سبد اضافه میشود، باید قیمت بر اساس اندازه محاسبه شود:
// 1. اضافه کردن دادههای سفارشی به سبد
add_filter('woocommerce_add_cart_item_data', function($cart_item_data, $product_id) {
$product = wc_get_product($product_id);
if ($product->get_type() !== 'custom_carpet') return $cart_item_data;
$length = floatval($_POST['carpet_length'] ?? 0);
$width = floatval($_POST['carpet_width'] ?? 0);
$area = $length * $width;
$cart_item_data['custom_carpet'] = [
'length' => $length,
'width' => $width,
'area' => $area,
'color' => sanitize_text_field($_POST['carpet_color'] ?? ''),
'notes' => sanitize_textarea_field($_POST['carpet_notes'] ?? ''),
];
// آپلود فایل
if (!empty($_FILES['carpet_design']['name'])) {
require_once ABSPATH . 'wp-admin/includes/file.php';
$upload = wp_handle_upload($_FILES['carpet_design'], ['test_form' => false]);
if (!isset($upload['error'])) {
$cart_item_data['custom_carpet']['design_url'] = $upload['url'];
}
}
// یونیک کردن کلید (هر اندازه یک ردیف جدا)
$cart_item_data['unique_key'] = md5(microtime() . rand());
return $cart_item_data;
}, 10, 2);
// 2. محاسبه قیمت بر اساس اندازه
add_action('woocommerce_before_calculate_totals', function($cart) {
if (is_admin() && !defined('DOING_AJAX')) return;
foreach ($cart->get_cart() as $item) {
if (empty($item['custom_carpet'])) continue;
$product = $item['data'];
if ($product->get_type() !== 'custom_carpet') continue;
$price_per_meter = floatval($product->get_meta('_price_per_meter'));
$area = floatval($item['custom_carpet']['area']);
$new_price = $price_per_meter * $area;
$product->set_price($new_price);
}
});
// 3. نمایش جزئیات در سبد
add_filter('woocommerce_get_item_data', function($item_data, $cart_item) {
if (empty($cart_item['custom_carpet'])) return $item_data;
$cc = $cart_item['custom_carpet'];
$item_data[] = ['key' => 'اندازه', 'value' => sprintf('%s × %s متر (%s متر مربع)',
$cc['length'], $cc['width'], $cc['area'])];
$item_data[] = ['key' => 'رنگ', 'value' => $cc['color']];
if (!empty($cc['notes'])) {
$item_data[] = ['key' => 'توضیحات', 'value' => $cc['notes']];
}
if (!empty($cc['design_url'])) {
$item_data[] = ['key' => 'نقشه دلخواه',
'value' => 'مشاهده فایل'];
}
return $item_data;
}, 10, 2);
// 4. ذخیره در آیتم سفارش
add_action('woocommerce_checkout_create_order_line_item', function($item, $cart_item_key, $values) {
if (empty($values['custom_carpet'])) return;
$cc = $values['custom_carpet'];
$item->add_meta_data('اندازه', sprintf('%s × %s', $cc['length'], $cc['width']));
$item->add_meta_data('متراژ', $cc['area'] . ' مربع');
$item->add_meta_data('رنگ', $cc['color']);
if (!empty($cc['notes'])) $item->add_meta_data('توضیحات', $cc['notes']);
if (!empty($cc['design_url'])) $item->add_meta_data('فایل نقشه', $cc['design_url']);
}, 10, 3);
🎯 مثال کامل: فرش سفارشی
تصویر کامل عملکرد:
سناریو واقعی برای ICSD:
- ادمین یک محصول از نوع «فرش سفارشی» میسازد، قیمت هر مربع را ۲ میلیون تومان میگذارد
- کاربر در فرانتاند طول ۳ و عرض ۴ متر انتخاب میکند
- JavaScript قیمت را ۲۴ میلیون تومان نمایش میدهد
- کاربر رنگ «شیری» را انتخاب و فایل نقشه را آپلود میکند
- محصول با تمام جزئیات به سبد اضافه میشود
- در پنل ادمین، تمام مشخصات قابل مشاهده است
- بعد از پرداخت، تولید آغاز میشود
اضافه کردن وضعیت سفارش «در حال بافت»
add_action('init', function() {
register_post_status('wc-weaving', [
'label' => 'در حال بافت',
'public' => true,
'show_in_admin_status_list' => true,
'show_in_admin_all_list' => true,
'label_count' => _n_noop('در حال بافت (%s)',
'در حال بافت (%s)'),
]);
});
add_filter('wc_order_statuses', function($statuses) {
$new = [];
foreach ($statuses as $key => $label) {
$new[$key] = $label;
if ($key === 'wc-processing') {
$new['wc-weaving'] = 'در حال بافت';
}
}
return $new;
});
📝 خلاصه فصل
- ساخت کلاس Custom Product Type با extend از WC_Product
- ثبت در product_type_selector و mapping با کلاس
- تب اختصاصی در پنل ادمین با woocommerce_wp_*
- قالب فرانتاند با محاسبه پویا قیمت
- ذخیره دادههای سفارشی در سبد و آیتم سفارش
- وضعیت سفارش سفارشی (مثل «در حال بافت»)
🎯 ایدههای دیگر برای Custom Product Type:
- کارت هدیه با مبلغ دلخواه و تاریخ ارسال
- اشتراک ماهانه قهوه (با انتخاب طعم)
- چاپ سفارشی روی تیشرت (با آپلود طرح)
- کیک تولد با پیام و تصویر دلخواه