前言
在管理 WordPress 自訂文章類型(Custom Post Type)時,常會遇到需要批次替換文章內容或摘要中的特定字串的需求。手動逐篇修改不僅耗時,也容易出錯。這段程式碼示範如何在 WordPress 後台為名為 solution 的自訂文章類型新增一個子選單,提供一個簡單介面讓管理員輸入舊字串與新字串,並自動搜尋所有 solution 文章的內容與摘要進行批次替換。
這篇文章適合有基本 WordPress 開發經驗,想要了解如何擴充後台功能並操作自訂文章內容的工程師與自學者。
新增後台子選單
使用 add_action('admin_menu', ...) 來掛載函式,先判斷 solution 文章類型是否存在,避免錯誤。接著用 add_submenu_page 在 solution 文章列表下新增「內容關鍵字取代」的子選單,權限設定為可編輯文章的用戶。
add_action('admin_menu', function () {
if (!post_type_exists('solution')) {
return;
}
add_submenu_page(
'edit.php?post_type=solution',
'內容關鍵字取代',
'內容關鍵字取代',
'edit_posts',
'solution-content-replace',
'solution_content_replace_page'
);
});
這段程式碼確保只有在 solution 文章類型存在時才新增選單,避免後台出現無效連結。
後台頁面與表單設計
solution_content_replace_page 函式負責渲染後台頁面。首先檢查使用者權限,防止未授權存取。頁面包含一個表單,讓使用者輸入「舊字串」與「新字串」,並使用 WordPress 的 nonce 機制防止 CSRF 攻擊。
if (!current_user_can('edit_posts')) {
wp_die('沒有權限。');
}
// 表單中使用 wp_nonce_field 產生安全碼
表單送出後會執行字串替換邏輯。
批次搜尋與替換邏輯說明
當表單送出且 nonce 驗證通過後,程式會取得輸入的舊字串與新字串,並用 WP_Query 撈出所有 solution 文章的 ID。
$query = new WP_Query([
'post_type' => 'solution',
'post_status' => 'any',
'posts_per_page' => -1,
'fields' => 'ids',
]);
接著逐篇文章讀取內容(post_content)與摘要(post_excerpt),利用 mb_strpos(若可用)或 strpos 檢查舊字串是否存在。若存在,則用 str_replace 替換。
if ($content_pos !== false) {
$updated_content = str_replace($old, $new, $content);
if ($updated_content !== $content) {
$update_args['post_content'] = $updated_content;
$status_parts[] = '內容';
}
}
替換完成後,只有在內容或摘要確實有變動時才呼叫 wp_update_post 進行更新,避免不必要的資料庫寫入。
結果呈現與使用者體驗
執行完替換後,頁面會顯示替換結果列表,包括文章 ID、標題、替換狀態(內容、摘要或兩者)以及快速編輯連結,方便管理者後續檢查與微調。
若未輸入舊字串,會顯示警告;若找不到符合條件的文章,則提示資訊,提升使用者體驗。
實務應用與優化建議
此工具適合用於需要大量文字修正的場景,例如品牌名稱變更、產品名稱更新或錯字修正。未來可擴充功能,如:
- 支援正規表達式替換
- 限制替換範圍(例如只替換內容或摘要)
- 增加替換前預覽功能
- 加入替換記錄與回滾機制
此外,批次更新大量文章時,建議搭配分批處理避免伺服器超時。
常見問題與注意事項
- 請確認使用者權限設定正確,避免無權限使用此工具。
- 替換字串為空白時,會將舊字串刪除,請謹慎操作。
- 使用
mb_strpos可避免多字節字串判斷錯誤,若環境不支援會退回strpos。
完整程式碼
<?php
// 在 solution post type 底下新增子選單:內容關鍵字取代
add_action('admin_menu', function () {
if (!post_type_exists('solution')) {
return;
}
add_submenu_page(
'edit.php?post_type=solution',
'內容關鍵字取代',
'內容關鍵字取代',
'edit_posts',
'solution-content-replace',
'solution_content_replace_page'
);
});
// 後台頁面
function solution_content_replace_page() {
if (!current_user_can('edit_posts')) {
wp_die('沒有權限。');
}
$old = '';
$new = '';
$results = [];
$executed = false;
if (isset($_POST['solution_replace_submit'])) {
check_admin_referer('solution_replace_action', 'solution_replace_nonce');
$old = isset($_POST['old_keyword']) ? sanitize_text_field($_POST['old_keyword']) : '';
$new = isset($_POST['new_keyword']) ? sanitize_text_field($_POST['new_keyword']) : '';
$executed = true;
if ($old !== '') {
$query = new WP_Query([
'post_type' => 'solution',
'post_status' => 'any',
'posts_per_page' => -1,
'fields' => 'ids',
]);
foreach ($query->posts as $post_id) {
$post = get_post($post_id);
if (!$post) {
continue;
}
$content = $post->post_content;
$excerpt = $post->post_excerpt;
// 檢查舊字串是否存在(內容+摘要)
if (function_exists('mb_strpos')) {
$content_pos = ($content !== '') ? mb_strpos($content, $old) : false;
$excerpt_pos = ($excerpt !== '') ? mb_strpos($excerpt, $old) : false;
} else {
$content_pos = ($content !== '') ? strpos($content, $old) : false;
$excerpt_pos = ($excerpt !== '') ? strpos($excerpt, $old) : false;
}
if ($content_pos !== false || $excerpt_pos !== false) {
$update_args = [
'ID' => $post_id,
];
$status_parts = [];
// 內容替換
if ($content_pos !== false) {
$updated_content = str_replace($old, $new, $content);
if ($updated_content !== $content) {
$update_args['post_content'] = $updated_content;
$status_parts[] = '內容';
}
}
// 摘要替換
if ($excerpt_pos !== false) {
$updated_excerpt = str_replace($old, $new, $excerpt);
if ($updated_excerpt !== $excerpt) {
$update_args['post_excerpt'] = $updated_excerpt;
$status_parts[] = '摘要';
}
}
// 真的有需要更新才存
if (!empty($status_parts)) {
wp_update_post($update_args);
$results[] = [
'id' => $post_id,
'title' => $post->post_title,
'status' => '已替換:' . implode('、', $status_parts),
];
}
}
}
wp_reset_postdata();
}
}
?>
<div class="wrap">
<h1>Solution – 內容關鍵字取代</h1>
<p>此工具會掃描所有 <code>solution 文章的內容與摘要,將符合的字串批次替換。
| <input type="text" id="old_keyword" name="old_keyword" class="regular-text" value="" placeholder="例如:舊字串"> | |
| <input type="text" id="new_keyword" name="new_keyword" class="regular-text" value="" placeholder="例如:新字串(可空白)"> |
取代結果
請輸入要搜尋的舊字串。
沒有任何文章的內容或摘要包含「」。
| ID | 標題 | 狀態 | 編輯連結 |
|---|---|---|---|
| <a href="" class="button button-small">編輯 |