Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

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

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'));
share|improve this question
    
If I can get this to work, then I'll post it as a full answer, but I wonder whether you can place a shortcode that has a do_action() hook in it that you can then hook to. It's at least kind of possible, though I can't get it to work the way I expected yet. –  mrwweb Jul 2 '12 at 18:53

1 Answer 1

up vote 4 down vote accepted
+50

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.

share|improve this answer
    
Amazingly, this worked, I really didn't think it would but it does, thanks you! –  jasondavis Jul 1 '12 at 1:32
    
@jasondavis Please award the bounty to the solution. It's gone anyway. –  kaiser Jul 2 '12 at 17:13

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.