前言
在 WordPress 後台管理自訂文章類型(此處為 solution)時,若想快速搜尋文章內容中是否包含特定關鍵字,系統預設的搜尋功能可能無法精準或方便達成此需求。這段程式碼示範如何為 solution 文章類型新增一個後台子選單,提供內容關鍵字搜尋功能,適合需要管理大量文章並快速定位內容的開發者與網站管理員。
新增後台子選單
使用 admin_menu action,先檢查 solution 文章類型是否存在,避免錯誤。接著透過 add_submenu_page 在 solution 列表底下新增「內容關鍵字搜尋」子選單,設定權限為 edit_posts,確保只有具備編輯權限的使用者能操作。
add_action( 'admin_menu', function () {
if ( ! post_type_exists( 'solution' ) ) {
return;
}
add_submenu_page(
'edit.php?post_type=solution',
'內容關鍵字搜尋',
'內容關鍵字搜尋',
'edit_posts',
'solution-content-search',
'solution_content_search_page_render'
);
} );
搜尋頁面渲染與表單處理
solution_content_search_page_render 函式負責渲染搜尋表單與結果頁面。首先檢查使用者權限,避免未授權存取。接著判斷是否有表單送出,並使用 WordPress 的 nonce 機制驗證安全性。
關鍵字透過 sanitize_text_field 與 wp_unslash 清理,避免 XSS 與注入風險。
if ( isset( $_POST['solution_content_search_submit'] ) ) {
check_admin_referer( 'solution_content_search_action', 'solution_content_search_nonce' );
$keyword = isset( $_POST['solution_keyword'] ) ? sanitize_text_field( wp_unslash( $_POST['solution_keyword'] ) ) : '';
$searched = true;
if ( $keyword !== '' ) {
// ...
}
}
透過 WP_Query 及字串比對精確搜尋
WordPress 內建搜尋(s 參數)會搜尋標題、內容等欄位,但可能不夠精準。此處先用 WP_Query 以關鍵字搜尋取得可能相關文章 ID,然後逐篇讀取內容,使用 mb_stripos(若有)或 stripos 做不區分大小寫的字串比對,確保內容確實包含關鍵字。
$query = new WP_Query( array(
'post_type' => 'solution',
'post_status' => 'any',
'posts_per_page' => -1,
's' => $keyword,
'fields' => 'ids',
) );
foreach ( $query->posts as $post_id ) {
$post = get_post( $post_id );
if ( ! $post ) {
continue;
}
$content = wp_strip_all_tags( $post->post_content );
if ( function_exists( 'mb_stripos' ) ) {
$pos = mb_stripos( $content, $keyword );
} else {
$pos = stripos( $content, $keyword );
}
if ( $pos !== false ) {
$results[] = $post_id;
}
}
wp_reset_postdata();
搜尋結果呈現與操作介面
搜尋結果會以表格形式呈現,包含文章 ID、標題(連結至編輯頁面)、前台連結與編輯按鈕,方便快速查看與編輯。若無輸入關鍵字或找不到結果,會顯示相應提示訊息。
表單使用 WordPress 內建的 wp_nonce_field 與 submit_button,確保安全與一致性。
實務應用與優化方向
此搜尋功能適合用於自訂文章類型管理,尤其是內容量大且需要精準定位文本的場景。未來可考慮加入分頁功能避免一次載入過多文章,或結合全文索引外掛提升搜尋效能。也可擴充搜尋範圍至自訂欄位或分類。
完整程式碼
<?php
// 在「solution」這個 post type 底下新增一個後台子選單:內容關鍵字搜尋
add_action( 'admin_menu', function () {
// 確保有這個 post type
if ( ! post_type_exists( 'solution' ) ) {
return;
}
add_submenu_page(
'edit.php?post_type=solution', // 父層 (solution 列表底下)
'內容關鍵字搜尋', // 頁面標題
'內容關鍵字搜尋', // 左側選單名稱
'edit_posts', // 權限
'solution-content-search', // slug
'solution_content_search_page_render' // callback
);
} );
// 後台頁面:渲染搜尋表單與結果
function solution_content_search_page_render() {
if ( ! current_user_can( 'edit_posts' ) ) {
wp_die( '沒有權限檢視此頁面。' );
}
$keyword = '';
$results = array();
$searched = false;
// 處理表單送出
if ( isset( $_POST['solution_content_search_submit'] ) ) {
check_admin_referer( 'solution_content_search_action', 'solution_content_search_nonce' );
$keyword = isset( $_POST['solution_keyword'] ) ? sanitize_text_field( wp_unslash( $_POST['solution_keyword'] ) ) : '';
$searched = true;
if ( $keyword !== '' ) {
// 先用 WP_Query 找出可能包含關鍵字的文章 (全文搜尋)
$query = new WP_Query( array(
'post_type' => 'solution',
'post_status' => 'any',
'posts_per_page' => -1,
's' => $keyword,
'fields' => 'ids',
) );
if ( $query->have_posts() ) {
foreach ( $query->posts as $post_id ) {
$post = get_post( $post_id );
if ( ! $post ) {
continue;
}
// 從內容中做字串檢查(確保 content 真的有包含關鍵字)
$content = wp_strip_all_tags( $post->post_content );
if ( function_exists( 'mb_stripos' ) ) {
$pos = mb_stripos( $content, $keyword );
} else {
$pos = stripos( $content, $keyword );
}
if ( $pos !== false ) {
$results[] = $post_id;
}
}
}
wp_reset_postdata();
}
}
?>
<div class="wrap">
<h1>Solution 內容關鍵字搜尋</h1>
<p>輸入一個關鍵字,系統會掃描 <code>solution</code>
文章類型的內容(content),只要有包含該字串,就列出標題與編輯連結。</p>
<form method="post" style="margin-top: 1em; margin-bottom: 2em;">
<?php wp_nonce_field( 'solution_content_search_action', 'solution_content_search_nonce' ); ?>
<table class="form-table" role="presentation">
<tr>
<th scope="row">
<label for="solution_keyword">關鍵字字串</label>
</th>
<td>
<input
type="text"
id="solution_keyword"
name="solution_keyword"
class="regular-text"
value="<?php echo esc_attr( $keyword ); ?>"
placeholder="例如:API、某段程式碼、特定中文句子"
/>
</td>
</tr>
</table>
<?php submit_button( '開始搜尋', 'primary', 'solution_content_search_submit' ); ?>
</form>
<?php if ( $searched ) : ?>
<h2>搜尋結果</h2>
<?php if ( $keyword === '' ) : ?>
<div class="notice notice-warning"><p>請輸入關鍵字。</p></div>
<?php elseif ( empty( $results ) ) : ?>
<div class="notice notice-info"><p>沒有找到內容包含「<?php echo esc_html( $keyword ); ?>」的 solution 文章。</p></div>
<?php else : ?>
<p>找到 <?php echo esc_html( count( $results ) ); ?> 篇文章,內容中包含「<?php echo esc_html( $keyword ); ?>」。</p>
<table class="widefat fixed striped">
<thead>
<tr>
<th scope="col" style="width: 50px;">ID</th>
<th scope="col">標題</th>
<th scope="col" style="width: 200px;">前台連結</th>
<th scope="col" style="width: 150px;">編輯</th>
</tr>
</thead>
<tbody>
<?php foreach ( $results as $post_id ) :
$title = get_the_title( $post_id );
$view_link = get_permalink( $post_id );
$edit_link = get_edit_post_link( $post_id, '' );
?>
<tr>
<td><?php echo esc_html( $post_id ); ?></td>
<td>
<?php if ( $edit_link ) : ?>
<a href="<?php echo esc_url( $edit_link ); ?>">
<?php echo esc_html( $title ); ?>
</a>
<?php else : ?>
<?php echo esc_html( $title ); ?>
<?php endif; ?>
</td>
<td>
<?php if ( $view_link ) : ?>
<a href="<?php echo esc_url( $view_link ); ?>" target="_blank" rel="noopener noreferrer">
檢視
</a>
<?php else : ?>
-
<?php endif; ?>
</td>
<td>
<?php if ( $edit_link ) : ?>
<a href="<?php echo esc_url( $edit_link ); ?>" class="button button-small">
編輯文章
</a>
<?php else : ?>
-
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<?php endif; ?>
</div>
<?php
}