path = plugins_url( '', __FILE__ ); $this->exclude_post_types = array( 'attachment', 'revision', 'nav_menu_item', 'safecss' ); // get options $defaults = array( // default options 'heading_text' => 'Contents', 'start' => 3, 'show_heading_text' => true, 'auto_insert_post_types' => array('page', 'post'), 'heading_levels' => array('1', '2' ,'3', '4', '5', '6'), 'show_hierarchy' => true, 'number_list_items' => true, ); $options = get_option( EXTENDED_TOC_ID, $defaults ); $this->options = wp_parse_args( $options, $defaults ); add_action( 'plugins_loaded', array(&$this, 'plugins_loaded') ); if( is_admin() ) { //Additional links on the plugin page add_filter('plugin_row_meta', array(&$this, 'register_plugin_links'), 10, 2); add_action('admin_init', array(&$this, 'admin_init')); add_action('admin_menu', array(&$this, 'admin_menu')); } else { /** Add the content filter and enqueue css **/ add_filter( 'the_content', array(&$this, 'the_content'), 100 ); add_action( 'wp_enqueue_scripts', array(&$this, 'wp_enqueue_scripts') ); add_shortcode( 'extoc', array(&$this, 'shortcode_extoc') ); add_shortcode( 'noextoc', array(&$this, 'shortcode_noextoc') ); } } public function __destruct() { } public function register_plugin_links($links, $file) { if( $file == plugin_basename(__FILE__) ) { $links[] = '' . __('Donate', EXTENDED_TOC_ID) . ''; } return $links; } public function admin_init() { wp_register_style( EXTENDED_TOC_ID, $this->path . '/admin-style.css', array(), EXTENDED_TOC_VERSION ); wp_enqueue_style(EXTENDED_TOC_ID); } public function admin_menu() { // Create menu tab $page = add_submenu_page( 'plugins.php', EXTENDED_TOC_NAME, EXTENDED_TOC_NAME, 'manage_options', EXTENDED_TOC_ID, array(&$this, 'admin_options') ); } private function save_admin_options() { global $post_id; // security check if ( !wp_verify_nonce( @$_POST[EXTENDED_TOC_ID], plugin_basename(__FILE__) ) ) return false; // require an administrator level to save if ( !current_user_can( 'manage_options', $post_id ) ) return false; $this->options = array_merge( $this->options, array( 'heading_text' => stripslashes( trim($_POST['heading_text']) ), 'auto_insert_post_types' => @(array)$_POST['auto_insert_post_types'], 'start' => intval($_POST['start']), 'show_heading_text' => (isset($_POST['show_heading_text']) && $_POST['show_heading_text']) ? true : false, 'show_hierarchy' => (isset($_POST['show_hierarchy']) && $_POST['show_hierarchy']) ? true : false, 'number_list_items' => (isset($_POST['number_list_items']) && $_POST['number_list_items']) ? true : false, 'heading_levels' => @(array)$_POST['heading_levels'], ) ); // update_option will return false if no changes were made update_option( EXTENDED_TOC_ID, $this->options ); return true; } public function admin_options() { if( isset($_GET['update']) ) { if( $this->save_admin_options() ) $msg = '

' . __('Options saved.', EXTENDED_TOC_ID) . '

'; else $msg = '

' . __('Save failed.', EXTENDED_TOC_ID) . '

'; } ?>

displayMainContent(); } else { $this->displayHelpContent(); } ?>
options['show_heading_text'] ) echo ' checked="checked"'; ?> />
exclude_post_types) ): ?> options['auto_insert_post_types'])?' checked="checked"':''?> />
>
options['show_hierarchy'] ) echo ' checked="checked"'; ?> />
options['number_list_items'] ) echo ' checked="checked"'; ?> />
options['heading_levels']) ) echo ' checked="checked"'; echo ' />
'; } ?>

" name="submit" />

Position the ToC

