';
}
else {
// this is the last item, make sure we close off all tags
for ($current_depth; $current_depth >= $numbered_items_min; $current_depth--) {
$html .= '';
if ( $current_depth != $numbered_items_min ) $html .= '';
}
}
}
return $html;
}
/**
* Returns a string with all items from the $find array replaced with their matching
* items in the $replace array. This does a one to one replacement (rather than
* globally).
*
* This function is multibyte safe.
*
* $find and $replace are arrays, $string is the haystack. All variables are
* passed by reference.
*/
private function mb_find_replace( &$find = false, &$replace = false, &$string = '' )
{
if ( is_array($find) && is_array($replace) && $string ) {
// check if multibyte strings are supported
if ( function_exists( 'mb_strpos' ) ) {
for ($i = 0; $i < count($find); $i++) {
$string =
mb_substr( $string, 0, mb_strpos($string, $find[$i]) ) . // everything befor $find
$replace[$i] . // its replacement
mb_substr( $string, mb_strpos($string, $find[$i]) + mb_strlen($find[$i]) ) // everything after $find
;
}
}
else {
for ($i = 0; $i < count($find); $i++) {
$string = substr_replace(
$string,
$replace[$i],
strpos($string, $find[$i]),
strlen($find[$i])
);
}
}
}
return $string;
}
/**
* This function extracts headings from the html formatted $content. It will pull out
* only the required headings as specified in the options. For all qualifying headings,
* this function populates the $find and $replace arrays (both passed by reference)
* with what to search and replace with.
*
* Returns a html formatted string of list items for each qualifying heading. This
* is everything between and NOT including
and
*/
public function extract_headings( &$find, &$replace, $content = '' )
{
$matches = array();
$anchor = '';
$items = false;
// reset the internal collision collection as the_content may have been triggered elsewhere
// eg by themes or other plugins that need to read in content such as metadata fields in
// the head html tag, or to provide descriptions to twitter/facebook
$this->collision_collector = array();
if ( is_array($find) && is_array($replace) && $content ) {
// get all headings
// the html spec allows for a maximum of 6 heading depths
if ( preg_match_all('/(]*>).*<\/h\2>/msuU', $content, $matches, PREG_SET_ORDER) ) {
// remove undesired headings (if any) as defined by heading_levels
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;
}
// remove specific headings if provided via the 'exclude' property
if ( $this->options['exclude'] ) {
$excluded_headings = explode('|', $this->options['exclude']);
if ( count($excluded_headings) > 0 ) {
for ($j = 0; $j < count($excluded_headings); $j++) {
// escape some regular expression characters
// others: http://www.php.net/manual/en/regexp.reference.meta.php
$excluded_headings[$j] = str_replace(
array('*'),
array('.*'),
trim($excluded_headings[$j])
);
}
$new_matches = array();
for ($i = 0; $i < count($matches); $i++) {
$found = false;
for ($j = 0; $j < count($excluded_headings); $j++) {
if ( @preg_match('/^' . $excluded_headings[$j] . '$/imU', strip_tags($matches[$i][0])) ) {
$found = true;
break;
}
}
if (!$found) $new_matches[] = $matches[$i];
}
if ( count($matches) != count($new_matches) )
$matches = $new_matches;
}
}
// remove empty headings
$new_matches = array();
for ($i = 0; $i < count($matches); $i++) {
if ( trim( strip_tags($matches[$i][0]) ) != false )
$new_matches[] = $matches[$i];
}
if ( count($matches) != count($new_matches) )
$matches = $new_matches;
// check minimum number of headings
if ( count($matches) >= $this->options['start'] ) {
for ($i = 0; $i < count($matches); $i++) {
// get anchor and add to find and replace arrays
$anchor = $this->url_anchor_target( $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]
);
// assemble flat list
if ( !$this->options['show_heirarchy'] ) {
$items .= '
';
}
}
// build a hierarchical toc?
// we could have tested for $items but that var can be quite large in some cases
if ( $this->options['show_heirarchy'] ) $items = $this->build_hierarchy( $matches );
}
}
}
return $items;
}
/**
* Returns true if the table of contents is eligible to be printed, false otherwise.
*/
public function is_eligible( $shortcode_used = false )
{
global $post;
// do not trigger the TOC when displaying an XML/RSS feed
if ( is_feed() ) return false;
// if the shortcode was used, this bypasses many of the global options
if ( $shortcode_used !== false ) {
// shortcode is used, make sure it adheres to the exclude from
// homepage option if we're on the homepage
if ( !$this->options['include_homepage'] && is_front_page() )
return false;
else
return true;
}
else {
if (
( in_array(get_post_type($post), $this->options['auto_insert_post_types']) && $this->show_toc && !is_search() && !is_archive() && !is_front_page() ) ||
( $this->options['include_homepage'] && is_front_page() )
) {
if ( $this->options['restrict_path'] ) {
if ( strpos($_SERVER['REQUEST_URI'], $this->options['restrict_path']) === 0 )
return true;
else
return false;
}
else
return true;
}
else
return false;
}
}
function the_content( $content )
{
global $post;
$items = $css_classes = $anchor = '';
$custom_toc_position = strpos($content, '');
$find = $replace = array();
if ( $this->is_eligible($custom_toc_position) ) {
$items = $this->extract_headings($find, $replace, $content);
if ( $items ) {
// do we display the toc within the content or has the user opted
// to only show it in the widget? if so, then we still need to
// make the find/replace call to insert the anchors
if ( $this->options['show_toc_in_widget_only'] && (in_array(get_post_type(), $this->options['show_toc_in_widget_only_post_types'])) ) {
$content = $this->mb_find_replace($find, $replace, $content);
}
else {
// wrapping css classes
switch( $this->options['wrapping'] ) {
case TOC_WRAPPING_LEFT:
$css_classes .= ' toc_wrap_left';
break;
case TOC_WRAPPING_RIGHT:
$css_classes .= ' toc_wrap_right';
break;
case TOC_WRAPPING_NONE:
default:
// do nothing
}
// colour themes
switch ( $this->options['theme'] ) {
case TOC_THEME_LIGHT_BLUE:
$css_classes .= ' toc_light_blue';
break;
case TOC_THEME_WHITE:
$css_classes .= ' toc_white';
break;
case TOC_THEME_BLACK:
$css_classes .= ' toc_black';
break;
case TOC_THEME_TRANSPARENT:
$css_classes .= ' toc_transparent';
break;
case TOC_THEME_GREY:
default:
// do nothing
}
// bullets?
if ( $this->options['bullet_spacing'] )
$css_classes .= ' have_bullets';
else
$css_classes .= ' no_bullets';
if ( $this->options['css_container_class'] ) $css_classes .= ' ' . $this->options['css_container_class'];
$css_classes = trim($css_classes);
// an empty class="" is invalid markup!
if ( !$css_classes ) $css_classes = ' ';
// add container, toc title and list items
$html = '
' . "\n";
if ( $custom_toc_position !== false ) {
$find[] = '';
$replace[] = $html;
$content = $this->mb_find_replace($find, $replace, $content);
}
else {
if ( count($find) > 0 ) {
switch ( $this->options['position'] ) {
case TOC_POSITION_TOP:
$content = $html . $this->mb_find_replace($find, $replace, $content);
break;
case TOC_POSITION_BOTTOM:
$content = $this->mb_find_replace($find, $replace, $content) . $html;
break;
case TOC_POSITION_AFTER_FIRST_HEADING:
$replace[0] = $replace[0] . $html;
$content = $this->mb_find_replace($find, $replace, $content);
break;
case TOC_POSITION_BEFORE_FIRST_HEADING:
default:
$replace[0] = $html . $replace[0];
$content = $this->mb_find_replace($find, $replace, $content);
}
}
}
}
}
}
else {
// remove (inserted from shortcode) from content
$content = str_replace('', '', $content);
}
return $content;
}
} // end class
endif;
if ( !class_exists( 'toc_widget' ) ) :
class toc_widget extends WP_Widget {
function __construct()
{
$widget_options = array(
'classname' => 'toc_widget',
'description' => __('Display the table of contents in the sidebar with this widget', 'table-of-contents-plus')
);
$control_options = array(
'width' => 250,
'height' => 350,
'id_base' => 'toc-widget'
);
parent::__construct( 'toc-widget', 'TOC+', $widget_options, $control_options );
}
/**
* Widget output to the public
*/
function widget( $args, $instance )
{
global $tic, $wp_query;
$items = $custom_toc_position = '';
$find = $replace = array();
$toc_options = $tic->get_options();
$post = get_post( $wp_query->post->ID );
$custom_toc_position = strpos( $post->post_content, '[toc]' ); // at this point, shortcodes haven't run yet so we can't search for
if ( $tic->is_eligible($custom_toc_position) ) {
extract( $args );
$items = $tic->extract_headings( $find, $replace, wptexturize($post->post_content) );
$title = ( array_key_exists('title', $instance) ) ? apply_filters('widget_title', $instance['title']) : '';
if ( strpos($title, '%PAGE_TITLE%') !== false ) $title = str_replace( '%PAGE_TITLE%', get_the_title(), $title );
if ( strpos($title, '%PAGE_NAME%') !== false ) $title = str_replace( '%PAGE_NAME%', get_the_title(), $title );
$hide_inline = $toc_options['show_toc_in_widget_only'];
$css_classes = '';
// bullets?
if ( $toc_options['bullet_spacing'] )
$css_classes .= ' have_bullets';
else
$css_classes .= ' no_bullets';
if ( $items ) {
// before widget (defined by themes)
echo $before_widget;
// display the widget title if one was input (before and after titles defined by themes)
if ( $title ) echo $before_title . $title . $after_title;
// display the list
echo '
' . $items . '
';
// after widget (defined by themes)
echo $after_widget;
}
}
}
/**
* Update the widget settings
*/
function update( $new_instance, $old_instance )
{
global $tic;
$instance = $old_instance;
// strip tags for title to remove HTML (important for text inputs)
$instance['title'] = strip_tags( trim( $new_instance['title'] ) );
// no need to strip tags for the following
//$instance['hide_inline'] = $new_instance['hide_inline'];
$tic->set_show_toc_in_widget_only( $new_instance['hide_inline'] );
$tic->set_show_toc_in_widget_only_post_types( (array)$new_instance['show_toc_in_widget_only_post_types'] );
return $instance;
}
/**
* Displays the widget settings on the widget panel.
*/
function form( $instance )
{
global $tic;
$toc_options = $tic->get_options();
$defaults = array(
'title' => $toc_options['heading_text']
);
$instance = wp_parse_args( (array)$instance, $defaults );
?>