Skip to content

functions.php

text
================================================================================
 ctkpro-childtheme / functions.php 詳細解說
================================================================================

檔案位置:wp-content/themes/ctkpro-childtheme/functions.php
總行數  :142 行
語系    :繁體中文(台灣)
編碼    :UTF-8(無 BOM)

本文件依「行號區間 → 功能標題 → 用途/邏輯/注意事項」結構,逐段拆解
functions.php 內所有客製邏輯,方便接手團隊快速掌握每段程式碼的目的與相依。


--------------------------------------------------------------------------------
 全檔結構速覽
--------------------------------------------------------------------------------

  行號         | 功能名稱                                       | 類型
  -------------|------------------------------------------------|--------
  L1           | <?php 開檔                                     | 結構
  L3  - L8     | ctkpro_theme_enqueue_styles                    | 主題初始化
  L10 - L24    | ctkpro_enqueue_styles_scripts                  | 資源載入
  L26 - L34    | add_slug_body_class                            | body class
  L36 - L43    | ctkpro_reset_theme_mods                        | 切換主題
  L45 - L62    | ctkpro_customize_register                      | 客製器
  L64 - L80    | add_wc_order_list_shipping_column              | WC 訂單列表欄位
  L82 - L108   | display_wc_order_list_shipping_column_content  | WC 訂單列表內容
  L110 - L141  | ctk_store_complete                             | 超商付款自動完成
  L142         | ?> 結束


================================================================================
 L1            區段:開檔
================================================================================

  L1: <?php

  說明:標準 PHP 開檔。整個檔案結尾於 L142 以 ?> 收尾。

  注意:WordPress 慣例上 functions.php 不建議寫 ?> 結束標籤(避免尾端
        空白導致 "headers already sent")。本檔案有保留 ?>,接手後若調整
        請小心檔尾不要有任何空白或換行。


================================================================================
 L3 - L8       函式:ctkpro_theme_enqueue_styles
                Hook:after_setup_theme
================================================================================

  L3:  function ctkpro_theme_enqueue_styles() {
  L4:      if (is_admin()) return;
  L5:
  L6:      require_once get_template_directory() . '/functions.php';
  L7:  }
  L8:  add_action('after_setup_theme', 'ctkpro_theme_enqueue_styles');

  用途:
    在「前台」主動載入父主題 sober 的 functions.php。

  邏輯:
    - 透過 is_admin() 判斷僅在前台執行(後台已由 WP 自動載入父主題)。
    - require_once 確保父主題函式只載入一次。

  相依:
    - 父主題 sober 必須存在於 wp-content/themes/sober/。

  注意:
    - 若父主題改名或被移除,所有依賴父主題函式的功能(如 sober_header_icons)
      將會崩潰。


================================================================================
 L10 - L24     函式:ctkpro_enqueue_styles_scripts
                Hook:wp_enqueue_scripts
================================================================================

  L11: function ctkpro_enqueue_styles_scripts() {
  L12:     $version = wp_get_theme()->get('Version');
  L13:
  L14:     wp_enqueue_style('parent-style', get_template_directory_uri() . '/style.css', array(), $version);
  L15:     wp_enqueue_style('theme-style', get_stylesheet_directory_uri(). '/css/theme-update.css', array('parent-style'), $version);
  L16:
  L17:     wp_enqueue_script('custom-script',
  L18:         get_stylesheet_directory_uri() . '/js/main.min.js',
  L19:         array('jquery'),
  L20:         $version,
  L21:         true
  L22:     );
  L23: }
  L24: add_action( 'wp_enqueue_scripts', 'ctkpro_enqueue_styles_scripts' );

  用途:
    前台載入 (1) 父主題 style.css、(2) 子主題覆寫 css/theme-update.css、
    (3) 子主題 js/main.min.js。

  邏輯:
    - 取得子主題 Version(style.css 內宣告 1.0.0)作為 cache buster。
    - 'theme-style' 依賴 'parent-style',確保載入順序:
        parent-style → theme-style
    - main.min.js 載入於 footer(in_footer = true),相依 jQuery。

  注意:
    - SCSS 編譯後輸出到 css/theme-update.css,請勿手改 css 檔。
    - JS 修改時請編輯 js/main.js 後 minify 為 main.min.js(實際上線載入的是 min 版)。
    - 改 Version 數字(在 style.css)能強制清除快取。


================================================================================
 L26 - L34     函式:add_slug_body_class
                Hook:body_class(filter)