The table of contents is generated automatically and is inserted at the very top of your post and, if its paginated, at the top of every subpage. To change the position of the ToC you can insert the markup [extoc] at the position you want it to be displayed. You have to position the ToC on every subpage, otherwise it will be shown on the subpages again at the top of the page.

Blacklist posts/pages

If you need a table of content for the main part of you posts and pages, but you want to exclude the ToC from sepcial posts, you can use a blacklist. Per default the ToC is shown in posts and pages. You can insert the markup [noextoc] to prohibit the insertion of the ToC in this page/post/subpage. This markup also has to be inserted in every subpage, if you use the nextpage-tag, otherwise it will be inserted within the subpages.

Whitelist posts/pages

If you have only a few posts where you want the ToC to be inserted, you can switch off the ToC from general settings and insert it within your posts/pages by the markup [extoc].

Individual setting withthin the [extoc] markup

The [extoc] markup can also be used to change the main settings for the ToC only for some posts/pages.

Example: [extoc start=10 headers=1,2,3 title="My individual ToC title"]

This will insert a ToC that only will be displayed if 10 oder more headings are contained. "headers=1,2,3" means that only the header h1, h2 and h3 are considered for the ToC. The "title" attribute lets you set an individual title for the ToC. If one of these attributes is missing, the default value will be taken.

