show_toc = true; $this->exclude_post_types = [ 'attachment', 'revision', 'nav_menu_item', 'safecss' ]; $this->collision_collector = []; // get options $defaults = [ // default options 'fragment_prefix' => 'i', 'position' => TOC_POSITION_BEFORE_FIRST_HEADING, 'start' => 4, 'show_heading_text' => true, 'heading_text' => 'Contents', 'auto_insert_post_types' => [ 'page' ], 'show_heirarchy' => true, 'ordered_list' => true, 'smooth_scroll' => false, 'smooth_scroll_offset' => TOC_SMOOTH_SCROLL_OFFSET, 'visibility' => true, 'visibility_show' => 'show', 'visibility_hide' => 'hide', 'visibility_hide_by_default' => false, 'width' => 'Auto', 'width_custom' => '275', 'width_custom_units' => 'px', 'wrapping' => TOC_WRAPPING_NONE, 'font_size' => '95', 'font_size_units' => '%', 'theme' => TOC_THEME_GREY, 'custom_background_colour' => TOC_DEFAULT_BACKGROUND_COLOUR, 'custom_border_colour' => TOC_DEFAULT_BORDER_COLOUR, 'custom_title_colour' => TOC_DEFAULT_TITLE_COLOUR, 'custom_links_colour' => TOC_DEFAULT_LINKS_COLOUR, 'custom_links_hover_colour' => TOC_DEFAULT_LINKS_HOVER_COLOUR, 'custom_links_visited_colour' => TOC_DEFAULT_LINKS_VISITED_COLOUR, 'lowercase' => false, 'hyphenate' => false, 'bullet_spacing' => false, 'include_homepage' => false, 'exclude_css' => false, 'exclude' => '', 'heading_levels' => [ 1, 2, 3, 4, 5, 6 ], 'restrict_path' => '', 'css_container_class' => '', 'sitemap_show_page_listing' => true, 'sitemap_show_category_listing' => true, 'sitemap_heading_type' => 3, 'sitemap_pages' => 'Pages', 'sitemap_categories' => 'Categories', 'show_toc_in_widget_only' => false, 'show_toc_in_widget_only_post_types' => [ 'page' ], ]; $options = get_option( 'toc-options', $defaults ); $this->options = wp_parse_args( $options, $defaults ); add_action( 'plugins_loaded', [ $this, 'plugins_loaded' ] ); add_action( 'wp_enqueue_scripts', [ $this, 'wp_enqueue_scripts' ] ); add_action( 'admin_init', [ $this, 'admin_init' ] ); add_action( 'admin_menu', [ $this, 'admin_menu' ] ); add_action( 'widgets_init', [ $this, 'widgets_init' ] ); add_action( 'sidebar_admin_setup', [ $this, 'sidebar_admin_setup' ] ); add_action( 'init', [ $this, 'init' ] ); add_filter( 'the_content', [ $this, 'the_content' ], 100 ); // run after shortcodes are interpreted (level 10) add_filter( 'plugin_action_links', [ $this, 'plugin_action_links' ], 10, 2 ); add_filter( 'widget_text', 'do_shortcode' ); add_shortcode( 'toc', [ $this, 'shortcode_toc' ] ); add_shortcode( 'no_toc', [ $this, 'shortcode_no_toc' ] ); add_shortcode( 'sitemap', [ $this, 'shortcode_sitemap' ] ); add_shortcode( 'sitemap_pages', [ $this, 'shortcode_sitemap_pages' ] ); add_shortcode( 'sitemap_categories', [ $this, 'shortcode_sitemap_categories' ] ); add_shortcode( 'sitemap_posts', [ $this, 'shortcode_sitemap_posts' ] ); } public function __destruct() {} public function get_options() { return $this->options; } public function set_option( $options ) { $this->options = array_merge( $this->options, $options ); } /** * Allows the developer to disable TOC execution */ public function disable() { $this->show_toc = false; } /** * Allows the developer to enable TOC execution */ public function enable() { $this->show_toc = true; } public function set_show_toc_in_widget_only( $value = false ) { if ( $value ) { $this->options['show_toc_in_widget_only'] = true; } else { $this->options['show_toc_in_widget_only'] = false; } update_option( 'toc-options', $this->options ); } public function set_show_toc_in_widget_only_post_types( $value = false ) { if ( $value ) { $this->options['show_toc_in_widget_only_post_types'] = $value; } else { $this->options['show_toc_in_widget_only_post_types'] = []; } update_option( 'toc-options', $this->options ); } public function get_exclude_post_types() { return $this->exclude_post_types; } public function plugin_action_links( $links, $file ) { if ( 'table-of-contents-plus/toc.php' === $file ) { $settings_link = '' . __( 'Settings', 'table-of-contents-plus' ) . ''; $links = array_merge( [ $settings_link ], $links ); } return $links; } public function shortcode_toc( $attributes ) { $atts = shortcode_atts( [ 'label' => $this->options['heading_text'], 'label_show' => $this->options['visibility_show'], 'label_hide' => $this->options['visibility_hide'], 'no_label' => false, 'class' => false, 'wrapping' => $this->options['wrapping'], 'heading_levels' => $this->options['heading_levels'], 'exclude' => $this->options['exclude'], 'collapse' => false, 'no_numbers' => false, 'start' => $this->options['start'], ], $attributes ); $re_enqueue_scripts = false; if ( $atts['no_label'] ) { $this->options['show_heading_text'] = false; } if ( $atts['label'] ) { $this->options['heading_text'] = wp_kses_post( html_entity_decode( $atts['label'] ) ); } if ( $atts['label_show'] ) { $this->options['visibility_show'] = wp_kses_post( html_entity_decode( $atts['label_show'] ) ); $re_enqueue_scripts = true; } if ( $atts['label_hide'] ) { $this->options['visibility_hide'] = wp_kses_post( html_entity_decode( $atts['label_hide'] ) ); $re_enqueue_scripts = true; } if ( $atts['class'] ) { $this->options['css_container_class'] = wp_kses_post( html_entity_decode( $atts['class'] ) ); } if ( $atts['wrapping'] ) { switch ( strtolower( trim( $atts['wrapping'] ) ) ) { case 'left': $this->options['wrapping'] = TOC_WRAPPING_LEFT; break; case 'right': $this->options['wrapping'] = TOC_WRAPPING_RIGHT; break; default: // do nothing } } if ( $atts['exclude'] ) { $this->options['exclude'] = $atts['exclude']; } if ( $atts['collapse'] ) { $this->options['visibility_hide_by_default'] = true; $re_enqueue_scripts = true; } if ( $atts['no_numbers'] ) { $this->options['ordered_list'] = false; } if ( is_numeric( $atts['start'] ) ) { $this->options['start'] = $atts['start']; } if ( $re_enqueue_scripts ) { do_action( 'wp_enqueue_scripts' ); } // if $atts['heading_levels'] is an array, then it came from the global options // and wasn't provided by per instance if ( $atts['heading_levels'] && ! is_array( $atts['heading_levels'] ) ) { // make sure they are numbers between 1 and 6 and put into // the $clean_heading_levels array if not already $clean_heading_levels = []; foreach ( explode( ',', $atts['heading_levels'] ) as $heading_level ) { if ( is_numeric( $heading_level ) ) { $heading_level = (int) $heading_level; if ( 1 <= $heading_level && $heading_level <= 6 ) { if ( ! in_array( $heading_level, $clean_heading_levels, true ) ) { $clean_heading_levels[] = (int) $heading_level; } } } } if ( count( $clean_heading_levels ) > 0 ) { $this->options['heading_levels'] = $clean_heading_levels; } } if ( ! is_search() && ! is_archive() && ! is_feed() ) { return ''; } else { return ''; } } public function shortcode_no_toc( $atts ) { $this->show_toc = false; return ''; } public function shortcode_sitemap( $atts ) { $html = ''; // only do the following if enabled if ( $this->options['sitemap_show_page_listing'] || $this->options['sitemap_show_category_listing'] ) { $html = '
'; if ( $this->options['sitemap_show_page_listing'] ) { $html .= 'options['sitemap_heading_type'] . ' class="toc_sitemap_pages">' . htmlentities( $this->options['sitemap_pages'], ENT_COMPAT, 'UTF-8' ) . 'options['sitemap_heading_type'] . '>' . ''; } if ( $this->options['sitemap_show_category_listing'] ) { $html .= 'options['sitemap_heading_type'] . ' class="toc_sitemap_categories">' . htmlentities( $this->options['sitemap_categories'], ENT_COMPAT, 'UTF-8' ) . 'options['sitemap_heading_type'] . '>' . ''; } $html .= '
'; } return $html; } public function shortcode_sitemap_pages( $attributes ) { $atts = shortcode_atts( [ 'heading' => $this->options['sitemap_heading_type'], 'label' => $this->options['sitemap_pages'], 'no_label' => false, 'exclude' => '', 'exclude_tree' => '', 'child_of' => 0, ], $attributes ); $atts['heading'] = intval( $atts['heading'] ); // make sure it's an integer if ( $atts['heading'] < 1 || $atts['heading'] > 6 ) { // h1 to h6 are valid $atts['heading'] = $this->options['sitemap_heading_type']; } if ( 'current' === strtolower( $atts['child_of'] ) ) { $atts['child_of'] = get_the_ID(); } elseif ( is_numeric( $atts['child_of'] ) ) { $atts['child_of'] = intval( $atts['child_of'] ); } else { $atts['child_of'] = 0; } $html = '
'; if ( ! $atts['no_label'] ) { $html .= '' . htmlentities( $atts['label'], ENT_COMPAT, 'UTF-8' ) . ''; } $html .= '' . '
'; return $html; } public function shortcode_sitemap_categories( $attributes ) { $atts = shortcode_atts( [ 'heading' => $this->options['sitemap_heading_type'], 'label' => $this->options['sitemap_categories'], 'no_label' => false, 'exclude' => '', 'exclude_tree' => '', ], $attributes ); $atts['heading'] = intval( $atts['heading'] ); // make sure it's an integer if ( $atts['heading'] < 1 || $atts['heading'] > 6 ) { // h1 to h6 are valid $atts['heading'] = $this->options['sitemap_heading_type']; } $html = '
'; if ( ! $atts['no_label'] ) { $html .= '' . htmlentities( $atts['label'], ENT_COMPAT, 'UTF-8' ) . ''; } $html .= '' . '
'; return $html; } public function shortcode_sitemap_posts( $attributes ) { $atts = shortcode_atts( [ 'order' => 'ASC', 'orderby' => 'title', 'separate' => true, ], $attributes ); $articles = new WP_Query( [ 'post_type' => 'post', 'post_status' => 'publish', 'order' => $atts['order'], 'orderby' => $atts['orderby'], 'posts_per_page' => -1, ] ); $html = ''; $letter = ''; $atts['separate'] = strtolower( $atts['separate'] ); if ( 'false' === $atts['separate'] || 'no' === $atts['separate'] ) { $atts['separate'] = false; } while ( $articles->have_posts() ) { $articles->the_post(); $title = wp_strip_all_tags( get_the_title() ); if ( $atts['separate'] ) { if ( strtolower( $title[0] ) !== $letter ) { if ( $letter ) { $html .= ''; } $html .= '

' . strtolower( $title[0] ) . '

'; } else { $html = '
'; } } wp_reset_postdata(); return $html; } /** * Register and load CSS and javascript files for frontend. */ public function wp_enqueue_scripts() { $js_vars = []; // register our CSS and scripts wp_register_style( 'toc-screen', TOC_PLUGIN_PATH . '/screen.min.css', [], TOC_VERSION ); wp_register_script( 'toc-front', TOC_PLUGIN_PATH . '/front.min.js', [ 'jquery' ], TOC_VERSION, true ); // enqueue them! if ( ! $this->options['exclude_css'] ) { wp_enqueue_style( 'toc-screen' ); // add any admin GUI customisations $custom_css = $this->get_custom_css(); if ( $custom_css ) { wp_add_inline_style( 'toc-screen', $custom_css ); } } if ( $this->options['smooth_scroll'] ) { $js_vars['smooth_scroll'] = true; } wp_enqueue_script( 'toc-front' ); if ( $this->options['show_heading_text'] && $this->options['visibility'] ) { $width = ( 'User defined' !== $this->options['width'] ) ? $this->options['width'] : $this->options['width_custom'] . $this->options['width_custom_units']; $js_vars['visibility_show'] = esc_js( $this->options['visibility_show'] ); $js_vars['visibility_hide'] = esc_js( $this->options['visibility_hide'] ); if ( $this->options['visibility_hide_by_default'] ) { $js_vars['visibility_hide_by_default'] = true; } $js_vars['width'] = esc_js( $width ); } if ( TOC_SMOOTH_SCROLL_OFFSET !== $this->options['smooth_scroll_offset'] ) { $js_vars['smooth_scroll_offset'] = esc_js( $this->options['smooth_scroll_offset'] ); } if ( count( $js_vars ) > 0 ) { wp_localize_script( 'toc-front', 'tocplus', $js_vars ); } } public function plugins_loaded() { load_plugin_textdomain( 'table-of-contents-plus', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' ); } public function admin_init() { wp_register_script( 'toc_admin_script', TOC_PLUGIN_PATH . '/admin.js', [], TOC_VERSION, true ); wp_register_style( 'toc_admin_style', TOC_PLUGIN_PATH . '/admin.css', [], TOC_VERSION ); } public function admin_menu() { $page = add_submenu_page( 'options-general.php', __( 'TOC', 'table-of-contents-plus' ) . '+', __( 'TOC', 'table-of-contents-plus' ) . '+', 'manage_options', 'toc', [ $this, 'admin_options' ] ); add_action( 'admin_print_styles-' . $page, [ $this, 'admin_options_head' ] ); } public function widgets_init() { register_widget( 'toc_widget' ); } /** * Remove widget options on widget deletion */ public function sidebar_admin_setup() { // this action is loaded at the start of the widget screen // so only do the following when a form action has been initiated if ( 'post' === strtolower( $_SERVER['REQUEST_METHOD'] ) ) { if ( isset( $_POST['id_base'] ) && 'toc-widget' === $_POST['id_base'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( isset( $_POST['delete_widget'] ) && 1 === (int) $_POST['delete_widget'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing $this->set_show_toc_in_widget_only( false ); $this->set_show_toc_in_widget_only_post_types( [ 'page' ] ); } } } } public function init() { // Add compatibility with Rank Math SEO if ( class_exists( 'RankMath' ) ) { add_filter( 'rank_math/researches/toc_plugins', function ( $toc_plugins ) { $toc_plugins['table-of-contents-plus/toc.php'] = 'Table of Contents Plus'; return $toc_plugins; } ); } } /** * Load needed scripts and styles only on the toc administration interface. */ public function admin_options_head() { wp_enqueue_style( 'farbtastic' ); wp_enqueue_script( 'farbtastic' ); wp_enqueue_script( 'jquery' ); wp_enqueue_script( 'toc_admin_script' ); wp_enqueue_style( 'toc_admin_style' ); } /** * Tries to convert $input into a valid hex colour. * Returns $default if $input is not a hex value, otherwise returns verified hex. */ private function hex_value( $input = '', $default = '#' ) { $return = $default; if ( $input ) { // strip out non hex chars $return = preg_replace( '/[^a-fA-F0-9]*/', '', $input ); switch ( strlen( $return ) ) { case 3: // do next case 6: $return = '#' . $return; break; default: if ( strlen( $return ) > 6 ) { $return = '#' . substr( $return, 0, 6 ); // if > 6 chars, then take the first 6 } elseif ( strlen( $return ) > 3 && strlen( $return ) < 6 ) { $return = '#' . substr( $return, 0, 3 ); // if between 3 and 6, then take first 3 } else { $return = $default; // not valid, return $default } } } return $return; } private function save_admin_options() { global $post_id; // security check if ( ! isset( $_POST['toc-admin-options'] ) ) { return false; } if ( ! wp_verify_nonce( $_POST['toc-admin-options'], plugin_basename( __FILE__ ) ) ) { return false; } // require an administrator level to save if ( ! current_user_can( 'manage_options', $post_id ) ) { return false; } // use stripslashes on free text fields that can have ' " \ // WordPress automatically slashes these characters as part of // wp-includes/load.php::wp_magic_quotes() $custom_background_colour = $this->hex_value( trim( $_POST['custom_background_colour'] ), TOC_DEFAULT_BACKGROUND_COLOUR ); $custom_border_colour = $this->hex_value( trim( $_POST['custom_border_colour'] ), TOC_DEFAULT_BORDER_COLOUR ); $custom_title_colour = $this->hex_value( trim( $_POST['custom_title_colour'] ), TOC_DEFAULT_TITLE_COLOUR ); $custom_links_colour = $this->hex_value( trim( $_POST['custom_links_colour'] ), TOC_DEFAULT_LINKS_COLOUR ); $custom_links_hover_colour = $this->hex_value( trim( $_POST['custom_links_hover_colour'] ), TOC_DEFAULT_LINKS_HOVER_COLOUR ); $custom_links_visited_colour = $this->hex_value( trim( $_POST['custom_links_visited_colour'] ), TOC_DEFAULT_LINKS_VISITED_COLOUR ); $restrict_path = trim( $_POST['restrict_path'] ); if ( $restrict_path ) { if ( strpos( $restrict_path, '/' ) !== 0 ) { // restrict path did not start with a / so unset it $restrict_path = ''; } } $this->options = array_merge( $this->options, [ 'fragment_prefix' => trim( $_POST['fragment_prefix'] ), 'position' => intval( $_POST['position'] ), 'start' => intval( $_POST['start'] ), 'show_heading_text' => ( isset( $_POST['show_heading_text'] ) && $_POST['show_heading_text'] ) ? true : false, 'heading_text' => stripslashes( trim( $_POST['heading_text'] ) ), 'auto_insert_post_types' => ( isset( $_POST['auto_insert_post_types'] ) ) ? (array) $_POST['auto_insert_post_types'] : array(), 'show_heirarchy' => ( isset( $_POST['show_heirarchy'] ) && $_POST['show_heirarchy'] ) ? true : false, 'ordered_list' => ( isset( $_POST['ordered_list'] ) && $_POST['ordered_list'] ) ? true : false, 'smooth_scroll' => ( isset( $_POST['smooth_scroll'] ) && $_POST['smooth_scroll'] ) ? true : false, 'smooth_scroll_offset' => intval( $_POST['smooth_scroll_offset'] ), 'visibility' => ( isset( $_POST['visibility'] ) && $_POST['visibility'] ) ? true : false, 'visibility_show' => stripslashes( trim( $_POST['visibility_show'] ) ), 'visibility_hide' => stripslashes( trim( $_POST['visibility_hide'] ) ), 'visibility_hide_by_default' => ( isset( $_POST['visibility_hide_by_default'] ) && $_POST['visibility_hide_by_default'] ) ? true : false, 'width' => trim( $_POST['width'] ), 'width_custom' => floatval( $_POST['width_custom'] ), 'width_custom_units' => trim( $_POST['width_custom_units'] ), 'wrapping' => intval( $_POST['wrapping'] ), 'font_size' => floatval( $_POST['font_size'] ), 'font_size_units' => trim( $_POST['font_size_units'] ), 'theme' => intval( $_POST['theme'] ), 'custom_background_colour' => $custom_background_colour, 'custom_border_colour' => $custom_border_colour, 'custom_title_colour' => $custom_title_colour, 'custom_links_colour' => $custom_links_colour, 'custom_links_hover_colour' => $custom_links_hover_colour, 'custom_links_visited_colour' => $custom_links_visited_colour, 'lowercase' => ( isset( $_POST['lowercase'] ) && $_POST['lowercase'] ) ? true : false, 'hyphenate' => ( isset( $_POST['hyphenate'] ) && $_POST['hyphenate'] ) ? true : false, 'bullet_spacing' => ( isset( $_POST['bullet_spacing'] ) && $_POST['bullet_spacing'] ) ? true : false, 'include_homepage' => ( isset( $_POST['include_homepage'] ) && $_POST['include_homepage'] ) ? true : false, 'exclude_css' => ( isset( $_POST['exclude_css'] ) && $_POST['exclude_css'] ) ? true : false, 'heading_levels' => ( isset( $_POST['heading_levels'] ) ) ? array_map( 'intval', (array) $_POST['heading_levels'] ) : array(), 'exclude' => stripslashes( trim( $_POST['exclude'] ) ), 'restrict_path' => $restrict_path, 'sitemap_show_page_listing' => ( isset( $_POST['sitemap_show_page_listing'] ) && $_POST['sitemap_show_page_listing'] ) ? true : false, 'sitemap_show_category_listing' => ( isset( $_POST['sitemap_show_category_listing'] ) && $_POST['sitemap_show_category_listing'] ) ? true : false, 'sitemap_heading_type' => intval( $_POST['sitemap_heading_type'] ), 'sitemap_pages' => stripslashes( trim( $_POST['sitemap_pages'] ) ), 'sitemap_categories' => stripslashes( trim( $_POST['sitemap_categories'] ) ), ] ); // update_option will return false if no changes were made update_option( 'toc-options', $this->options ); return true; } public function admin_options() { $msg = ''; // was there a form submission, if so, do security checks and try to save form if ( isset( $_GET['update'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( $this->save_admin_options() ) { $msg = '

' . __( 'Options saved.', 'table-of-contents-plus' ) . '

'; } else { $msg = '

' . __( 'Save failed.', 'table-of-contents-plus' ) . '

'; } } ?>

Table of Contents Plus

exclude_post_types, true ) ) { echo 'options['auto_insert_post_types'], true ) ) echo ' checked="checked"'; echo ' />
'; } } ?>
options['show_heading_text'] ) echo ' checked="checked"'; ?> />