================================================================================

  L27: function add_slug_body_class($classes) {
  L28:     global $post;
  L29:     if (isset($post)) {
  L30:         $classes[] = $post->post_type . '-' . $post->post_name;
  L31:     }
  L32:     return $classes;
  L33: }
  L34: add_filter('body_class', 'add_slug_body_class');

  用途:
    為 <body> 加上以「post_type-post_name」格式的 class,方便前端針對
    特定頁面寫 CSS(例如 .page-checkout、.product-xxx)。

  範例:
    - 商品 slug 為 elo-001 → body class 加上 "product-elo-001"
    - 頁面 slug 為 about → body class 加上 "page-about"

  注意:
    - 僅在有 $post 全域變數時生效(彙整頁、404 等可能沒有)。
    - 名稱中含中文、空白或特殊字元會原樣輸出,CSS 選擇器需自行處理。


================================================================================
 L36 - L43     函式:ctkpro_reset_theme_mods
                Hook:after_switch_theme
================================================================================

  L37: function ctkpro_reset_theme_mods() {
  L38:     $parent_mods = get_option('theme_mods_sober');
  L39:     if ($parent_mods) {
  L40:         update_option('theme_mods_' . get_stylesheet(), $parent_mods);
  L41:     }
  L42: }
  L43: add_action('after_switch_theme', 'ctkpro_reset_theme_mods');

  用途:
    每次切換主題(含啟用子主題)時,把父主題 sober 的客製器設定
    (theme_mods_sober)複製到子主題(theme_mods_{stylesheet})。

  原因:
    WordPress 對父子主題的 theme_mods 是分開儲存的。若僅啟用子主題而
    沒有複製,會導致 customizer 設定(如 logo、配色、選單等)全部歸零。

  邏輯:
    - get_stylesheet() 取得當前子主題目錄名(即 ctkpro-childtheme)。
    - update_option('theme_mods_ctkpro-childtheme', $parent_mods)。

  注意:
    - 觸發時機是「切換主題」當下,而非每次載入頁面。
    - 若父主題 mods 為空,不執行 update(避免清空既有設定)。


================================================================================
 L45 - L62     函式:ctkpro_customize_register
                Hook:customize_register(priority 11)
================================================================================

  L46: function ctkpro_customize_register($wp_customize) {
  L47:     // 檢查父主題的客製化設定檔案是否存在
  L48:     $parent_customizer_file = get_template_directory() . '/inc/customizer.php';
  L49:     if (file_exists($parent_customizer_file)) {
  L50:         include_once $parent_customizer_file;
  L51:     }
  L52:
  L53:     // 不直接實例化 Sober_Customize 類別
  L54:     // 而是複製必要的設定
  L55:     $parent_mods = get_theme_mods();
  L56:     if ($parent_mods) {
  L57:         foreach ($parent_mods as $key => $value) {
  L58:             set_theme_mod($key, $value);
  L59:         }
  L60:     }
  L61: }
  L62: add_action('customize_register', 'ctkpro_customize_register', 11);

  用途:
    在 WP 客製器(外觀 → 自訂)註冊階段,載入父主題 sober 的
    customizer.php,並複製當前 theme mods,確保子主題能繼承父主題的
    customizer 控制項與資料。

  邏輯:
    - priority 11 確保此 hook 在父主題(priority 10)之後執行。
    - 不直接 new Sober_Customize 類別,避免重複註冊衝突。
    - 透過 get_theme_mods() + set_theme_mod() 把目前所有設定再次寫入
      (等同確保 mods 正確同步於子主題 stylesheet 名稱)。

  注意:
    - 若父主題改名或結構調整(不再有 inc/customizer.php),此區塊會
      靜默失敗,customizer 控制項可能消失。


================================================================================
 L64 - L80     函式:add_wc_order_list_shipping_column
                Hook:manage_woocommerce_page_wc-orders_columns(priority 20)
                關聯:WooCommerce HPOS 訂單列表
================================================================================

  L65: add_filter('manage_woocommerce_page_wc-orders_columns', 'add_wc_order_list_shipping_column', 20);
  L66: add_action('manage_woocommerce_page_wc-orders_custom_column', 'display_wc_order_list_shipping_column_content', 20, 2);
  L67:
  L68: function add_wc_order_list_shipping_column($columns) {
  L69:     $new_columns = array();
  L70:
  L71:     foreach ($columns as $column_name => $column_info) {
  L72:         $new_columns[$column_name] = $column_info;
  L73:
  L74:         if ('order_status' === $column_name) {
  L75:             $new_columns['shipping_column'] = '運送方式';
  L76:         }
  L77:     }
  L78:
  L79:     return $new_columns;
  L80: }

  用途:
    在 WooCommerce 訂單列表(後台「訂單」頁)的「狀態」欄位之後,
    插入一個自訂的「運送方式」欄位。

  邏輯:
    - 走訪原本的欄位陣列,找到 'order_status' 之後立即插入 'shipping_column'。
    - 用 foreach + 重新建立陣列的方式控制欄位順序(PHP 關聯陣列無法在中間
      插入鍵值,故採此寫法)。

  hook 名稱說明:
    - manage_woocommerce_page_wc-orders_columns 是 WooCommerce 「HPOS
      (高效能訂單儲存)」啟用後的訂單列表 hook。
    - 若未啟用 HPOS(仍使用 wp_posts 儲存訂單),此 hook 不會觸發,
      需要改用 manage_edit-shop_order_columns。

  注意:
    - priority 20 確保在 WC 預設欄位都已加入後再插入。
    - 若未來 WC 重新命名 'order_status' 欄位 key,此插入位置會失效。