You can also remove the title by adding "notitle" e.g. [extoc notitle]. Leaving the title attribute empty will also take the header defined within the general plugin settings.
path . '/style.css', array(), 'POWER_TOC_VERSION'); wp_enqueue_style(EXTENDED_TOC_ID); } public function plugins_loaded() { load_plugin_textdomain( EXTENDED_TOC_ID, false, dirname(plugin_basename(__FILE__)) . '/locale/' ); } public function shortcode_extoc($atts) { extract( shortcode_atts( array( 'start' => $this->options["start"], 'headers' => $this->options["heading_levels"], 'title' => $this->options["heading_text"], ), $atts ) ); if( !is_array($headers) ) $headers = preg_split('/[\s*,]+/i', $headers); if($start) $this->options['start'] = $start; if($headers) $this->options['heading_levels'] = $headers; if($title) $this->options['heading_text'] = $title; if( isset($atts[0]['notitle']) ) $this->options['show_heading_text'] = false; if( !is_search() && !is_archive() && !is_feed() && !is_front_page() ) return '[extoc]'; else return; } public function shortcode_noextoc($atts) { return; } public function the_content($content) { global $post; // Reset the counter $this->counter = array(); if( is_search() || is_archive() || is_front_page() || is_feed() ) return $content; /** Extract the content, and extract the part content if was used **/ $this->content = $content; // The original content (subpage) that is displayed $this->extract_full_post_content(); $toc_content = "
"; if( $this->options['show_heading_text'] == true ) $toc_content .= "

" . $this->options["heading_text"] . "

"; $toc_content .= "
"; if( $this->totalHeadings >= $this->options['start'] ) return $this->insert_toc_at_markup_position($toc_content); // $toc_content . $this->content; else { $content = preg_replace("/\[extoc\]|\[noextoc\]/", "", $this->content); return $content; } } /** returns the content for display added by the ToC */ private function insert_toc_at_markup_position($toc_content) { // clean content without markups for returning $content = $this->content; $content = preg_replace("/\[noextoc\]/", "", $content); // preg_replace("/\[extoc\]|\[noextoc\]/", "", $content); // [noextoc] has priority. If this is found, return the original if( strpos($this->content, '[noextoc]') !== false ) return $content; // try to find the markup for the ToC $pos = strpos($this->content, '[extoc]'); // we didn't find any markup... if( $pos === false ) { global $post; // There was no markup, so insert at top or return original if this type does not need a ToC if( !in_array(get_post_type($post), $this->options['auto_insert_post_types']) ) return $content; else return $toc_content . $content; } // In this case, the markup was found in the content if( is_numeric($pos) && $pos >= 0 ) { return str_replace('[extoc]', $toc_content, $content); // substr($content, 0, $pos) . $toc_content . substr($content, $pos); } // Absolute backup, return the content. This point should actually never be reached return $content; } /** Extract the full unshortened content from the post **/ private function extract_full_post_content() { global $post; $this->fullcontent = $post->post_content; $this->ID = $post->ID; } private function extract_toc() { /** check within the full content how many pages exists */ $this->extract_pages(); $headers = ""; // Reset all settings $this->minLevel = null; // private $path; // private $content = ""; // private $fullcontent = ""; // private $pages = array(); // private $ID = 0; // private $counter = array(); // private $totalHeadings = 0; // private $minLevel = null; /** Extract headings from every pages */ for( $pagenum = 1; $pagenum <= count($this->pages); $pagenum++ ) { $headers .= $this->exctract_headings($pagenum); } return $headers; } private function extract_pages() { /** Split the content by "nextpage"-tags if some exists */ $this->pages = preg_split("//msuU", $this->fullcontent); } private function exctract_headings($pagenum) { /** find all header tags within the page **/ preg_match_all('/(]*>).*<\/h\2>/msuU', $this->pages[$pagenum-1], $matches, PREG_SET_ORDER); if (count($matches) == 0) { return null; } /** Check the headings that are desired */ if( count($this->options['heading_levels']) != 6 ) { $new_matches = array(); for ($i = 0; $i < count($matches); $i++) { if( in_array($matches[$i][2], $this->options['heading_levels']) ) $new_matches[] = $matches[$i]; } $matches = $new_matches; } $items = ""; /** Take first h-level as baseline */ if( $this->minLevel == null ) $this->minLevel = $matches[0][2]; // lowest level e.g. h3 $currentLevel = $this->minLevel; // $minLevel; $this->counter[$currentLevel] = 0; for( $i = 0; $i < count($matches); $i++ ) { /** get anchor and add to find and replace arrays **/ $anchor = $this->url_encode_anchor($matches[$i][0]); $find = $matches[$i][0]; $replace = str_replace( array( $matches[$i][1], // start of heading '' // end of heading ), array( $matches[$i][1] . '', '' ), $matches[$i][0] ); $this->content = str_replace($find, $replace, $this->content); /** Check if header lower current header, then add level and update current header */ if( $matches[$i][2] > $currentLevel && $this->options['show_hierarchy'] == true) { $currentLevel = $matches[$i][2]; $this->counter[$currentLevel] += 1; } else if( $matches[$i][2] < $currentLevel && $matches[$i][2] >= $this->minLevel && $this->options['show_hierarchy'] == true) { $currentLevel = $matches[$i][2]; $this->counter[$currentLevel] += 1; } else { $this->counter[$currentLevel] += 1; } /** build html */ $items .= '
  • '; global $page; if( $pagenum == $page && is_single() ) $items .= ''; else { if( $pagenum == 1 ) $items .= ''; else $items .= ''; } // Show numbers only if user wants it if( $this->options['number_list_items'] ) { $items .= ""; if( $this->options['show_hierarchy'] == true ) { for( $j = $this->minLevel; $j < $currentLevel; $j++ ) { $items = $items . $this->counter[$j] . "."; } } $items = $items . $this->counter[$currentLevel]; $items .= ""; } $items .= strip_tags($matches[$i][0]) . ''; $items .= '
  • '; $this->totalHeadings++; } return $items; } private function url_encode_anchor($anchor) { $return = false; if(!empty($anchor) ) { /** Remove tags */ $return = trim( strip_tags($anchor) ); /** remove & */ $return = str_replace( '&', '', $return ); /** remove all unknown chars **/ $return = preg_replace("/[^0-9a-zA-Z \-_]+/", "", $return); /** Remove backspace etc */ $return = preg_replace("/[\s]+/", "-", $return); /** If we now start or end with a - or _ remove it */ $return = preg_replace("/^[-_]/", "", $return); $return = preg_replace("/[-_]$/", "", $return); } return $return; } } } /** Initialise the class */ $tocPlugin = new ExToC(); ?>