options['visibility'] ) echo ' checked="checked"'; ?> />
options['visibility_hide_by_default'] ) echo ' checked="checked"'; ?> />
options['show_heirarchy'] ) echo ' checked="checked"'; ?> />
options['ordered_list'] ) echo ' checked="checked"'; ?> />
options['smooth_scroll'] ) echo ' checked="checked"'; ?> />

TOC_THEME_GREY, 'name' => __( 'Grey (default)', 'table-of-contents-plus' ), 'url' => TOC_PLUGIN_PATH . '/images/grey.png', ], [ 'id' => TOC_THEME_LIGHT_BLUE, 'name' => __( 'Light blue', 'table-of-contents-plus' ), 'url' => TOC_PLUGIN_PATH . '/images/blue.png', ], [ 'id' => TOC_THEME_WHITE, 'name' => __( 'White', 'table-of-contents-plus' ), 'url' => TOC_PLUGIN_PATH . '/images/white.png', ], [ 'id' => TOC_THEME_BLACK, 'name' => __( 'Black', 'table-of-contents-plus' ), 'url' => TOC_PLUGIN_PATH . '/images/black.png', ], [ 'id' => TOC_THEME_TRANSPARENT, 'name' => __( 'Transparent', 'table-of-contents-plus' ), 'url' => TOC_PLUGIN_PATH . '/images/transparent.png', ], [ 'id' => TOC_THEME_CUSTOM, 'name' => __( 'Custom', 'table-of-contents-plus' ), 'url' => TOC_PLUGIN_PATH . '/images/custom.png', ], ]; foreach ( $theme_options as $theme_option ) { printf( '
' . '' . '' . '
', esc_attr( $theme_option['id'] ), ( $theme_option['id'] === $this->options['theme'] ) ? ' checked="checked"' : '', esc_html( $theme_option['name'] ), esc_url( $theme_option['url'] ) ); } ?>
'custom_background_colour', 'name' => __( 'Background', 'table-of-contents-plus' ), 'value' => $this->options['custom_background_colour'], ], [ 'id' => 'custom_border_colour', 'name' => __( 'Border', 'table-of-contents-plus' ), 'value' => $this->options['custom_border_colour'], ], [ 'id' => 'custom_title_colour', 'name' => __( 'Title', 'table-of-contents-plus' ), 'value' => $this->options['custom_title_colour'], ], [ 'id' => 'custom_links_colour', 'name' => __( 'Links', 'table-of-contents-plus' ), 'value' => $this->options['custom_links_colour'], ], [ 'id' => 'custom_links_hover_colour', 'name' => __( 'Links (hover)', 'table-of-contents-plus' ), 'value' => $this->options['custom_links_hover_colour'], ], [ 'id' => 'custom_links_visited_colour', 'name' => __( 'Links (visited)', 'table-of-contents-plus' ), 'value' => $this->options['custom_links_visited_colour'], ], ]; foreach ( $custom_theme_props as $custom_theme_prop ) { printf( '' . '' . '' . '', esc_attr( $custom_theme_prop['id'] ), esc_html( $custom_theme_prop['name'] ), esc_attr( $custom_theme_prop['value'] ), esc_url( TOC_PLUGIN_PATH ) ); } ?>