================================================================================
 L82 - L108    函式:display_wc_order_list_shipping_column_content
                Hook:manage_woocommerce_page_wc-orders_custom_column(priority 20)
================================================================================

  L82: function display_wc_order_list_shipping_column_content($column, $order) {
  L83:     if ($column == 'shipping_column') {
  L84:         // 獲取訂單的配送方式
  L85:         $shipping_items = $order->get_items('shipping');
  L86:
  L87:         foreach ($shipping_items as $item_id => $shipping_item_obj) {
  L88:             // 獲取配送數據
  L89:             $shipping_item_data = $shipping_item_obj->get_data();
  L90:             $shipping_method_title = isset($shipping_item_data['method_title']) ? $shipping_item_data['method_title'] : '';
  L91:
  L92:             // 檢查是否為超商取貨相關的配送方式
  L93:             if (strpos($shipping_method_title, '超商') !== false ||
  L94:                 $shipping_method_title == '超商取貨付款' ||
  L95:                 $shipping_method_title == '超商取貨') {
  L96:
  L97:                 // 使用get_post_meta獲取超商門市名稱
  L98:                 $order_id = $order->get_id();
  L99:                 $store_name = get_post_meta($order_id, '_newebpayStoreName', true);
  L100:
  L101:                // 檢查門市名稱是否不為空
  L102:                if (!empty($store_name)) {
  L103:                    echo '<span style="color: #ca4a1f">超取需出貨</span>';
  L104:                }
  L105:            }
  L106:        }
  L107:    }
  L108: }

  用途:
    當訂單為「超商取貨」且已綁定門市(有 _newebpayStoreName meta)時,
    在訂單列表的「運送方式」欄位顯示橘紅色文字「超取需出貨」,
    讓客服一眼看出哪些訂單需要備貨送往超商。

  邏輯:
    1. 只處理 'shipping_column'(其他欄位忽略)。
    2. 取出訂單的所有 shipping items。
    3. 比對 method_title 是否含「超商」字樣(涵蓋「超商取貨」「超商取貨付款」
       「7-11 超商取貨」等)。
    4. 取 _newebpayStoreName meta(藍新 plugin 會在訂單建立時寫入)。
    5. 若門市名稱不為空,輸出醒目提示。

  相依:
    - Plugin:藍新金流(NewebPay)— 提供 _newebpayStoreName meta key。
    - 訂單需經過藍新超商取貨流程才會有此 meta。

  變更歷史:
    - 9e8610a [dev] 訂單列表超取顯示判斷修改
    - 33545db [dev] WooCommerce 訂單列表添加超取偵測(首次新增)

  注意:
    - 顏色寫死在 inline style(#ca4a1f),如要統一管理可移到 CSS。
    - 若改用其他金流的超商取貨,需擴充 meta key 判斷。


================================================================================
 L110 - L141   函式:ctk_store_complete
                Hook:woocommerce_order_status_changed
================================================================================

  L111: add_action('woocommerce_order_status_changed', 'ctk_store_complete', 10, 3);
  L112: function ctk_store_complete($order_id, $old_status, $new_status){
  L113:     if( $old_status == 'pending' && $new_status == 'processing'){
  L114:         global $wpdb;
  L115:         $is_store_pay = get_post_meta( $order_id, '_newebpayStoreName', true ) ?: false;
  L116:
  L117:         $sql = "SELECT DISTINCT comment_post_ID, comment_date_gmt, comment_approved, SUBSTRING(comment_content,1,100) AS com_excerpt FROM $wpdb->comments WHERE comment_approved = '1' AND comment_post_ID = {$order_id} ORDER BY comment_date_gmt ASC LIMIT 3";
  L118:     $comments = $wpdb->get_results($sql);
  L119:
  L120:     $comment = $comments[0]->com_excerpt;
  L121:     $comment2 = isset($comments[1]) ? $comments[1]->com_excerpt : '';
  L122:
  L123:     $is_credit = strpos($comment, '信用卡') !== false || strpos($comment2, '信用卡') !== false;
  L124:     $is_taiwanpay = strpos($comment, 'TAIWANPAY') !== false || strpos($comment2, 'TAIWANPAY') !== false;
  L125:     $is_store_code_payment = strpos($comment, '超商代碼繳費') !== false || strpos($comment2, '超商代碼繳費') !== false;
  L126:
  L127:     error_log("is_store_pay = $is_store_pay");
  L128:     error_log("comments = " . print_r($comments, true));
  L129:     error_log("comment = $comment");
  L130:     error_log("comment2 = $comment2");
  L131:     error_log("is_credit = $is_credit");
  L132:     error_log("is_taiwanpay = $is_taiwanpay");
  L133:     error_log("is_store_code_payment = $is_store_code_payment");
  L134:     // 超商取貨已先付,不進行跳轉
  L135:     if( $is_credit || $is_taiwanpay || $is_store_code_payment ){ return; }
  L136:     if( $is_store_pay ){
  L137:         $order = wc_get_order( $order_id );
  L138:         $order->update_status('completed', '超商付款完成');
  L139:     }
  L140:    }
  L141: }

  用途:
    當訂單狀態從「pending(等待付款)」變更為「processing(處理中)」時,
    若判定為「超商取貨已先付款」的情境,自動把訂單狀態跳到「completed」,
    避免客服還要手動處理。

  邏輯流程:
    1. 僅在 pending → processing 觸發。
    2. 取訂單留言(comment)前 3 筆,作為「付款方式判斷依據」。
       — 藍新金流回傳結果通常會以訂單留言記錄付款方式(如「信用卡 ********」、
         「TAIWANPAY 結帳」、「超商代碼繳費」等)。
    3. 解析留言文字,判斷是否為以下類型;若是則「不」自動完成(return):
         - 信用卡
         - TAIWANPAY
         - 超商代碼繳費
       原因:這幾種付款方式的訂單仍需要走「出貨流程」,不能直接 completed。
    4. 若有 _newebpayStoreName 且不屬於上述 3 種付款方式,
       視為「超商取貨已先付款」→ 直接更新訂單狀態為 completed,
       並附上 admin note「超商付款完成」。

  相依:
    - Plugin:藍新金流(NewebPay)— 提供 _newebpayStoreName meta 與訂單留言。
    - DB:直接查詢 wp_comments 資料表。

  變更歷史:
    - 4a31511 [dev] 判斷超商付款完成更改訂單狀態(首次新增)
    - f4053bf [dev] 判斷超商付款完成更改訂單狀態排除超商代碼方式
                   (新增 $is_store_code_payment 排除)

  ⚠️ 重要注意事項:
    1. 第 117 行原生 SQL 直接組字串:
         "WHERE ... comment_post_ID = {$order_id}"
       雖然 $order_id 來自 WC hook 應為整數,但仍建議改用
       $wpdb->prepare() 以防未來呼叫端變更。
    2. 第 127 - 133 行 error_log() 是除錯用,**正式環境建議移除**,
       否則每筆訂單狀態變更都會產生大量日誌。
    3. 此邏輯依賴「訂單留言文字」做判斷,若藍新 plugin 升級後改變留言
       格式(例如「信用卡」改成「Credit Card」),判斷會失效。
       — 建議改用 _payment_method(WC 內建 meta)會更穩定。
    4. LIMIT 3 假設付款相關留言會在最早期 3 筆內,若訂單一開始就有大量
       自動留言可能漏判。


================================================================================
 L142          區段:結束
================================================================================

  L142: ?>

  說明:PHP 結束標籤。如前述,functions.php 在 WordPress 慣例上不建議
        保留 ?>,但本檔案保留。修改後請務必確保檔尾沒有任何空白或換行。


================================================================================
 附錄:對照表
================================================================================

【關聯 Plugin】
  - 藍新金流 NewebPay
      → 提供 _newebpayStoreName meta key
      → 提供訂單留言(含付款方式關鍵字「信用卡」「TAIWANPAY」「超商代碼繳費」)
  - SUMO Reward Points(rewardsystem)
      → 與本檔案無直接 hook 關聯
      → 透過 [rs_my_reward_points] shortcode 在 template-parts/header-v4.php
        顯示購物金

【關聯 Meta keys】
  - _newebpayStoreName
      → 出現於 L99(訂單列表標示)
      → 出現於 L115(自動完成判斷)

【關聯 Options / Theme Mods】
  - theme_mods_sober          → L38(after_switch_theme 時複製)
  - theme_mods_{stylesheet}   → L40(複製目標)

【依賴的父主題函式】
  - sober 主題的 functions.php 內所有公用函式
      → 透過 L6 的 require_once 載入
  - sober 主題的 inc/customizer.php
      → 透過 L48 - L51 的 include_once 載入

================================================================================
 文件結束
================================================================================