WordPressで目次付きショートコードを作成する方法

## はじめに

エンジニアアルバイトをしている大学生です。先日、アルバイト業務中にWordPressで使用するショートコードを作成する機会がありましたのでその際のことを振り返るためにこの記事を投稿します。

この記事では、WordPress のショートコードを使用してコンテンツに動的な目次を追加する方法を説明します。これは、H2とH3の見出しを自動的に識別し、それぞれに一意のアンカーIDを付け、それに基づいて目次を作成します。(記事内で紹介するコードは実際に作成したコードよりも構造が簡単なものに変更しています)

## コード全体の紹介

まずはじめに、WordPressの `functions.php` ファイルに以下のコードを追加します。

```diff_php
function add_anchor_to_headings_shortcode($atts, $content = null) {
    $dom = new DOMDocument();
    @$dom->loadHTML(mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8'));

    $h2_counter = 0;
    $h3_counter = 0;
    $links = [];
    $nodes = $dom->getElementsByTagName('*');

    foreach($nodes as $node) {
        if($node->nodeName == 'h2') {
            $h2_counter++;
            $h3_counter = 0; // Reset h3 counter for each new h2
            $id = 'anchor' . sprintf("%02d", $h2_counter);
            $node->setAttribute('id', $id);
            $links[] = ['id' => $id, 'text' => $node->nodeValue, 'sub' => []];
        } elseif($node->nodeName == 'h3' && $h2_counter > 0) { // Only assign ids to h3 tags if they are under a h2 tag
            $h3_counter++;
            $id = 'anchor' . sprintf("%02d", $h2_counter) . '-' . sprintf("%02d", $h3_counter);
            $node->setAttribute('id', $id);
            $links[end(array_keys($links))]['sub'][] = ['id' => $id, 'text' => $node->nodeValue];
        }
    }

    $table_of_contents = '<ol class="mokuji_list01">';
    foreach ($links as $link) {
        $table_of_contents .= '<li><a href="#' . $link['id'] . '">' . $link['text'] . '</a>';
        if (!empty($link['sub'])) {
            $table_of_contents .= '<ol class="mokuji_list01">';
            foreach ($link['sub'] as $sub_link) {
                $table_of_contents .= '<li><a href="#' . $sub_link['id'] . '">' . $sub_link['text'] . '</a></li>';
            }
            $table_of_contents .= '</ol>';
        }
        $table_of_contents .= '</li>';
    }
    $table_of_contents .= '</ol>';

    $html = $dom->saveHTML();
    return $table_of_contents . $html;
}
add_shortcode('add_anchor', 'add_anchor_to_headings_shortcode');
```

このコードはショートコード `[add_anchor]` を追加します。このショートコードは記事の中に挿入することで、そのコンテンツ内のH2とH3見出しに対して一意のアンカーIDを付与し、それを基に目次を作成します。

次に、この新しいショートコードを使用するためのWordPressエディタ部分のコード例を見てみましょう。

```
[add_anchor]
<h2>############</h2>
<h3>############</h3>
<h3>############</h3>
<h2>############</h2>
<h3>############</h3>
<h3>############</h3>
<h2>############</h2>
<h3>############</h3>
<h3>############</h3>
[/add_anchor]
```

ショートコード `[add_anchor]` と`[/add_anchor]`の間にコンテンツを配置することで、そのコンテンツ内のH2とH3の見出しにアンカーIDが付与され、それに基づく目次が作成されます。

# コードの処理ごとの説明

それではコードを分割しながら解説していきます。

# ショートコードの設定

まず、`functions.php` ファイルにショートコード関数 `add_anchor_to_headings_shortcode()` を追加します。この関数は、ショートコードを解析し、HTML構造を読み込み、目次とアンカーを追加します。

```diff_php
function add_anchor_to_headings_shortcode($atts, $content = null) {
```

# DOMの準備

ここで、PHPのDOMDocumentオブジェクトを作成します。これにより、HTMLコードをパースして操作できます。

```diff_php
$dom = new DOMDocument();
@$dom->loadHTML(mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8'));
```

# カウンターとリンクの配列の初期化

見出しタグ (`<h2>` と `<h3>`) の数を追跡するカウンターを初期化します。また、リンク情報を保存するための配列も準備します。

```diff_php
$h2_counter = 0;
$h3_counter = 0;
$links = [];
```

# タグの走査とアンカーの追加

全てのタグを走査し、`<h2>` または `<h3>` タグが見つかった場合、アンカーIDを生成し、そのタグに追加します。同時に、リンク情報を配列に保存します。

```diff_php
$nodes = $dom->getElementsByTagName('*');
    foreach($nodes as $node) {
        if($node->nodeName == 'h2') {
            $h2_counter++;
            $h3_counter = 0; // Reset h3 counter for each new h2
            $id = 'anchor' . sprintf("%02d", $h2_counter);
            $node->setAttribute('id', $id);
            $links[] = ['id' => $id, 'text' => $node->nodeValue, 'sub' => []];
        } elseif($node->nodeName == 'h3' && $h2_counter > 0) { // Only assign ids to h3 tags if they are under a h2 tag
            $h3_counter++;
            $id = 'anchor' . sprintf("%02d", $h2_counter) . '-' . sprintf("%02d", $h3_counter);
            $node->setAttribute('id', $id);
            $links[end(array_keys($links))]['sub'][] = ['id' => $id, 'text' => $node->nodeValue];
        }
    }
```

# 目次の生成

リンク情報の配列を使って目次のHTMLを生成します。`<h2>` タグの見出しは目次のメインレベルに、`<h3>` タグの見出しはサブレベルに表示されます。

```diff_php
$table_of_contents = '<ol class="mokuji_list01">';
    foreach ($links as $link) {
        $table_of_contents .= '<li><a href="#' . $link['id'] . '">' . $link['text'] . '</a>';
        if (!empty($link['sub'])) {
            $table_of_contents .= '<ol class="mokuji_list01">';
            foreach ($link['sub'] as $sub_link) {
                $table_of_contents .= '<li><a href="#' . $sub_link['id'] . '">' . $sub_link['text'] . '</a></li>';
            }
            $table_of_contents .= '</ol>';
        }
        $table_of_contents .= '</li>';
    }
    $table_of_contents .= '</ol>';
```

# HTMLの生成とショートコードの登録

最後に、改変したHTMLと生成した目次を結合し、それを返します。そして、新しく作成した関数をショートコードとして登録します。

```diff_php
    $html = $dom->saveHTML();
    return $table_of_contents . $html;
}
add_shortcode('add_anchor', 'add_anchor_to_headings_shortcode');
```

# まとめ

以上が、`functions.php`に追加するコードの詳細な説明です。これにより `[add_anchor]` ショートコードが追加され、これを記事内で使うことで、そのコンテンツ内のH2とH3の見出しにアンカーIDが付与され、それに基づいて目次が作成されます。この方法を用いることで、ユーザーが特定のセクションに簡単にナビゲートできるような、より使いやすいコンテンツを作成することが可能になります。