# */ echo wp_kses_post( sprintf( __( "Leaving the value as %s will inherit your theme's styles", 'table-of-contents-plus' ), '#' ) ); ?>

()

options['lowercase'] ) echo ' checked="checked"'; ?> />
options['hyphenate'] ) echo ' checked="checked"'; ?> />
options['include_homepage'] ) echo ' checked="checked"'; ?> />
options['exclude_css'] ) echo ' checked="checked"'; ?> />
options['bullet_spacing'] ) echo ' checked="checked"'; ?> />

options['heading_levels'], true ) ) { echo ' checked="checked"'; } echo '>
'; } ?>


  • Fruit* ignore headings starting with "Fruit"', 'table-of-contents-plus' ) ); ?>
  • *Fruit Diet* ignore headings with "Fruit Diet" somewhere in the heading', 'table-of-contents-plus' ) ); ?>
  • Apple Tree|Oranges|Yellow Bananas ignore headings that are exactly "Apple Tree", "Oranges" or "Yellow Bananas"', 'table-of-contents-plus' ) ); ?>
px


[toc] */ echo wp_kses_post( sprintf( __( 'If you would like to fully customise the position of the table of contents, you can use the %s shortcode by placing it at the desired position of your post, page or custom post type. This method allows you to generate the table of contents despite having auto insertion disabled for its content type. Please visit the help tab for further information about this shortcode.', 'table-of-contents-plus' ), '[toc]' ) ); ?>

