wordpress 投稿内の特定のキーワードに指定のリンクを自動で貼る-ACF対応改修ver 作成日: 2025年8月11日
MTのkotonohaリンクplに似せたfunction.php記述コードで作ってきたが・・・
管理サイトに実装するうち「あれもこれも」となって追記などしてきたが、改めて直してみた。
修正点
- the_contentは動的、acfのフィールドのものは静的=コード記述した時点でDB内の文字列にリンクが貼られるものを、動的に統一した。
ここちょっと疎かにしてて確認してなかったのだが、ページが重くなるリスクはあるが全て動的にしてもとの文章・テキストはRAW状態にした。 - 除外処理の優先度確認。オプション的な機能なので、システム上のエラーで自動リンクが為されなくてもOKだが、pのインラインのbやspanなどまた、除外ワードの厳格化を施した。
- function.php記述のための可読性とか。設定項目を上位にして、設定しやすくね。設定入力inputとかを備えた設定ページ付ければプラグインにもなるけど、エンドユーザーによる利用は考えてない(ここらへんで作ってる機能はみんなそう!)ので、管理者レベルで使いやすく程度かな。
で、コードは以下の通り
/* MTのkotonohalink的-wiki的なコンテンツ制作用
重くなること注意。キーワードで重複的になるもの、「日経平均」と「日経」などを指定する場合は重複部分を内包する「日経平均」を処理順上にする */
// Type1内部・外部ページへのリンクと除外ページ指定
$my_autolink_settings = [
// 内部リンクのキーワードとページID
'internal' => [
'ことのは' => 38,/////////////////////////////////////////////////-*-初期設定/確認-*-
],
// 外部リンクのキーワードとURL
'external' => [
'除外' => 'https://リンク先1',/////////////////////////////////////////////////-*-初期設定/確認-*-
'リンク先2' => 'https://リンク先2',
],
// 除外キーワード(これらを含む単語はリンク化しない)
'excluded' => [
'ことのはリンク',/////////////////////////////////////////////////-*-初期設定/確認-*-
'除外2',
],
// 処理をスキップするページID(リンク先になるページなど)
'excluded_page_ids' => [9999, 9998],/////////////////////////////////////////////////-*-初期設定/確認-*-
// ACFの対象フィールド名
'acf_target_fields' => ['access', 'field'],/////////////////////////////////////////////////-*-初期設定/確認-*-!!!ACFフィールドはその出力でpかliにラップされている必要があるのでtextやtextareaではなくリッチエディターにしておく
];
function my_autolink_keywords_process($content) {
global $my_autolink_settings;
$internal_keywords_and_ids = $my_autolink_settings['internal'];
$external_keywords_and_urls = $my_autolink_settings['external'];
$excluded_keywords = $my_autolink_settings['excluded'];
// pタグとliタグの中身だけを処理する
$content = preg_replace_callback('/<(p|li)([^>]*)>(.*?)<\/\1>/is', function($matches) use ($internal_keywords_and_ids, $external_keywords_and_urls, $excluded_keywords) {
$tag_name = $matches[1];
$tag_attributes = $matches[2];
$inner_content = $matches[3];
$placeholders = [];
$placeholder_count = 0;
// <a>タグとインラインタグ(b, strong, span, em, i)を一時的にプレースホルダーに置き換える
//これにより、これらのタグ内のキーワードがリンク化されるのを防ぎます。
$inner_content = preg_replace_callback('/(<a\b[^>]*>.*?<\/a>)|(<(b|strong|span|em|i)[^>]*>.*?<\/\3>)/is', function($matches) use (&$placeholders, &$placeholder_count) {
$placeholder = '__PLACEHOLDER_' . $placeholder_count++ . '__';
$placeholders[$placeholder] = $matches[0];
return $placeholder;
}, $inner_content);
// 除外キーワードをプレースホルダーに置き換える
// 除外キーワードを優先して処理するため、長いキーワードからソート
$excluded_keywords_sorted = $excluded_keywords;
usort($excluded_keywords_sorted, function($a, $b) {
return mb_strlen($b) - mb_strlen($a);
});
foreach ($excluded_keywords_sorted as $keyword) {
$keyword_escaped = preg_quote($keyword, '/');
$inner_content = preg_replace_callback('/(' . $keyword_escaped . ')/u', function($matches) use (&$placeholders, &$placeholder_count) {
$placeholder = '__PLACEHOLDER_' . $placeholder_count++ . '__';
$placeholders[$placeholder] = $matches[0];
return $placeholder;
}, $inner_content, 1);
}
// 内部・外部リンクのキーワードをプレースホルダーに置き換える(リンクを生成するため)
$link_keywords_to_process = array_merge($internal_keywords_and_ids, $external_keywords_and_urls);
uksort($link_keywords_to_process, function($a, $b) {
return mb_strlen($b) - mb_strlen($a);
});
foreach ($link_keywords_to_process as $keyword => $data) {
$keyword_escaped = preg_quote($keyword, '/');
$replacement = '';
if (isset($internal_keywords_and_ids[$keyword])) {
$url = get_permalink($internal_keywords_and_ids[$keyword]);
$replacement = '<a href="' . esc_url($url) . '" class="totri">' . $keyword . '</a>';
} elseif (isset($external_keywords_and_urls[$keyword])) {
$url = $external_keywords_and_urls[$keyword];
$replacement = '<a href="' . esc_url($url) . '" class="totri" target="_blank">' . $keyword . '</a>';
}
if ($replacement) {
$inner_content = preg_replace('/(' . $keyword_escaped . ')(?![^<]*>)/u', $replacement, $inner_content, 1);
}
}
// 最後に、すべてのプレースホルダーを元の文字列に戻す
$inner_content = str_replace(array_keys($placeholders), array_values($placeholders), $inner_content);
return '<' . $tag_name . $tag_attributes . '>' . $inner_content . '</' . $tag_name . '>';
}, $content);
return $content;
}
//the_contentフィルターに自動リンク機能を適用
function my_autolink_keywords_in_paragraphs($content) {
global $my_autolink_settings;
$excluded_page_ids = $my_autolink_settings['excluded_page_ids'];
$current_page_id = get_the_ID();
if (in_array($current_page_id, $excluded_page_ids)) {
return $content;
}
return my_autolink_keywords_process($content);
}
add_filter('the_content', 'my_autolink_keywords_in_paragraphs', 10);
//ACFフィールドに自動リンク機能を適用
function my_autolink_keywords_acf_filter($value, $post_id, $field) {
global $my_autolink_settings;
$excluded_page_ids = $my_autolink_settings['excluded_page_ids'];
$current_page_id = get_the_ID();
$target_fields = $my_autolink_settings['acf_target_fields'];
if (in_array($current_page_id, $excluded_page_ids) || !in_array($field['name'], $target_fields) || !is_string($value)) {
return $value;
}
return my_autolink_keywords_process($value);
}
add_filter('acf/format_value', 'my_autolink_keywords_acf_filter', 10, 3);