本篇文章更新時間:2023/08/09
如有資訊過時或語誤之處,歡迎使用 Contact 功能通知。
一介資男的 LINE 社群開站囉!歡迎入群聊聊~
如果本站內容對你有幫助,歡迎使用 BFX Pay 加密貨幣 或 新台幣 贊助支持。
剛好最近的案件有這個深度客製化需求,繼承 WooCommerce 預設的「商品」並改寫成「OO商品」的做法。不同以往只是單純使用這套外掛內建功能來販售,而是整合這套電商外掛的各部分功能模組,來實現客戶端對消費體驗的設計。
是怎樣的設計就不多寫了,主要會分下面四個部分來拆解改造的主軸:
繼承商品類型
// 註冊 XXX 商品
class XXX_product extends WC_Product {
public function __construct($product) {
$this->product_type = 'XXX';
parent::__construct($product);
}
public function get_type() {
return 'XXX';
}
// 某個設定沒完成,預設就不顯示於搜尋與商品頁面
public function get_catalog_visibility($context = 'view') {
$complete = $this->get_meta('_XXX_product_process_completed', true);
if ($complete == 0 || $complete == '') {
return 'hidden';
} else {
return 'visible';
}
}
// 預設不給公開下單(但寫在這裡沒用,要透過 woocommerce_is_purchasable 勾點處理)
public function is_purchasable() {
return false;
}
}
不論是繼承 WC_product
或是 WC_Product_Simple
都可以,這邊要區別「新款商品類型」關鍵是 product_type
變數的設定與 get_type()
方法回傳。
類型名稱建議全小寫,不然接下來在其他地方運用的時候,有些會是輸出全小寫,有些又是你當初設定的大小寫形式,會錯亂!
再來就是參考 WC_Product 類別中設計的方法來繼承,這邊舉例 get_catalog_visibility()
會是用來控制用此類別建立的商品「可不可以在網站中被找到」,如果商品還沒有準備好,就可以透過 meta
的控制來限制他的「曝光程度」。
其次關於「可不可以下單這類型商品」,預設雖有方法 is_purchasable()
但這邊會被 woocommerce_is_purchasable
這個勾點給覆蓋設定,所以如果要控制可不可以下單這件事,要註冊這個勾點並提供一個下單邏輯處理。
繼承商品處理的這部分其實還有更深一點的 WC_Product_Data_Store_CPT 可以玩,算是管理到整個新類型商品的後端資料流。讓你有客製化彈性來去選擇新類型商品要怎麼儲存資料。例如轉拋資料到某個後勤管理系統、庫存系統等。本篇簡單筆記就不多著墨了!
註冊此類型商品
到這邊準備好一個商品類別後,就是「向 WooCommerce 註冊此類型商品。
add_filter('product_type_selector', function ($types) {
$types = array(); //移除其他類型
$types['XXX'] = 'XXX 商品';
return $types;
}, 11, 1);
add_filter('woocommerce_product_class', function ($classname, $product_type) {
if ($product_type == 'XXX') {
$classname = 'XXX_product';//新類型的類別名
}
return $classname;
}, 11, 2);
註冊 product_type_selector
勾點,把新類型加入,當然同時也可以管理到其他的類型,就看這邊需求如何!
其次註冊 woocommerce_product_class
勾點,跟 WooCommerce 提供如果是這個新類型商品,要找到哪個類別來對應。
這邊要特別注意的是這個新類別的作用範圍(scope)問題,如果無法公開取得這個類別會導致「看似有這個商品類型可以用,但實際上資料無法儲存」的問題。
為新類型商品建立控制項目分頁
到這個步驟後,新商品類型基礎算是處理好,接下來就是準備一個對這商品類型提供控制的操作介面部分。
add_filter('product_type_options', function ($opt) {
// 如果是 XXX 類型商品,顯示「虛擬」這個設定項目
$opt['virtual']['wrapper_class'] .= ' show_if_XXX';
return $opt;
}, 11, 1);
add_filter('woocommerce_product_data_tabs', function ($tabs) {
$tabs['XXX'] = array(
'label' => 'XXX 商品設定',
'target' => 'XXX_product_data',
'class' => array('show_if_XXX'),
'priority' => 10,
);
$tabs['general']['class'][] = 'hide_if_XXX'; // 一般
$tabs['inventory']['class'][] = 'hide_if_XXX'; // 庫存
$tabs['shipping']['class'][] = 'hide_if_XXX'; // 運送方式
$tabs['linked_product']['class'][] = 'hide_if_XXX'; // 連結商品
$tabs['attribute']['class'][] = 'hide_if_XXX'; // 屬性
$tabs['variations']['class'][] = 'hide_if_XXX'; // 變化類型
return $tabs;
}, 11, 1);
add_action('woocommerce_product_data_panels', function () {
global $post, $product_object, $wpdb;
echo "
";
});
這部分真的是沒看到 WooCommerce 文件哪裡有描述,只能憑藉著他的設計邏輯去測試看看,測試出來的結果!
首先要看下面這張圖解:
- 選擇商品類型,也就是新註冊的類型會在這邊出現。
- 預設的子類型,用來判斷是虛擬又或者是數位檔案的商品。
- 商品設定頁籤群組
- 對應頁籤的設定項目
首先就是 woocommerce_product_data_tabs
這個勾點會處理輸出商品可以設定的頁籤,而處理「看不看得到」是用動態輸出的 CSS 來做判斷,不難理解的設計「show_if_XXX
」或「hide_if_XXX
」,當選到的類型是 XXX 時,有 show
的 class 元素就會顯示,反之 hide
就會隱藏。用此方式來控制這一個商品設定區塊。
到頁籤管理這塊也不是說建立好自己的頁籤就好。還要去斟酌是否要把其他設定選項隱藏等操作。
然後設定頁籤屬性中的 target
就是指在第四區塊的頁籤設定項目(一個 div
元素)。
使用 woocommerce_product_data_panels
此勾點來產生該 div
元素區塊。結構上照寫,而其中那些項目設定欄位都可以使用 WooCommerce 提供的欄位輸出方法來置換。
除了本篇簡單舉例的 woocommerce_wp_text_input
文字輸入框、woocommerce_wp_select
下拉選單 與 woocommerce_wp_hidden_input
隱藏欄位之外,還有更多詳細參數可以參考 WooCommerce 原始碼中的 includes/admin/wc-meta-box-functions.php
這份文件。
以上,就完成了一個新商品類型的建立與使用者端互動的介面部分。
其實這邊就算不要「這麼 WooCommerce」的方式來解決這些設定需求,搭配 ACF 這些客製化欄位也不是不行,純粹就是有個介面能讓網站操作者可以輸入就好,並不是「必要」的! 只是如果要與多個類型的商品去互動,其他做法也不會比較省事就是。
儲存新類型商品的設定
最後,既然都建立好一個設定互動介面,那只差把這些設定記錄起來保存,完成建立一個新類型商品的流程。
add_action('woocommerce_process_product_meta', function ($product_id, $product) {
$product_type = WC_Product_Factory::get_product_type($product_id);
if ('XXX' === $product_type) {
$sale_price = isset($_POST['_XXX_sale_price']) ? intval($_POST['_XXX_sale_price']) : 0;
$product = wc_get_product($product_id);
$product->set_regular_price($sale_price); //標價及售價
$product->set_manage_stock(false); //不管庫存
$product->update_meta_data('_XXX_sale_price', $sale_price);
$product->set_virtual(true);
$product->save();
}
}, 11, 2);
這步驟只需要使用 woocommerce_process_product_meta
勾點來處理即可。
要注意得部分是如果要取得正確的商品類型(product_type
),建議使用 WC_Product_Factory::get_product_type($product_id)
此方法來處理。那個「get_type()
」總是回傳「simple
(簡單商品)」的問題至少已經存在八年以上了!
確認當下儲存的商品是客製化開發的新類型後,其他就是商品方法的操作與 meta
中繼資料的更新而已。
結語
架構大致就是這樣,其他都會是開發過程中要去重新設計流程/體驗的部分。簡單筆記如下:
// 移除預設商店頁面顯示加入購物車按鈕(保留內頁的)
remove_action('woocommerce_after_shop_loop_item', 'woocommerce_template_loop_add_to_cart', 10);
// 移除商店頁面顯示的價格標籤
remove_action('woocommerce_after_shop_loop_item_title', 'woocommerce_template_loop_price', 10);
// 移除商品內頁顯示的價格標籤
remove_action('woocommerce_single_product_summary', 'woocommerce_template_single_price', 10);
// 關閉內建 add-to-cart URL加入購物車功能
remove_action('wp_loaded', 'WC_Form_Handler::add_to_cart_action', 20);
// 不開放 XXX 商品透過前端下單
add_filter('woocommerce_is_purchasable', function ($is_purchasable, $product) {
$product_type = WC_Product_Factory::get_product_type($product->get_id());
if ('XXX' == $product_type) {
//如果是 XXX 類型商品,就提示+不給下單
wc_add_notice('你走錯地方了,理論上應該不會到這裡的才是。', 'error');
return false;
}
return $is_purchasable;
}, 11, 2);
// 後台調整商品列表名稱顯示
add_action('manage_product_posts_custom_column', function ($column, $post_id) {
if ($column === 'name') {
echo '根據條件輸出資料,強化商品列表中 XXX 類型商品的描述';
}
}, 11, 2);
// 給 XXX 類型商品補上「加入購物車」的功能。預設新的類型商品不會有註冊這個勾點
add_action('woocommerce_XXX_add_to_cart', function () {
global $product;
// 使用 WooCommerce 預設的「加入購物車」按鈕和方法
// wc_get_template('single-product/add-to-cart/simple.php');
// 使用自己客製化的樣式來套用,搭配 wc_get_template 方法讓範本可以被主題繼承
wc_get_template(
'XXX_FOLDER/XXX_add_to_cart.php',
array(
'product' => $product,
),
'',
trailingslashit(__DIR__)
);
});
善用 WooCommerce 保留的勾點和方法來處理全站架構,比起從頭到尾都客製化寫過算是輕鬆的了!
搭配已經宣告整合好 WooCommerce 的主題來開發,這絕對是使用 WordPress 這樣基底去實作的最大好處。