標籤: php

  • 解決 PHP 的 dompdf 中文字體亂碼的方法

    解決 PHP 的 dompdf 中文字體亂碼的方法

    記錄一下一個之前解決過的問題。

    在使用 php 的 dompdf 會有中文字無法正確讀取的問題,這主要是字型的關係,官方有提供一個解決方法。

    先說什麼是 dompdf 請參考開源程式說明

    Dompdf 是一個 HTML 到 PDF 轉換器

    從本質上講,dompdf(主要)是一個用 PHP 編寫的、符合CSS 2.1 的HTML 佈局和渲染引擎。它是一個樣式驅動的渲染器:它將下載並讀取外部樣式表、內聯樣式標籤以及各個 HTML 元素的樣式屬性。它還支援大多數表現性 HTML 屬性。

    這是一個 PHP 的工具,有幾點注意:

    1. 如果需要連外部圖片的話需要開啟設定,關鍵字請找:isRemoteEnabled
    2. 圖片或是連結請用絕對路徑。
    3. 不是全支援 CSS 3
    4. 通常會指定紙張樣式,這塊 html 需要考慮平面印刷的特色才行。

    接下來讓 AI 說明,使用 dompdf 支援可使用中文字型的方法。


    要在 Dompdf 中正確顯示中文,您需要引入支援中文的字體。以下是詳細步驟:

    1. 安裝 Dompdf:如果尚未安裝,請使用 Composer 安裝 Dompdf:
       composer require dompdf/dompdf
    1. 下載 load_font.php 工具:從 Dompdf 的 utils 儲存庫下載 load_font.php,並將其放置在 vendor/dompdf/dompdf 目錄下,與 libsrc 目錄同級。
    2. 下載中文字體:取得支援中文的字體檔案(如 simsun.ttfmsyh.ttf),並將其放置在與 load_font.php 相同的目錄中。
    3. 修改 load_font.php:確保 load_font.php 正確載入自動加載器。
       // 確認以下路徑正確
       require_once "../../autoload.php";
    1. 安裝字體:在命令列中執行以下命令來安裝字體:
       php load_font.php 字體名稱 字體檔案.ttf

    例如,若字體名稱為 simsun,字體檔案為 simsun.ttf,則執行:

       php load_font.php simsun simsun.ttf
    1. 確認字體安裝成功:在 vendor/dompdf/dompdf/lib/fonts 目錄中,應該能看到新生成的字體檔案(如 simsun.ttfsimsun.ufm)。此外,檢查 installed-fonts.json 檔案,確認字體資訊已被記錄。
    2. 在 CSS 中引用字體:在您的 HTML 或 CSS 中,使用 @font-face 規則引用新安裝的字體,並在需要的地方設置 font-family
       @font-face {
           font-family: 'simsun';
           src: url('path/to/simsun.ttf') format('truetype');
       }
       body {
           font-family: 'simsun';
       }

    請確保 src 的路徑正確指向字體檔案的位置。

    1. 生成 PDF:在生成 PDF 的程式碼中,確保載入的 HTML 包含上述字體設定。以下是範例程式碼:
       use Dompdf\Dompdf;
       use Dompdf\Options;
    
       // 初始化 Dompdf
       $options = new Options();
       $options->set('isRemoteEnabled', true);
       $dompdf = new Dompdf($options);
    
       // 載入 HTML 內容
       $html = '<html><head><style>
       @font-face {
           font-family: "simsun";
           src: url("path/to/simsun.ttf") format("truetype");
       }
       body {
           font-family: "simsun";
       }
       </style></head><body>您的中文內容</body></html>';
       $dompdf->loadHtml($html);
    
       // 設定紙張大小和方向
       $dompdf->setPaper('A4', 'portrait');
    
       // 生成 PDF
       $dompdf->render();
    
       // 輸出 PDF
       $dompdf->stream('document.pdf');

    請確保 @font-face 中的 src 路徑在 HTML 中是可訪問的。

    按照上述步驟,您應該能夠在 Dompdf 中成功引入並使用中文字體,確保生成的 PDF 正確顯示中文內容。


    我在 Github 上弄了兩個頁面可以把他架設起來試試看,請點這裡

    同場加映,有時我們想找中文開源字型,請到這裡

    可以 filiter 你要的字型,點擊搜尋出來的字型後再點擊 「Get font」按鈕。

    在這裡可以從 Download all 中下載字型,也可以從 embed code 中找到字型的連結。

    舉例像是抓出來的這個:

    https://fonts.googleapis.com/css2?family=Noto+Sans+TC:[email protected]&family=Noto+Serif+TC:[email protected]&display=swap

    打開來會得到一串 @font-face 的 css 資料,除了可以引用以外,也可以從裡面複製處來使用。 以上!

  • Sublime Text3 檢查 Codeigniter4 專案的外掛

    Sublime Text3 檢查 Codeigniter4 專案的外掛

    使用 Sublime Text 檢查 CodeIgniter 4 專案的命名規範與 use 語句。

    在 CodeIgniter 4 開發中,為了提升專案的可維護性,遵守檔案命名規範與 use 語句的正確性是非常重要的。然而,手動檢查這些細節可能耗時且容易出錯。為了解決這個問題,我開發了一款 Sublime Text 外掛,專門用於自動檢查 CodeIgniter 4 專案的命名規範和 use 語句。

    在這篇文章中,我將介紹這個外掛的功能、使用方式、注意事項,以及如何對其進行客製化修改。


    外掛功能(截至 2024-12-13)

    這個外掛的主要功能是檢查 PHP 檔案是否符合 CodeIgniter 4 的命名規範和 use 語句的使用規則:

    1. 檔案命名規範檢查
      • 確保所有 PHP 檔案名稱遵循 PascalCase 並以 .php 結尾。
      • 舉例:MyClass.php 是正確的命名,my_class.php 則會觸發警告。
    2. use 語句檢查
      • 檢查是否所有使用到的類別名稱都已經通過 use 正確引入。
      • 找出未被使用的類別,協助開發者清理多餘的 use 語句。
    3. CodeIgniter 4 專案結構檢查
      • 自動檢測專案是否為 CodeIgniter 4,要求至少包含以下目錄:
        • 必須有:app/writable/
        • 至少存在:public/vendor/

    如何安裝與使用

    1. 下載外掛
    2. 安裝外掛
      • 將外掛放入 Sublime Text 的 Packages 目錄中: Sublime Text > Preferences > Browse Packages 將外掛資料夾解壓縮到上述目錄內。
    3. 保存 PHP 檔案以觸發檢查
      • 在 Sublime Text 中打開 CodeIgniter 4 專案的 PHP 檔案,進行編輯後保存。
      • 外掛會自動檢查檔案並在 Sublime Text 狀態列中顯示結果。
      • 如果發現錯誤,會彈出對話框提醒,顯示具體問題。

    使用注意事項(截至 2024-12-13)

    1. 適用專案
      • 此外掛僅適用於 CodeIgniter 4 專案,需保證專案根目錄存在 .env 檔案,並符合 CodeIgniter 4 的基本目錄結構。
    2. 支援的檔案類型
      • 僅檢查 .php 檔案,其他檔案類型將被忽略。
    3. 檢查邏輯
      • 外掛會移除 PHP 中的註解與引號內容來確保檢查不受干擾,但可能會忽略某些極端情況。例如,代碼中以字串拼接的動態類別名稱。

    如何進行修改

    如果你需要針對專案需求進一步客製化這個外掛,可以參考以下指引:

    1. 修改專案結構檢查邏輯

    • is_codeigniter4_project 方法中,你可以自訂需要檢查的目錄結構: def is_codeigniter4_project(self, directory): required_paths = ["app", "writable"] optional_paths = ["public", "vendor"] for path in required_paths: if not os.path.isdir(os.path.join(directory, path)): return False if not any(os.path.isdir(os.path.join(directory, path)) for path in optional_paths): return False return True

    2. 新增自訂檢查規則

    • 如果需要檢查額外的命名規範或程式碼規範,可以在 check_file_namingcheck_use_statements 方法中新增對應邏輯。

    3. 排除特定情境

    • 若某些專案使用特定的非標準命名方式,可以修改正則表達式來放寬檢查。例如: if not re.match(r"^[A-Z][a-zA-Z0-9_]*\.php$", base_name):

    4. 偵錯模式

    • 在開發或調試外掛時,可以透過 print 將日誌輸出到 Sublime Text 的 Console(`Ctrl+“)。

    結語

    這個 Sublime Text 外掛旨在提升 CodeIgniter 4 專案的開發效率,減少手動檢查的時間成本。如果你有其他需求或遇到問題,歡迎前往 GitHub 儲存庫 提交 Issue 或 Pull Request。

    希望這款工具能夠成為你在 CodeIgniter 4 開發中的得力助手!

    笑死,現在部落格用 AI 幫忙生成,講的比自己清楚。附上連結:

  • 解決 PHP8.2  mb_convert_encoding 方法已經棄用的問題。

    解決 PHP8.2 mb_convert_encoding 方法已經棄用的問題。

    PHP 中 mb_convert_encoding() 實作了將字串從一種字元編碼轉換為另一種字元編碼,但是在 PHP 8.2 時這個方法就棄用了,找到解決辦法並且測試一下。

    先講結論,應該使用這個方法

    function _($string)
    {
        return mb_encode_numericentity(
            htmlspecialchars_decode(
                htmlentities($string, ENT_NOQUOTES, 'UTF-8', false)
                , ENT_NOQUOTES
            ), [0x80, 0x10FFFF, 0, ~0],
            'UTF-8'
        );
    }

    參考這裡是正確答案:

    測試程式碼:

    <?php
    
    $string = '中文:!"$%&/()=ÖÄÜöäü<<accentué За матеріалами';
    
    
    echo mb_convert_encoding($string, 'HTML-ENTITIES', 'utf-8')."\n\n";
    // 方法 1
    echo mb_encode_numericentity(
        htmlspecialchars_decode(
            htmlentities($string, ENT_NOQUOTES, 'UTF-8', false)
            ,ENT_NOQUOTES
        ), [0x80, 0x10FFFF, 0, ~0],
        'UTF-8'
    );
    echo "\n\n";
    // 方法 2
    echo htmlspecialchars_decode(htmlentities($string));

    變化後方法 1 和原方法是一樣的。先自首,我其實看不太懂這意思,丟個 AI 解釋水字數一下:

    這段程式碼通過以下步驟將 UTF-8 字符串轉換為 HTML 實體:

    1. htmlentities():將特殊字符轉換為 HTML 實體,但不處理引號。
    2. htmlspecialchars_decode():將 HTML 實體轉回原始字符。
    3. mb_encode_numericentity():將範圍內的字符轉換為數值實體表示,這裡的範圍 [0x80, 0x10FFFF] 適用於大多數 Unicode 字符。

    此方法提供更精確的 HTML 實體處理,尤其適合多字節字符編碼。

    接下來就是接 DOMDocument 做 HTML 解析了,DOMDocument 還會有一個問題是 HTML5 不太支援,這算是用 PHP 做爬蟲的原罪吧。

  • Nginx 中同一個網域設定不同應用程式/目錄

    Nginx 中同一個網域設定不同應用程式/目錄

    技能點還沒有點太多的領域一點點成果就會獲得大大的成就感。

    最近弄了一個可以在同一個網域中實作多的應用程式的設定,這邊紀錄一下。先講結論:

    1. 可以實現同一個網域前後端分離,讓版本控制的操作也分開來。
    2. 同理多個不同應用如果需要互相呼叫時,也可以使用 sub-path 不要 sub-domain 來處理。
    3. 強烈建議不要用套裝的應用程式,關於網域使用的設計需要特別處理。
    4. 個人認為,如果是不同功能的應用場景,還是應該用 sub-domain 處理,這應該是 UX 相關的設計,和技術較為無關。
    5. 系統層的設定較為複雜,包含 Nginx 的設定,如果沒有 full controll 的權限可能實現上較為困難。

    前提列一下:

    1. 主網域是 a.com.tw
    2. 第二個網域是 b.com.tw 作為 nginx 的 proxy 使用,如果有需要也會在 dns 上設定。

    目標有幾個:

    1. 用戶訪問 a.com.tw 時候,會跳轉至 /root-document/a.com.tw/ 路徑上的 web-app
    2. 用戶訪問 a.com.tw/admin 時候,會跳轉至 /root-document/b.com.tw/ 路徑上的 web-app
    3. 用戶訪問 a.com.tw/uploads 時候,會跳轉至 /root-document/b.com.tw/uploads 路徑上的靜態檔案

    實作步驟:

    設定 /etc/hosts 檔案,新增:

    127.0.0.1 b.com.tw

    如果有需要,可以在 DNS 上設定 CNAME 把 b.com.tw 設定為 a.com.tw 別名。

    設定 a.com.tw 的網站,Nginx 中添加:

    # 將 /admin/ 和 /admin/* 路徑指向後端應用程式
    location ^~ /admin/ {
      proxy_pass http://b.com.tw;
    }
    # 將 /uploads/ 和 /uploads/* 路徑的靜態檔案指向其他資料夾
    location ^~ /uploads/ {
      alias /root-document/b.com.tw/uploads/;
      try_files $uri $uri/ /index.html;
    }
    
    
    # Option 其他網站的配置,例如前端 
    location ^~ / {
      root /root-document/a.com.tw;
      ...
    }

    設定 b.com.tw 的網站,Nginx 按照一般的設定即可,不過在動態程式中需要特別注意幾項:

    1. b.com.tw 的顯示網域是主網域 a.com.tw,在畫面上如果顯示非相對域名,要注意顯示的是 a.com.tw 而不是 b.com.tw
    2. 尤其後端上傳行為與 form action 會需要特別注意。
    3. http://b.com.tw 作為後端服務 proxy_pass,要注意 ssl 設定,包含顯示網域的 ssl 是否寫死?或是 proxy_set_header 判斷處理。
    4. uploads 的路徑在範例中也是導向 /root-document/b.com.tw/ 裡面的路徑,如果單純只是靜態檔案也可以自行設定。建議事先規劃整理好。
    5. 需要請注意根目錄設定 .user.ini 檔案中的設定,建議把不同路徑都補進去。

    還有一些小細節:

    1. Nginx 中 alias 的路徑需要是 / 結尾。 alias 和 root 不同的地方,參考這裡
    2. Nginx 路徑匹配優先權問題,請參考這裡

    關於 Nginx 設定的注意事項,附上問 ChatGPT 生成回覆:

    配置意圖

    1. /admin//admin/* 路徑指向後端應用程式
      • location ^~ /admin/ { ... } 這個配置塊的意圖是將所有匹配 /admin/ 開頭的路徑(包括 /admin//admin/*)的請求,轉發到後端應用程式,其地址為 http://b.com.tw
      • proxy_pass http://b.com.tw; 這個指令表示將符合條件的請求轉發給 http://b.com.tw 這個後端服務處理。
    2. /uploads//uploads/* 路徑的靜態檔案指向其他資料夾
      • location ^~ /uploads/ { ... } 這個配置塊的意圖是處理匹配 /uploads/ 開頭的路徑(包括 /uploads//uploads/*)的請求,並將這些請求指向伺服器上的一個具體的資料夾來提供靜態檔案。
      • alias /root-document/b.com.tw/uploads/; 這個指令表示將 /uploads/ 路徑的請求映射到伺服器上 /root-document/b.com.tw/uploads/ 目錄中的對應檔案。例如,請求 /uploads/image.jpg 會映射到 /root-document/b.com.tw/uploads/image.jpg
      • try_files $uri $uri/ /index.html; 這個指令會嘗試按順序查找以下檔案:
        • $uri:請求的原始檔案或目錄(例如 /root-document/b.com.tw/uploads/image.jpg)。
        • $uri/:如果是目錄,則查找目錄下的檔案(例如 /root-document/b.com.tw/uploads/image.jpg/)。
        • 如果以上都未找到,則回退到 /index.html 這個檔案。

    注意事項

    1. ^~ 前綴
      • ^~ 表示如果路徑匹配 /admin//uploads/,那麼該配置塊將優先於其他正則表達式匹配(例如 location ~location /)來處理請求。這確保了 /admin//uploads/ 的請求被這些特定的配置塊處理。
    2. Proxy Pass 的上下文
      • 當使用 proxy_pass 指令時,Nginx 將請求轉發給後端服務(如 http://b.com.tw)。確保 b.com.tw 這個服務能夠正確處理接收到的 /admin/ 路徑,並且能夠返回正確的響應。
    3. Alias 的使用
      • alias 指令用於將 URL 路徑映射到文件系統上的實際路徑,注意它不同於 root 指令。使用 alias 時需要確保映射的目錄結構和權限設置正確,以便 Nginx 能夠讀取和提供靜態檔案。
    4. 靜態檔案的優先級
      • /uploads/ 路徑的靜態檔案配置中,try_files 的指令順序很重要。你需要確保 /index.html 是你希望用來處理靜態資源請求失敗時的回退頁面。
    5. 安全性考量
      • 在提供靜態資源時,應確保 /root-document/b.com.tw/uploads/ 資料夾內的檔案不包含敏感信息,且檔案權限設置得當,防止未經授權的訪問。

    這段配置主要是為了處理特定路徑的請求,將動態內容轉發給後端應用程式,並通過靜態資源提供功能來處理文件的提供。

  • ionCube 加密/混淆 PHP for Codeigniter4 筆記

    ionCube 加密/混淆 PHP for Codeigniter4 筆記

    前陣子在搞 Android 的混淆,在第三方套件和映射的機制上吃了不少苦頭,最近嘗試把 codeigniter4 做加密,選擇使用 ionCube 套件。這套件並非免費的,pro 版本折合台幣大約 10k 左右,也是不便宜,而選擇他的優點有幾個:

    • 目前最新版尚未有破解、解密的服務。
    • 加密功能選項完整,可設定 ip 限制、網域限制、限制時間等以及外部金鑰檔案等等。參考
    • 可選作混淆功能(不過這樣不會很難 debug 嗎?)
    • 可設定需要加密的檔案,並非直接整個專案加密。
    • 可以設定非 PHP 檔案加密(未嘗試)
    • 可執行 hook 等多種設定,詳細參考官網

    估計他的版本週期是跟著 PHP 版本走的, 所以購買最佳實機應該是在 PHP 出了一個新版本之後再來購買會比較好。

    而他簡單實作的是無框架的 PHP 服務。我針對使用 Codeigniter 4 的框架做了加密動作,以下筆記幾個注意事項:

    1. 可加密的範圍是 app/ 內除了 Config/ 資料夾以外的檔案資料夾。參考圖一
    2. 設定專案時可以把整個專案設定在內,除了可加密範圍之外,其他選擇 copy 動作。
    3. skip 的動作可以是 .DS_Store、 .user.ini 等等或是其他不想要發布的檔案,可充當過濾檔案使用。
    4. 混淆的選項請不要混淆 class 和 method ,弄個不好會有名字對不起來的問題。參考。更建議連 function 也別混淆,呼叫操作很容易對不上的。
      update:解決方法可以參考這裡,不過尚未嘗試。
    5. 如果使用 git 發布的話,要記得考慮清楚原始未加密的程式碼是否也要上 git。
    6. 有部分版本無法加密,參考
    7. 伺服器要能夠安裝 ionCube loader 套件,這滿重要的,不裝起來會無法讀取,如果有設定金鑰的話也需要確認是否可以放置。

    以上。

    圖一。 可加密的檔案範圍。
    圖二。混淆功能選項。
    圖三。截至 2024-03-09 所支援的加密版本。
  • WordPress 上 Cookie 的 SameSite 問題

    WordPress 上 Cookie 的 SameSite 問題

    參考這篇問題,主要是描述在支付頁面 retun 支付成功之後,跳回網頁會因為 cookie samesite 的問題導致會員登出,主要解法如同文中所敘述,需要把記錄 user 的 cookie samesite 改成 none,文中也提供了 apache 作法,這邊記錄一下其他作法,基本上都是沒成功的:

    Nginx 於 server 的 Location 中填寫,可能是位置沒填寫正確,沒作用:

    proxy_cookie_path ~^/(.+)$ "/$1;Secure;SameSite='None';Partitioned";

    PHP 有兩種作法,一種是在程式中處理,參考這篇,在 session_start() 之前寫入:

    session_set_cookie_params(['samesite' => 'None']);

    不過他沒理我,在 php.ini 中替換:

    session.cookie_samesite = "None"

    注意 None 是字串,需要雙引號起來。 這個修改會對 session_cookie 起作用。

    不過我的目的是為了修改 wordpress 網站,wp 的 auth cookie 是透過程式寫入的,檢查後也沒反應。鬱悶。

    於是跑進去追了一下程式碼,發現他使用的 setcookie 是 php 7.2 以前的工法,可能是為了向下相容吧,但偏偏在 php7.2 寫入的方式和 php 7.3 不同(參考這裡),但因為 wp core 的寫法,導致無法 hack 從 define 設定的 cookiepath 中直接修改,鬱悶,總不能把網站環境再改回兩蔣統治時期,快樂的 php 7.1 or 7.2 ,這樣挺尷尬的,連 wp 升級都不行。

    後來挖到了這則討論串,看起來 wordpress 他們也正在煩惱。於是在討論串中挖到了一個在 github 上的外掛:

    這個外掛看起來沒啥問題,經過安裝啟用檢查過後,看起來是可行的, auth 相關的 cookie 已經正確顯示 samesite 為 none 了。

    這外掛設定的部分,如同 github 說明一樣,請在後台的「設定」選項中第一個「一般」,點進去以後會發現多一個 uthentication SameSite Cookie parameter 的設定,像是金流跳轉的問題,把他設定為 None 就可以嘍。

    另外得提一下如果 goolge 關鍵字 「wordpress SameSite」 會搜到一款還正在架上間諜外掛,同評論所講的,我也不明白作者為什麼要上架這款外掛,有點坑,而且如果 php 有設定之後,安裝啟用這款外掛還會造成 cookie 錯誤。 此時可以體現測試環境的重要性。

  • PHP 源碼掃描實作,使用 SonarQube

    PHP 源碼掃描實作,使用 SonarQube

    PHP 的原始碼弱點掃描實作,這邊使用 SonarQube 的社區版,這邊實作是參考這個連結,使用 MacOS 實現。

    首先先提供相關資源網站:

    SonerQube 官網和下載網址:

    Download | SonarQube
    Get the latest LTS and version of SonarQube the leading product for Code Quality and Security from the official download page.

    SonerQube 的套件 sonarscanner 安裝文件:

    JDK 官網下載:

    JDK 安裝教學:

    MAC中安裝JDK、Maven
    一、安裝JDK 1、開啟JDK官網,選擇對應版本,並選擇maxOS系統,此處以Java 8為例。 …

    下載 SonarQube 社區版解壓縮後,在 command line 中找到執行的指令,像是 MacOS 是在資料夾/bin/macosx-universal-64/ 裡面。進入資料夾中輸入:

    ./sonar.sh console

    即可在瀏覽器 http://localhost:9000/ 中開啟網頁。預設帳號密碼 admin/admin 登入後更換密碼。

    以下重點整理:

    1. 新增專案,選擇語言等操作完成後,會產生一組程式碼,該組程式碼是提供進入要掃描的資料夾根目錄中使用的。
    2. 在掃描之前,系統會提示需要安裝 SonarScanner ,請選擇 MacOS 版本,推薦加入 bash 中方便運作,參考這裡
    3. 需要先安裝 JDK ,無法在 root 環境使用。
    4. 掃描後看結果可能心情會不太好,不過系統提到的都滿有道理的,建議改一下。
  • PHP 使用 PHP Code 的格式印出陣列

    使用 php 有時需要把程式碼印出來,提供動態的程式範例或是安裝的初始設定用途,雖然說大部分的應用場景只會使用到一次,但是資料量大的時候也是滿麻煩的。

    這邊提供一個範例將 array 印出來並且依照 php 格式作為 php file 儲存起來:

    $data = array('a'=>'b');
    file_put_contents('test.php', '<?php $arr = ' . var_export($data, true) . ';');

    參考:

  • WordPress SSL 錯誤修復

    最近使用 wordpress 外掛,透過 curl 可能會發生錯誤,如果遇到這類型的錯誤:

    SSL certificate problem: certificate has expired
    SSL certificate problem: certificate has expired

    是因為許多網站都在使用全球證書,該證書已於 2021 年 9 月 30 日到期。參考:

    解決方法是使用新的 crt 檔案替代 wordpress 目錄:wp-includes/certificates 底下的 ca-bundle.crt 檔案。
    crt 檔案下載網址:https://github.com/WordPress/WordPress/blob/master/wp-includes/certificates/ca-bundle.crt

    而這個問題預估在 wordpress 5.9 版會修復。

    參考:

    SSL certificate problem: certificate has expired – Smart Slider Documentation
    In this article Check the error Error only displays during update Solution on localhost servers (with WampServer example) A lot of websites are using a global c

  • WordPress 之中藍新金流開啟後, Elementor 外掛有時會打不開的問題處理

    WordPress 之中藍新金流開啟後, Elementor 外掛有時會打不開的問題處理

    WordPress 有很多外掛,在藍新金流啟用時,同時有使用 Elementor 這個 page builder 外掛,在某些編輯視窗中,可能會造成 AJAX “/wp-json/elementor/v1/globals” 路徑中抓不到 wc_get_chosen_shipping_method_ids() 這個 function 的錯誤訊息。

    這邊查了一下資料,不只是藍新金流外掛會這樣,應該 woocommerce 外掛和 page builder 都有可能造成類似的錯誤。紀錄一下避免老了忘記。

    最近又遇到一樣的問題,查看 log 發現是 wc_get_chosen_shipping_method_ids() 裡面的問題:

    2024/01/02 19:30:32 [error] 1870242#1870242: *46728 FastCGI sent in stderr: "PHP message: PHP Fatal error:  Uncaught Error: Call to a member function get() on null in /xxx/xxx/wp-content/plugins/woocommerce/includes/wc-cart-functions.php:394
    Stack trace:
    #0 /xxx/xxx/wp-content/plugins/newebpay/class-newebpay.php(1673): wc_get_chosen_shipping_method_ids()
    
    ...

    查看 woocommerce 的程式碼(參考),並且參考有類似問題的 stackoverflow

    function wc_get_chosen_shipping_method_ids() {
    	$method_ids     = array();
    	$chosen_methods = WC()->session->get( 'chosen_shipping_methods', array() );
    	foreach ( $chosen_methods as $chosen_method ) {
    		$chosen_method = explode( ':', $chosen_method );
    		$method_ids[]  = current( $chosen_method );
    	}
    	return $method_ids;
    }

    判斷應該是 WC()->session 取不到 get 參數,實際上就是這樣沒錯, woocommerce 當下這個沒有判斷 session 為空的狀況。
    可以參考原文,於 class-newebpay.php:1676 附近, wc_get_chosen_shipping_method_ids() 呼叫之前判斷 WC()->session 是否為空。這邊於底下原文中添加新的 code 。

    附上錯誤訊息:

    Fatal error: Uncaught Error: Call to undefined function wc_get_chosen_shipping_method_ids() in /xxx/wp-content/plugins/newebpay/class-newebpay.php:1676 Stack trace: #0 /xxx/wp-includes/class-wp-hook.php(303): newebpay_alter_payment_gateways(Array) ...

    附上發現錯誤的版本資訊,wordpress 核心版本為 5.8.1 , Elementor 版本為 3.4.3:

    /**
     * newebpay Payment Gateway
     * Plugin URI: http://www.newebpay.com/
     * Description: 藍新金流收款/物流 模組
     * Version: 1.0.3
     * Author URI: http://www.newebpay.com/
     * Author: 藍新金流 newebpay
     * Plugin Name:   藍新金流
     * @class       newebpay
     * @extends     WC_Payment_Gateway
     * @version
     * @author  Pya2go Libby
     * @author  Pya2go Chael
     * @author  Spgateway Geoff
     * @author  Spgateway_Pay2go Q //20170217 1.0.1
     * @author  Spgateway_Pay2go jack //20170622 1.0.2
     * @author  Spgateway_Pay2go Stally //20180420 1.0.3 20181018 1.0.4 20181222 newebpay 1.0.0 20190417 1.0.1 20190711 1.0.2 20200326 1.0.3
     */

    目前的解決方法是在 class-newebpay.php:1676 的 newebpay_alter_payment_gateways() 內添加判斷,檢查是否有方法,沒方法就直接跳回不動作。
    另外也在 plugin meta 上修改版本號和描述,避免被直接更新。不過這個外掛也沒上版控,所以屆時可能需要也是手動更新嘍。

    附上修改後資訊:

    <?php
    /**
     * newebpay Payment Gateway
     * Plugin URI: http://www.newebpay.com/
     * Description: 藍新金流收款/物流 模組 修復了 elementor 開啟會有 wc_get_chosen_shipping_method_ids 錯誤的問題 
     * Version: 99.99.99
     * Author URI: http://www.newebpay.com/
     * Author: 藍新金流 newebpay 修改過 By Jerry
     * Plugin Name:   藍新金流
     * @class       newebpay
     * @extends     WC_Payment_Gateway
     * @version
     * @author  Pya2go Libby
     * @author  Pya2go Chael
     * @author  Spgateway Geoff
     * @author  Spgateway_Pay2go Q //20170217 1.0.1
     * @author  Spgateway_Pay2go jack //20170622 1.0.2
     * @author  Spgateway_Pay2go Stally //20180420 1.0.3 20181018 1.0.4 20181222 newebpay 1.0.0 20190417 1.0.1 20190711 1.0.2 20200326 1.0.3
     */
    add_action('plugins_loaded', 'newebpay_gateway_init', 0);
    
    function newebpay_gateway_init() {
        if (!class_exists('WC_Payment_Gateway')) {
            return;
        }
    
        class WC_newebpay extends WC_Payment_Gateway {
    
     ...
    
        // 選擇藍新金流超商取貨後 payment只輸出藍新金流
        function newebpay_alter_payment_gateways($list) {
            if(isset($_GET['pay_for_order']) && isset($_GET['key'])) {
                $order_id = wc_get_order_id_by_order_key($_GET['key']);
                $order = wc_get_order($order_id);
                if($order->has_shipping_method('newebpay_cvscom')) {
                    $list = array('WC_newebpay');
                }
            } elseif(!is_admin()) { //後台無wc_get_chosen_shipping_method_ids function
                if ( !function_exists( 'wc_get_chosen_shipping_method_ids' ) ) { 
                    return $list;
                } 
    
                // 2024-01-02 更新判斷
                $session = WC()->session;
                if($session){
                    $chosen_shipping = wc_get_chosen_shipping_method_ids();
                    //判斷購物車內商品是否全為虛擬商品 全為虛擬商品時會無法選擇物流方式 導致session的chosen_shipping會維持上次所選
                    $virtual_count = 0;
                    $cart_items = WC()->cart->get_cart();
                    foreach ($cart_items as $key => $cart_item) {
                        $virtual_count += ($cart_item['data']->is_virtual()) ? 1 : 0;
                    }
                    if (@in_array('newebpay_cvscom', $chosen_shipping) && $virtual_count < count($cart_items)) {
                        $list = array('WC_newebpay');
                    }
                } // end 判斷
            }
    
            return $list;
        }
        add_filter('woocommerce_payment_gateways', 'newebpay_alter_payment_gateways', 100);
    
    ...