本篇文章更新時間: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_wp_text_input( array( 'id' => '_XXX_sale_price', 'data_type' => 'price', 'label' => '點數標價', 'description' => '設定此商品售價', ) ); woocommerce_wp_select( array( 'id' => '_XXX_product_OOO', 'label' => '設定 XXX', 'options' => $options, 'description' => '設定內容', ) ); woocommerce_wp_hidden_input($hidden_args); echo "
"; });

這部分真的是沒看到 WooCommerce 文件哪裡有描述,只能憑藉著他的設計邏輯去測試看看,測試出來的結果!

首先要看下面這張圖解:

file

  1. 選擇商品類型,也就是新註冊的類型會在這邊出現。
  2. 預設的子類型,用來判斷是虛擬又或者是數位檔案的商品。
  3. 商品設定頁籤群組
  4. 對應頁籤的設定項目

首先就是 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 這樣基底去實作的最大好處。


Share:

作者: Chun

資訊愛好人士。主張「人人都該為了偷懶而進步」。期許自己成為斜槓到變進度條 100% 的年輕人。[///////////____36%_________]

參與討論

1 則留言

  1. 自動引用通知: [WooCommerce] 增加購物車商品項目中繼資料 Meta 的方法 – 一介資男

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *


文章
Filter
Mastodon