前言
在 WordPress 開發中,有時候需要自動從文章內容擷取一部分作為預覽,方便在前台或後台顯示摘要。這段程式碼示範如何利用 PHP 的 DOMDocument 解析文章 HTML,並將第一個區塊內容存入自訂欄位,適合希望自動管理文章預覽的開發者或自學者。
主要功能說明
這段程式碼的核心目標是:
- 避免在自動儲存或文章修訂版本時重複執行
- 只處理文章(post)類型
- 取得文章內容並套用 WordPress 內建的內容過濾器(the_content)
- 使用 DOMDocument 將 HTML 內容解析為節點樹
- 擷取第一個 HTML 區塊(元素節點)作為預覽內容
- 將擷取結果寫入 Advanced Custom Fields (ACF) 的自訂欄位
這樣的設計能確保預覽內容是有效的 HTML 片段,且不會因為純文字截斷而破壞標籤結構。
程式碼解析
避免不必要的觸發
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if (wp_is_post_revision($post_id)) return;
這兩個判斷用來避免在 WordPress 自動儲存或文章修訂版本時執行預覽生成,減少不必要的資源消耗。
文章類型與內容驗證
$post = get_post($post_id);
if ($post->post_type !== 'post') return;
$content = $post->post_content;
if (empty($content)) return;
確保只處理標準文章(post),且內容不為空。
套用內容過濾器
$filtered_content = apply_filters('the_content', $content);
這步驟會讓內容經過 WordPress 內建的過濾器處理,例如短碼解析、自動換行等,確保後續解析的 HTML 是完整且符合前台顯示的狀態。
使用 DOMDocument 解析 HTML
libxml_use_internal_errors(true);
$doc = new DOMDocument();
$doc->loadHTML('<?xml encoding="utf-8" ?>' . $filtered_content);
libxml_clear_errors();
因為 HTML 可能不完全符合 XML 規範,先抑制 libxml 錯誤,並在字串前加上 XML 編碼宣告,確保 UTF-8 正確處理。
擷取第一個區塊元素
$body = $doc->getElementsByTagName('body')->item(0);
$output_blocks = [];
$count = 0;
$max_blocks = 1;
foreach ($body->childNodes as $node) {
if ($node->nodeType === XML_ELEMENT_NODE) {
$output_blocks[] = $doc->saveHTML($node);
$count++;
if ($count >= $max_blocks) break;
}
}
$preview_html = implode("\n", $output_blocks);
這段程式碼從 body 標籤下取得第一個元素節點(例如第一個段落或區塊標籤),並將其轉成 HTML 字串。這樣做的好處是避免截取不完整的 HTML,保持標籤結構完整。
寫入自訂欄位
update_field('ks_post_preview', $preview_html, $post_id);
利用 ACF 提供的 update_field 函式,將預覽內容寫入名為 ks_post_preview 的自訂欄位,方便後續在前台或後台調用。
實務應用與優化建議
- 可依需求調整 $max_blocks 參數,擷取多個區塊作為預覽
- 若文章內容包含複雜結構,DOMDocument 解析可確保 HTML 不會因截斷而破損
- 可結合 WordPress 的鉤子(如 save_post)自動更新預覽,減少手動維護成本
- 注意 ACF 欄位名稱需與後台設定一致,否則無法成功更新
常見問題與注意事項
- DOMDocument 解析 HTML 時可能因不標準標籤產生警告,使用 libxml_use_internal_errors 可避免影響
- 若文章內容過短或無有效區塊,預覽欄位可能為空,需在前端做好容錯處理
- 自動儲存與修訂版本判斷是避免重複觸發的關鍵,缺少可能導致效能問題
完整程式碼
<?php
function ks_generate_post_preview($post_id) {
// 避免自動儲存時觸發
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if (wp_is_post_revision($post_id)) return;
$post = get_post($post_id);
if ($post->post_type !== 'post') return;
$content = $post->post_content;
if (empty($content)) return;
// 套用 the_content 濾鏡
$filtered_content = apply_filters('the_content', $content);
// 使用 DOMDocument 分析 HTML
libxml_use_internal_errors(true);
$doc = new DOMDocument();
$doc->loadHTML('<?xml encoding="utf-8" ?>' . $filtered_content);
libxml_clear_errors();
$body = $doc->getElementsByTagName('body')->item(0);
$output_blocks = [];
$count = 0;
$max_blocks = 1;
foreach ($body->childNodes as $node) {
if ($node->nodeType === XML_ELEMENT_NODE) {
$output_blocks[] = $doc->saveHTML($node);
$count++;
if ($count >= $max_blocks) break;
}
}
$preview_html = implode("\n", $output_blocks);
// 寫入 ACF 欄位
update_field('ks_post_preview', $preview_html, $post_id);
}
add_action('save_post', 'ks_generate_post_preview');