Table of Contents with a shortcode

I have this Table of Contents class below that will find all <h2> <h3> and <h4> tags in a wordpress post and it will create a Table of Contents out of them, it adds an ID to each heading so when you click a link it will take you to that part of the page.

Here is a snapshot of what the generated TOC can look like…
enter image description here

Read More

Now the problem I have with this class it is simply finds all Headings and builds the TOC on page load, it then prepends the generated TOC to the beginning of the $content post.

I would like to figure out a way where I can specify where the TOC will be shown in the post. So for example if a post has a featured image, then I would like to show the TOC below the image instead of at the very beginning of the post. Something like a shortcode would be ideal for this but I am not sure how to get it to work with a shortcode, I tried creating a shortcode which would run this class but it doesn’t work as I am wanting it to, it doesn’t create the TOC when I run it with a shortcode.

Anyone have any ideas how to improve this and get it working as I describe?

The class is ran with this Filter

add_filter('the_content', array('TableOfContents', 'writeTOC'));

The class

/**
 * cd-table-of-contents.php
 */

class TableOfContents {

    /**
     * Counts the occurence of header elements in WordPress content
     * 
     * @param type $content
     * @return null|boolean|array
     */
    static function hasToc($tiers, $content) {

        $pattern = '/<h[2-' . $tiers . ']*[^>]*>(.*?)</h([2-' . $tiers . '])>/';
        $return = array();
        if (empty($content))
            return null;

        if (!preg_match_all($pattern, $content, $return)) {
            return false;
        }
        return $return;
    }

    /**
     * Generates a table of content only when singular pages are being viewed
     * 
     * @param type $tiers
     * @param type $text
     */
    static function generateTableOfContents($tiers, $content, $draw = TRUE, $return = array()) {

        if (!is_singular())
            return $content;

        // numbers on or off?
        $num_on = true;

        $content = $toc . $content;
        $searches = array();
        $replaces = array();
        $return = (is_array($return) && !empty($return) ) ? $return : TableOfContents::hasToc($tiers, $content);

        if ($draw && !empty($return)):
            if($num_on){
                $toc = '<div class="toc nonumbers">';
            }else{
                $toc = '<div class="toc">';
            }

            $toc .= "<h4>Table of Contents</h4>";
            $toc .= "<ul class="parent start">";
            $tags = reset($return);
            $titles = $return[1];
            $levels = end($return);
            $_level = 2;
            $chapters = array('0','0','0','0','0','0');

            $count = 0;
            foreach ($tags as $i => $htag) {
                $count++;
                $attributes = array();
                $href = $count;

                $newId = 'id="' . $href . '"';
                $newhtag = '><span style="position: absolute; margin-top: -60px;" ' .$newId. '></span>';
                $htagr = str_replace('>' . $titles[$i], "t" . $newhtag  . $titles[$i], $htag);
                $searches[] = $htag;
                $replaces[] = $htagr;


                if ((int)$levels[$i] === (int)$_level):
                    if($num_on){
                        $chapters[$_level-1] = ((int)$chapters[$_level-1]+1);
                        $chapter = implode('.', array_slice($chapters, 1, ($levels[$i]-1)  ) );
                        $toc .= '<li><span>' . strval($chapter) . '</span> <a href="#' . $href . '">' . $titles[$i] . '</a></li>';
                    }else{
                        $toc .= '<li><a href="#' . $href . '">' . $titles[$i] . '</a></li>';
                    }

                endif;

                if ($levels[$i] > $_level) {
                    $_steps = ((int) $levels[$i] - (int) $_level);

                    for ($j = 0; $j < $_steps; $j++):
                        $toc .= '<ol class="continue">';
                        $chapters[$levels[$i]-1+$j] = (int)$chapters[$levels[$i]-1+$j]+1;
                        $_level++;
                    endfor;
                    $chapter = implode('.', array_slice($chapters, 1, ($levels[$i]-1)  ) );

                    if($num_on){
                        $toc .= '<li><span>' . strval($chapter) . '</span> <a href="#' . $href . '">' . $titles[$i] . '</a></li>';
                    }else{
                        $toc .= '<li><a href="#' . $href . '">' . $titles[$i] . '</a></li>';
                    }

                }

                if ($levels[$i] < $_level) {

                    $_steps = ((int) $_level - (int) $levels[$i]);
                    $chapters[$levels[$i]-1] = (int)$chapters[$levels[$i]-1]+1;
                    $_olevel = $_level;
                    for ($j = 0; $j < $_steps; $j++):
                        $chapters[$levels[$i]+$j] = 0;
                        $toc .= '</ol>';
                        $_level--;
                    endfor;

                    $chapters[$_olevel-1] = 0;
                    $chapter = implode('.', array_slice($chapters, 1, ($levels[$i]-1)  ) );

                    if($num_on){
                        $toc .= '<li><span>' . strval($chapter) . '</span> <a href="#' . $href . '">' . $titles[$i] . '</a></li>';
                    }else{
                        $toc .= '<li><a href="#' . $href . '">' . $titles[$i] . '</a></li>';
                    }

                }
            }
            $toc .= '</ul>';
            $toc .= '</div><div class="clear"></div>';
            $content = str_replace($searches, $replaces, $content);
            $content = $toc . $content;
        endif;

        return $content;
    }

    /**
     * Appends the table of content to the $content
     * AKA. Executes our filter
     * 
     * @param type $content
     * @return type
     */
    static function writeToc($content) {

        $content = TableOfContents::generateTableOfContents(4, $content, TRUE);
        return $content;
    }

}

add_filter('the_content', array('TableOfContents', 'writeTOC'));

Related posts

Leave a Reply

1 comment

  1. The easiest way would be to replace

    $content = $toc . $content;
    

    with

    $content = str_replace( '[toc]', $toc, $content );
    

    This would do a search for the string [toc] and replace it with the table of contents.

    It might be more complicated due to how the plugin would work, but on paper it is simple.