プラグインなしで、wordpressに目次を表示するショートコードを実装する方法

[私のブログ](http://wkwkrnht.wp.xdomain.jp)には、[こういう](http://wkwkrnht.wp.xdomain.jp/game/gts/517)データベース系の記事が入り込んでいて、目次があった方がいいなと思っていました。そこで、テーマ側に移植してみました。（[元ネタ](https://xakuro.com/blog/wordpress/277)）

以下、コードです。機能自体は、グレードダウンしています。

```php
function make_toc($atts){
    $atts = shortcode_atts(array(
        'id'          => '',
        'class'       => 'toc',
        'title'       => '目次',
        'showcount'   => 2,
        'depth'       => 0,
        'toplevel'    => 1,
        'targetclass' => 'article-main'
    ),$atts);

    $content     = get_the_content();
    $headers     = array();
    $html        = '';
    $toc_list    = '';
    $id          = $atts['id'];
    $toggle      = '';
    $counter     = 0;
    $counters    = array(0,0,0,0,0,0);
    $top_level   = intval($atts['toplevel']);
    $harray      = array();
    $targetclass = trim($atts['targetclass']);
    if($targetclass===''){$targetclass = get_post_type();}
    for($h = $atts['toplevel']; $h <= 6; $h++){$harray[] = '"h' . $h . '"';}
    $harray = implode(',',$harray);

    preg_match_all('/<([hH][1-6]).*?>(.*?)<\/[hH][1-6].*?>/u',$content,$headers);
    $header_count = count($headers[0]);
    if($header_count > 0){
        $level = strtolower($headers[1][0]);
        if($top_level < $level){$top_level = $level;}
    }
    if($top_level < 1){$top_level = 1;}
    if($top_level > 6){$top_level = 6;}
    $atts['toplevel'] = $top_level;
    $current_depth          = $top_level - 1;
    $prev_depth             = $top_level - 1;
    $max_depth              = (($atts['depth'] == 0) ? 6 : intval($atts['depth'])) - $top_level + 1;

    for($i=0;$i < $header_count;$i++){
        $depth = 0;
        switch(strtolower($headers[1][$i])){
            case 'h1': $depth = 1 - $top_level + 1; break;
            case 'h2': $depth = 2 - $top_level + 1; break;
            case 'h3': $depth = 3 - $top_level + 1; break;
            case 'h4': $depth = 4 - $top_level + 1; break;
            case 'h5': $depth = 5 - $top_level + 1; break;
            case 'h6': $depth = 6 - $top_level + 1; break;
        }
        if($depth >= 1 && $depth <= $max_depth){
            if($current_depth == $depth){$toc_list .= '</li>';}
            while($current_depth > $depth){
                $toc_list .= '</li></ol>';
                $current_depth--;
                $counters[$current_depth] = 0;
            }
            if($current_depth != $prev_depth){$toc_list .= '</li>';}
            if($current_depth < $depth){
                $toc_list .= '<ol' . (($current_depth == $top_level - 1) ? ' class="toc-list open"' : '') . '>';
                $current_depth++;
            }
            $counters[$current_depth - 1] ++;
            $counter++;
            $toc_list .= '<li><a href="#toc' . $counter . '" tabindex="0">' . $headers[2][$i] . '</a>';
            $prev_depth = $depth;
        }
    }
    while($current_depth >= 1 ){
        $toc_list .= '</li></ol>';
        $current_depth--;
    }
    if($counter >= $atts['showcount']){
        if($id!==''){$id = ' id="' . $id . '"';}else{$id = '';}
        $html .= '
        <aside' . $id . ' class="' . $atts['class'] . '">
            <h2 class="toc-title">' . $atts['title'] . '</h2>
            ' . $toc_list .'
        </aside>
        <script>
            window.onload = function () {
                var idCounter = 0;
                var sub = [' . $harray . '];
                var targetClasses = document.getElementsByClassName("' . $targetclass . '");
                for (var i = 0; i < targetClasses.length; i++) {
                    var targetClass = targetClasses[i];
                    for (var m = 0; m < sub.length; m++) {
                        var targetHx = String(sub[m]);
                        var targetElements = targetClass.getElementsByTagName(targetHx);
                        for (var n = 0; n < targetElements.length; n++) {
                            var targetElement = targetElements[n];
                            if (targetElement.hasAttribute("class") === false) {
                                idCounter++;
                                targetElement.id = "toc" + idCounter;
                            }
                        }
                    }
                }
            };
        </script>';
    }
    return $html;
}
add_shortcode('toc','make_toc');
function wkwkrnht_add_quicktags(){
    if(wp_script_is('quicktags')===true){ ?>
    <script>
        QTags.addButton('qt-toc','目次','[toc id= class=toc title=目次 showcount=2 depth=0 toplevel=1 targetclass=article-main offset=]');
    </script>
    <?php }
}
add_action('admin_print_footer_scripts','wkwkrnht_add_quicktags');
```

本当に、目次を作って表示するだけです。そのほか、PHPで処理する部分は、ほぼそのままです。それでも、記事内で、目次内のリンクとhタグをリンクさせる部分は、完全オリジナルコードです。元ネタは、jQueryを使っていますが、移植する際に、内蔵のもので動くか心配だったため、ネイティブスクリプトに書き直しました。やっていることは単純です。ショートコードから来た変数を元に、要素を同定し、その中のhタグを昇順に取得、そして、その中でクラスを持っていないものに、目次用IDを付与しています。

WordPressの最新版で動作を確認しているので、安心してください。開閉ボタンは、暇を見つけて移植したいと思います。それでも、移動時間の設定はiQueryの特権に近いので、ご自身での移植をお願いします。