[sitemap] */ echo wp_kses_post( sprintf( __( 'At its simplest, placing %s into a page will automatically create a sitemap of all pages and categories. This also works in a text widget.', 'table-of-contents-plus' ), '[sitemap]' ) ); ?>

options['sitemap_show_page_listing'] ) echo ' checked="checked"'; ?> />
options['sitemap_show_category_listing'] ) echo ' checked="checked"'; ?> />

()

[sitemap_pages] [sitemap_categories] */ echo wp_kses_post( sprintf( __( 'lets you print out a listing of only pages. Similarly %s can be used to print out a category listing. They both can accept a number of attributes so visit the help tab for more information.', 'table-of-contents-plus' ), '[sitemap_categories]' ) ); ?>

  1. [sitemap_categories no_label="true"]
  2. [sitemap_pages heading="6" label="This is an awesome listing" exclude="1,15"] This is an awesome listing on a page listing excluding pages with IDs 1 and 15', 'table-of-contents-plus' ) ); ?>

options['exclude_css'] ) { if ( TOC_THEME_CUSTOM === $this->options['theme'] || 'Auto' !== $this->options['width'] ) { $css .= 'div#toc_container {'; if ( TOC_THEME_CUSTOM === $this->options['theme'] ) { $css .= 'background: ' . $this->options['custom_background_colour'] . ';border: 1px solid ' . $this->options['custom_border_colour'] . ';'; } if ( 'Auto' !== $this->options['width'] ) { $css .= 'width: '; if ( 'User defined' !== $this->options['width'] ) { $css .= $this->options['width']; } else { $css .= $this->options['width_custom'] . $this->options['width_custom_units']; } $css .= ';'; } $css .= '}'; } if ( '95%' !== $this->options['font_size'] . $this->options['font_size_units'] ) { $css .= 'div#toc_container ul li {font-size: ' . $this->options['font_size'] . $this->options['font_size_units'] . ';}'; } if ( TOC_THEME_CUSTOM === $this->options['theme'] ) { if ( TOC_DEFAULT_TITLE_COLOUR !== $this->options['custom_title_colour'] ) { $css .= 'div#toc_container p.toc_title {color: ' . $this->options['custom_title_colour'] . ';}'; } if ( TOC_DEFAULT_LINKS_COLOUR !== $this->options['custom_links_colour'] ) { $css .= 'div#toc_container p.toc_title a,div#toc_container ul.toc_list a {color: ' . $this->options['custom_links_colour'] . ';}'; } if ( TOC_DEFAULT_LINKS_HOVER_COLOUR !== $this->options['custom_links_hover_colour'] ) { $css .= 'div#toc_container p.toc_title a:hover,div#toc_container ul.toc_list a:hover {color: ' . $this->options['custom_links_hover_colour'] . ';}'; } if ( TOC_DEFAULT_LINKS_HOVER_COLOUR !== $this->options['custom_links_hover_colour'] ) { $css .= 'div#toc_container p.toc_title a:hover,div#toc_container ul.toc_list a:hover {color: ' . $this->options['custom_links_hover_colour'] . ';}'; } if ( TOC_DEFAULT_LINKS_VISITED_COLOUR !== $this->options['custom_links_visited_colour'] ) { $css .= 'div#toc_container p.toc_title a:visited,div#toc_container ul.toc_list a:visited {color: ' . $this->options['custom_links_visited_colour'] . ';}'; } } } return $css; } /** * Returns a clean url to be used as the destination anchor target */ private function url_anchor_target( $title ) { $return = false; if ( $title ) { $return = trim( wp_strip_all_tags( $title ) ); // convert accented characters to ASCII $return = remove_accents( $return ); // replace newlines with spaces (eg when headings are split over multiple lines) $return = str_replace( [ "\r", "\n", "\n\r", "\r\n" ], ' ', $return ); // remove & $return = str_replace( '&', '', $return ); // remove non alphanumeric chars $return = preg_replace( '/[^a-zA-Z0-9 \-_]*/', '', $return ); // convert spaces to _ $return = str_replace( [ ' ', ' ' ], '_', $return ); // remove trailing - and _ $return = rtrim( $return, '-_' ); // lowercase everything? if ( $this->options['lowercase'] ) { $return = strtolower( $return ); } // if blank, then prepend with the fragment prefix // blank anchors normally appear on sites that don't use the latin charset if ( ! $return ) { $return = ( $this->options['fragment_prefix'] ) ? $this->options['fragment_prefix'] : '_'; } // hyphenate? if ( $this->options['hyphenate'] ) { $return = str_replace( '_', '-', $return ); $return = str_replace( '--', '-', $return ); } } if ( array_key_exists( $return, $this->collision_collector ) ) { $this->collision_collector[ $return ]++; $return .= '-' . $this->collision_collector[ $return ]; } else { $this->collision_collector[ $return ] = 1; } return apply_filters( 'toc_url_anchor_target', $return ); } private function build_hierarchy( &$matches ) { $current_depth = 100; // headings can't be larger than h6 but 100 as a default to be sure $html = ''; $numbered_items = []; $numbered_items_min = null; $count_matches = count( $matches ); // reset the internal collision collection $this->collision_collector = []; // find the minimum heading to establish our baseline for ( $i = 0; $i < $count_matches; $i++ ) { if ( $current_depth > $matches[ $i ][2] ) { $current_depth = (int) $matches[ $i ][2]; } } $numbered_items[ $current_depth ] = 0; $numbered_items_min = $current_depth; for ( $i = 0; $i < $count_matches; $i++ ) { if ( $current_depth === (int) $matches[ $i ][2] ) { $html .= '
  • '; } // start lists if ( $current_depth !== (int) $matches[ $i ][2] ) { for ( $current_depth; $current_depth < (int) $matches[ $i ][2]; $current_depth++ ) { $numbered_items[ $current_depth + 1 ] = 0; $html .= ''; $numbered_items[ $current_depth ] = 0; } } if ( (int) @$matches[ $i + 1 ][2] === $current_depth ) { $html .= '
  • '; } } 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 ) { $count_find = count( $find ); // 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 before $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 */ public function extract_headings( &$find, &$replace, $content = '' ) { $matches = []; $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 = []; if ( is_array( $find ) && is_array( $replace ) && $content ) { // filter the content $content = apply_filters( 'toc_extract_headings', $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 = []; $count_matches = count( $matches ); for ( $i = 0; $i < $count_matches; $i++ ) { if ( in_array( (int) $matches[ $i ][2], $this->options['heading_levels'], true ) ) { $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'] ); $count_excluded_headings = count( $excluded_headings ); 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( [ '*' ], [ '.*' ], trim( $excluded_headings[ $j ] ) ); } $new_matches = []; $count_matches = count( $matches ); for ( $i = 0; $i < $count_matches; $i++ ) { $found = false; $count_excluded_headings = count( $excluded_headings ); for ( $j = 0; $j < $count_excluded_headings; $j++ ) { if ( @preg_match( '/^' . $excluded_headings[ $j ] . '$/imU', wp_strip_all_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 = []; $count_matches = count( $matches ); for ( $i = 0; $i < $count_matches; $i++ ) { if ( trim( wp_strip_all_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'] ) { $count_matches = count( $matches ); 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( [ $matches[ $i ][1], // start of heading '', // end of heading ], [ $matches[ $i ][1] . '', '', ], $matches[ $i ][0] ); // assemble flat list if ( ! $this->options['show_heirarchy'] ) { $items .= '
  • '; if ( $this->options['ordered_list'] ) { $items .= count( $replace ) . ' '; } $items .= wp_strip_all_tags( $matches[ $i ][0] ) . '
  • '; } } // 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 on REST Requests if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { return false; } // 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 ( false !== $shortcode_used ) { // 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'], true ) && $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; } } } public function the_content( $content ) { global $post; $items = ''; $css_classes = ''; $anchor = ''; $find = []; $replace = []; $custom_toc_position = strpos( $content, '' ); 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'], true ) ) ) { $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 = '
    '; if ( $this->options['show_heading_text'] ) { $toc_title = htmlentities( $this->options['heading_text'], ENT_COMPAT, 'UTF-8' ); if ( false !== strpos( $toc_title, '%PAGE_TITLE%' ) ) { $toc_title = str_replace( '%PAGE_TITLE%', get_the_title(), $toc_title ); } if ( false !== strpos( $toc_title, '%PAGE_NAME%' ) ) { $toc_title = str_replace( '%PAGE_NAME%', get_the_title(), $toc_title ); } $html .= '

    ' . $toc_title . '

    '; } $html .= '
    ' . "\n"; if ( false !== $custom_toc_position ) { $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;