` into the post content. * @see ezTOC_Post::extractPages() * @since 2.0 * @var array */ private $pages = array(); /** * The user defined heading levels to be included in the TOC. * @see ezTOC_Post::getHeadingLevels() * @since 2.0 * @var array */ private $headingLevels = array(); /** * Array of nodes that are excluded by class/id selector. * @since 2.0 * @var string[] */ private $excludedNodes = array(); /** * Keeps a track of used anchors for collision detecting. * @see ezTOC_Post::generateHeadingIDFromTitle() * @since 2.0 * @var array */ private $collision_collector = array(); /** * @var bool */ private $hasTOCItems = false; /** * ezTOC_Post constructor. * * @since 2.0 * * @param WP_Post $post * @param bool $apply_content_filter Whether or not to apply the `the_content` filter on the post content. */ public function __construct( WP_Post $post, $apply_content_filter = true ) { $this->post = $post; $this->permalink = get_permalink( $post ); $this->queriedObjectID = get_queried_object_id(); $apply_content_filter = $this->apply_filter_status( $apply_content_filter ); if ( $apply_content_filter ) { $this->applyContentFilter()->process(); } else { $this->process(); } } /** * apply_filter_status function * * @since 2.0.51 * @access private * @param bool $apply_content_filter * @return bool */ private function apply_filter_status( $apply_content_filter ) { /** * ez_toc_apply_filter_status Apply filter * for any plugin which conflict * in easy toc plugin * @since 2.0.51 */ $plugins = apply_filters( 'ez_toc_apply_filter_status', array( 'booster-extension/booster-extension.php', 'divi-bodycommerce/divi-bodyshop-woocommerce.php', 'social-pug/index.php', 'fusion-builder/fusion-builder.php', 'modern-footnotes/modern-footnotes.php', 'yet-another-stars-rating-premium/yet-another-stars-rating.php' ) ); foreach ( $plugins as $value ) { if ( in_array( $value, apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) { $apply_content_filter = false; } } $apply_content_filter = apply_filters('ez_toc_apply_filter_status_manually', $apply_content_filter); return $apply_content_filter; } /** * @access public * @since 2.0 * * @param $id * * @return ezTOC_Post|null */ public static function get( $id ) { $post = get_post( $id ); if ( ! $post instanceof WP_Post ) { return null; } return new static( $post ); } /** * Process post content for headings. * * This must be run after object init or after @see ezTOC_Post::applyContentFilter(). * * @since 2.0 * * @return static */ private function process() { $this->processPages(); return $this; } /** * Apply `the_content` filter to the post content. * * @since 2.0 * * @return static */ private function applyContentFilter() { /* * Parses dynamic blocks out of post_content and re-renders them for gutenberg blocks. */ if(function_exists('do_blocks')){ $this->post->post_content = do_blocks($this->post->post_content); }else{ $this->post->post_content = $this->post->post_content; } if( defined('EASY_TOC_AMP_VERSION') && function_exists('ampforwp_is_amp_endpoint') && ampforwp_is_amp_endpoint() ){ $ampforwp_pagebuilder_enable = get_post_meta(get_the_ID(),'ampforwp_page_builder_enable', true); if($ampforwp_pagebuilder_enable=='yes' && function_exists('ampforwp_eztoc_PageBuilder_content')){ $this->post->post_content = ampforwp_eztoc_PageBuilder_content(); } } add_filter( 'strip_shortcodes_tagnames', array( __CLASS__, 'stripShortcodes' ), 10, 2 ); /* * Ensure the ezTOC content filter is not applied when running `the_content` filter. */ remove_filter( 'the_content', array( 'ezTOC', 'the_content' ), 100 ); $this->post->post_content = apply_filters( 'the_content', strip_shortcodes( $this->post->post_content ) ); add_filter( 'the_content', array( 'ezTOC', 'the_content' ), 100 ); // increased priority to fix other plugin filter overwriting our changes remove_filter( 'strip_shortcodes_tagnames', array( __CLASS__, 'stripShortcodes' ) ); return $this; } /** * Callback for the `strip_shortcodes_tagnames` filter. * * Strip the shortcodes so their content is no processed for headings. * * @see ezTOC_Post::applyContentFilter() * * @since 2.0 * * @param array $tags_to_remove Array of shortcode tags to remove. * @param string $content Content shortcodes are being removed from. * * @return array */ public static function stripShortcodes( $tags_to_remove, $content ) { /* * Ensure the ezTOC shortcodes are not processed when applying `the_content` filter * otherwise an infinite loop may occur. */ $tags_to_remove = apply_filters( 'ez_toc_strip_shortcodes_tagnames', array( 'ez-toc', apply_filters( 'ez_toc_shortcode', 'toc' ), ), $content ); return $tags_to_remove; } /** * This is a work around for theme's and plugins * which break the WordPress global $wp_query var by unsetting it * or overwriting it which breaks the method call * that `get_query_var()` uses to return the query variable. * * @access protected * @since 2.0 * * @return int */ protected function getCurrentPage() { global $wp_query; // Check to see if the global `$wp_query` var is an instance of WP_Query and that the get() method is callable. // If it is then when can simply use the get_query_var() function. if ( $wp_query instanceof WP_Query && is_callable( array( $wp_query, 'get' ) ) ) { $page = get_query_var( 'page', 1 ); return 1 > $page ? 1 : $page; // If a theme or plugin broke the global `$wp_query` var, check to see if the $var was parsed and saved in $GLOBALS['wp_query']->query_vars. } elseif ( isset( $GLOBALS['wp_query']->query_vars[ 'page' ] ) ) { return $GLOBALS['wp_query']->query_vars[ 'page' ]; // We should not reach this, but if we do, lets check the original parsed query vars in $GLOBALS['wp_the_query']->query_vars. } elseif ( isset( $GLOBALS['wp_the_query']->query_vars[ 'page' ] ) ) { return $GLOBALS['wp_the_query']->query_vars[ 'page' ]; // Ok, if all else fails, check the $_REQUEST super global. } elseif ( isset( $_REQUEST[ 'page' ] ) ) { return $_REQUEST[ 'page' ]; } // Finally, return the $default if it was supplied. return 1; } /** * Get the number of page the post has. * * @access protected * @since 2.0 * * @return int */ protected function getNumberOfPages() { return count( $this->pages ); } /** * Whether or not the post has multiple pages. * * @access protected * @since 2.0 * * @return bool */ protected function isMultipage() { return 1 < $this->getNumberOfPages(); } /** * Parse the post content and headings. * * @access private * @since 2.0 */ private function processPages() { $content = apply_filters( 'ez_toc_modify_process_page_content', $this->post->post_content ); // Fix for wordpress category pages showing wrong toc if they have description if(is_category()){ $cat_from_query=get_query_var( 'cat', null ); if($cat_from_query){ $category = get_category($cat_from_query); if(is_object($category) && property_exists($category,'description') && !empty($category->description)){ $content = $category->description; } } } if(is_tax() || is_tag()){ global $wp_query; $tax = $wp_query->get_queried_object(); if(is_object($tax)){ $content = apply_filters('ez_toc_modify_taxonomy_content',$tax->description,$tax->term_id); } } if(function_exists('is_product_category') && is_product_category()){ $term_object = get_queried_object(); if(!empty($term_object->description)){ $content = $term_object->description; } } if ( in_array( 'js_composer_salient/js_composer.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) { $eztoc_post_id=get_the_ID(); $eztoc_post_meta = get_option( 'ez-toc-post-meta-content',false); if(!empty($eztoc_post_meta) && !empty($eztoc_post_id) && isset($eztoc_post_meta[$eztoc_post_id])){ if ( empty( $content ) ) { $content = $eztoc_post_meta[$eztoc_post_id]; } else { $content .= $eztoc_post_meta[$eztoc_post_id]; } } } else if ( ( in_array( 'divi-machine/divi-machine.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) || 'Fortunato Pro' == apply_filters( 'current_theme', get_option( 'current_theme' ) ) ) && false != get_option( 'ez-toc-post-content-core-level' ) ) { $content = get_option( 'ez-toc-post-content-core-level' ); } else { } $pages = array(); $split = preg_split( '//msuU', $content ); $page = $first_page = 1; $totalHeadings = []; if ( is_array( $split ) ) { foreach ( $split as $content ) { $this->extractExcludedNodes( $page, $content ); $totalHeadings[] = array( 'headings' => $this->extractHeadings( $content, $page ), 'content' => $content, ); $page++; } } $pages[$first_page] = $totalHeadings; $this->pages = $pages; } /** * Get the post's parse content and headings. * * @access public * @since 2.0 * * @return array */ public function getPages() { return $this->pages; } /** * Extract nodes that heading are to be excluded. * * @since 2.0 * * @param int $page * @param string $content */ private function extractExcludedNodes( $page, $content ) { if ( ! class_exists( 'TagFilter' ) ) { if(phpversion() <= 5.6) require_once( EZ_TOC_PATH . '/includes/vendor/ultimate-web-scraper/tag_filter56.php' ); else require_once( EZ_TOC_PATH . '/includes/vendor/ultimate-web-scraper/tag_filter.php' ); } $tagFilterOptions = TagFilter::GetHTMLOptions(); // Set custom TagFilter options. $tagFilterOptions['charset'] = get_option( 'blog_charset' ); $html = TagFilter::Explode( $content, $tagFilterOptions ); /** * @since 2.0 * * @param $selectors array Array of classes/id selector to exclude from TOC. * @param $content string Post content. */ $selectors = apply_filters( 'ez_toc_exclude_by_selector', array( '.ez-toc-exclude-headings' ), $content ); $nodes = $html->Find( implode( ',', $selectors ) ); if(isset($nodes['ids'])){ foreach ( $nodes['ids'] as $id ) { array_push( $this->excludedNodes, $html->Implode( $id, $tagFilterOptions ) ); } } /** * TagFilter::Implode() writes br tags as `
` while WP normalizes to `
`. * Normalize `$eligibleContent` to match WP. * * @see wpautop() */ } /** * Extract the posts content for headings. * * @access private * @since 2.0 * * @param string $content * * @return array */ private function extractHeadings( $content, $page = 1 ) { $matches = array(); if ( in_array( 'elementor/elementor.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) || in_array( 'divi-machine/divi-machine.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) || 'Fortunato Pro' == apply_filters( 'current_theme', get_option( 'current_theme' ) ) ) { $content = apply_filters( 'ez_toc_extract_headings_content', $content ); } else { $content = apply_filters( 'ez_toc_extract_headings_content', wptexturize( $content ) ); } /** * Lasso Product Compatibility * @since 2.0.46 */ $regEx = apply_filters( 'ez_toc_regex_filteration', '/(]*>)(.*)<\/h\2>/msuU' ); // get all headings // the html spec allows for a maximum of 6 heading depths if ( preg_match_all( $regEx, $content, $matches, PREG_SET_ORDER ) ) { $minimum = absint( ezTOC_Option::get( 'start' ) ); $this->removeHeadingsFromExcludedNodes( $matches ); $this->removeHeadings( $matches ); $this->excludeHeadings( $matches ); $this->removeEmptyHeadings( $matches ); if ( count( $matches ) >= $minimum ) { $this->alternateHeadings( $matches ); $this->headingIDs( $matches ); $this->addPage( $matches, $page ); $this->hasTOCItems = true; } else { return array(); } } return array_values( $matches ); // Rest the array index. } /** * addPage function * * @access private * @since 2.0.50 * @param array|null|false $matches * @param int $page * @return void */ private function addPage( &$matches, $page ) { foreach ( $matches as $i => $match ) { $matches[ $i ][ 'page' ] = $page; } return $matches; } /** * Whether or not the string is in one of the excluded nodes. * * @since 2.0 * * @param string $string * * @return bool */ private function inExcludedNode( $string ) { foreach ( $this->excludedNodes as $node ) { if ( empty( $node ) || empty( $string ) ) { return false; } if ( false !== strpos( $node, $string ) ) { return true; } } return false; } /** * Remove headings that are in excluded nodes. * * @since 2.0 * * @param array $matches * * @return array */ private function removeHeadingsFromExcludedNodes( &$matches ) { foreach ( $matches as $i => $match ) { if ( $this->inExcludedNode( "{$match[3]}" ) ) { unset( $matches[ $i ] ); } } return $matches; } /** * Get the heading levels to be included in the TOC. * * @access private * @since 2.0 * * @return array */ private function getHeadingLevels() { $levels = get_post_meta( $this->post->ID, '_ez-toc-heading-levels', true ); if ( ! is_array( $levels ) ) { $levels = array(); } if ( empty( $levels ) ) { $levels = ezTOC_Option::get( 'heading_levels', array() ); } $this->headingLevels = $levels; return $this->headingLevels; } /** * Remove the heading levels as defined by user settings from the TOC heading matches. * * @see ezTOC_Post::extractHeadings() * * @access private * @since 2.0 * * @param array $matches The heading from the post content extracted with preg_match_all(). * * @return array */ private function removeHeadings( &$matches ) { $levels = $this->getHeadingLevels(); if ( count( $levels ) != 6 ) { $new_matches = array(); foreach ( $matches as $i => $match ) { if ( in_array( $matches[ $i ][2], $levels ) ) { $new_matches[ $i ] = $matches[ $i ]; } } $matches = $new_matches; } return $matches; } /** * Exclude the heading, by title, as defined by the user settings from the TOC matches. * * @see ezTOC_Post::extractHeadings() * * @access private * @since 2.0 * * @param array $matches The headings from the post content extracted with preg_match_all(). * * @return array */ private function excludeHeadings( &$matches ) { $exclude = get_post_meta( $this->post->ID, '_ez-toc-exclude', true ); if ( empty( $exclude ) ) { $exclude = ezTOC_Option::get( 'exclude' ); } if ( $exclude ) { $excluded_headings = explode( '|', $exclude ); $excluded_count = count( $excluded_headings ); if ( $excluded_count > 0 ) { for ( $j = 0; $j < $excluded_count; $j++ ) { $excluded_headings[ $j ] = preg_quote( $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(); foreach ( $matches as $i => $match ) { $found = false; $against = html_entity_decode( ( in_array( 'divi-machine/divi-machine.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) || 'Fortunato Pro' == apply_filters( 'current_theme', get_option( 'current_theme' ) ) ) ? strip_tags( str_replace( array( "\r", "\n" ), ' ', $matches[ $i ][0] ) ) : wptexturize(strip_tags( str_replace( array( "\r", "\n" ), ' ', $matches[ $i ][0] ) ) ), ENT_NOQUOTES, get_option( 'blog_charset' ) ); for ( $j = 0; $j < $excluded_count; $j++ ) { // Since WP manipulates the post content it is required that the excluded header and // the actual header be manipulated similarly so a match can be made. $pattern = html_entity_decode( ( in_array( 'divi-machine/divi-machine.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) || 'Fortunato Pro' == apply_filters( 'current_theme', get_option( 'current_theme' ) ) ) ? $excluded_headings[ $j ] : wptexturize($excluded_headings[ $j ]), ENT_NOQUOTES, get_option( 'blog_charset' ) ); if ( @preg_match( '/^' . $pattern . '$/imU', $against ) ) { $found = true; break; } } if ( ! $found ) { $new_matches[ $i ] = $matches[ $i ]; } } $matches = $new_matches; } } return $matches; } /** * Return the alternate headings added by the user, saved in the post meta. * * The result is an associative array where the `key` is the original post heading * and the `value` is the alternate heading. * * @access private * @since 2.0 * * @return array */ private function getAlternateHeadings() { $alternates = array(); $value = get_post_meta( $this->post->ID, '_ez-toc-alttext', true ); if ( $value ) { $headings = preg_split( '/\r\n|[\r\n]/', $value ); $count = count( $headings ); if ( $headings ) { for ( $k = 0; $k < $count; $k++ ) { $heading = explode( '|', $headings[ $k ] ); /** * @link https://wordpress.org/support/topic/undefined-offset-1-home-blog-public-wp-content-plugins-easy-table-of-contents/ */ if ( ! is_array( $heading) || ! array_key_exists( 0, $heading ) || ! array_key_exists( 1, $heading ) ) { continue; } if ( 0 < strlen( $heading[0] ) && 0 < strlen( $heading[1] ) ) { $alternates[ $heading[0] ] = $heading[1]; } } } } return $alternates; } /** * Add the alternate headings to the array. * * @see ezTOC_Post::extractHeadings() * * @access private * @since 2.0 * * @param array $matches The heading from the post content extracted with preg_match_all(). * * @return array */ private function alternateHeadings( &$matches ) { $alt_headings = $this->getAlternateHeadings(); if ( 0 < count( $alt_headings ) ) { foreach ( $matches as $i => $match ) { foreach ( $alt_headings as $original_heading => $alt_heading ) { // Cleanup and texturize so alt heading can match heading in post content. if ( in_array( 'divi-machine/divi-machine.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) || 'Fortunato Pro' == apply_filters( 'current_theme', get_option( 'current_theme' ) ) ) { $original_heading = trim( $original_heading ); }else { $original_heading = wptexturize( trim( $original_heading ) ); } // Deal with special characters such as non-breakable space. $original_heading = str_replace( array( "\xc2\xa0" ), array( ' ' ), $original_heading ); // Escape for regular expression. $original_heading = preg_quote( $original_heading ); // Escape for regular expression some other characters: http://www.php.net/manual/en/regexp.reference.meta.php $original_heading = str_replace( array( '\*', '/', '%' ), array( '.*', '\/', '\%' ), $original_heading ); // Cleanup subject so alt heading can match heading in post content. $subject = strip_tags( $matches[ $i ][0] ); // Deal with special characters such as non-breakable space. $subject = str_replace( array( "\xc2\xa0" ), array( ' ' ), $subject ); if ( @preg_match( '/^' . $original_heading . '$/imU', $subject ) ) { $matches[ $i ]['alternate'] = $alt_heading; } } } } return $matches; } /** * Add the heading `id` to the array. * * @see ezTOC_Post::extractHeadings() * * @access private * @since 2.0 * * @param array $matches The heading from the post content extracted with preg_match_all(). * * @return mixed */ private function headingIDs( &$matches ) { foreach ( $matches as $i => $match ) { $matches[ $i ]['id'] = $this->generateHeadingIDFromTitle( $matches[ $i ][0] ); } return $matches; } /** * Create unique heading ID from heading string. * * @access private * @since 2.0 * * @param string $heading * * @return bool|string */ private function generateHeadingIDFromTitle( $heading ) { $return = false; if ( $heading ) { $heading = apply_filters( 'ez_toc_url_anchor_target_before', $heading ); // WP entity encodes the post content. $return = html_entity_decode( $heading, ENT_QUOTES, get_option( 'blog_charset' ) ); $return = br2( $return, ' ' ); $return = trim( strip_tags( $return ) ); // Convert accented characters to ASCII. $return = remove_accents( $return ); // replace newlines with spaces (eg when headings are split over multiple lines) $return = str_replace( array( "\r", "\n", "\n\r", "\r\n" ), ' ', $return ); // Remove `&` and ` ` NOTE: in order to strip "hidden" ` `, // title needs to be converted to HTML entities. // @link https://stackoverflow.com/a/21801444/5351316 $return = htmlentities2( $return ); $return = str_replace( array( '&', ' '), ' ', $return ); $return = str_replace( array( '­' ),'', $return ); // removed silent hypen $return = html_entity_decode( $return, ENT_QUOTES, get_option( 'blog_charset' ) ); // remove non alphanumeric chars $return = preg_replace( '/[\x00-\x1F\x7F]*/u', '', $return ); //for procesing shortcode in headings $return = apply_filters('ez_toc_table_heading_title_anchor',$return); // Reserved Characters. // * ' ( ) ; : @ & = + $ , / ? # [ ] $return = str_replace( array( '*', '\'', '(', ')', ';', '@', '&', '=', '+', '$', ',', '/', '?', '#', '[', ']' ), '', $return ); // Unsafe Characters. // % { } | \ ^ ~ [ ] ` $return = str_replace( array( '%', '{', '}', '|', '\\', '^', '~', '[', ']', '`' ), '', $return ); // Special Characters. // $ - _ . + ! * ' ( ) , // Special case for Apostrophes (’) which is causing TOC link to break in Block themes and CM Tooltip Glossary plugin #556 $return = str_replace( array( '$', '.', '+', '!', '*', '\'', '(', ')', ',', '’' ), '', $return ); // Dashes // Special Characters. // - (minus) - (dash) – (en dash) — (em dash) $return = str_replace( array( '-', '-', '–', '—' ), '-', $return ); // Curley quotes. // ‘ (curly single open quote) ’ (curly single close quote) “ (curly double open quote) ” (curly double close quote) $return = str_replace( array( '‘', '’', '“', '”' ), '', $return ); // AMP/Caching plugins seems to break URL with the following characters, so lets replace them. $return = str_replace( array( ':' ), '_', $return ); // Convert space characters to an `_` (underscore). $return = preg_replace( '/\s+/', '_', $return ); // Replace multiple `-` (hyphen) with a single `-` (hyphen). $return = preg_replace( '/-+/', '-', $return ); // Replace multiple `_` (underscore) with a single `_` (underscore). $return = preg_replace( '/_+/', '_', $return ); // Remove trailing `-` (hyphen) and `_` (underscore). $return = rtrim( $return, '-_' ); /* * Encode URI based on ECMA-262. * * Only required to support the jQuery smoothScroll library. * * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI#Description * @link https://stackoverflow.com/a/19858404/5351316 */ $return = preg_replace_callback( "{[^0-9a-z_.!~*'();,/?:@&=+$#-]}i", function( $m ) { return sprintf( '%%%02X', ord( $m[0] ) ); }, $return ); // lowercase everything? if ( ezTOC_Option::get( '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 //@since 2.0.59 if ( !$return || true == ezTOC_Option::get( 'all_fragment_prefix' ) ) { $return = ( ezTOC_Option::get( 'fragment_prefix' ) ) ? ezTOC_Option::get( 'fragment_prefix' ) : '_'; } // hyphenate? if ( ezTOC_Option::get( 'hyphenate' ) ) { $return = str_replace( '_', '-', $return ); $return = preg_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( 'ez_toc_url_anchor_target', $return, $heading ); } /** * Remove any empty headings from the TOC. * * @access private * @since 2.0 * * @param array $matches The heading from the post content extracted with preg_match_all(). * * @return array */ private function removeEmptyHeadings( &$matches ) { $new_matches = array(); foreach ( $matches as $i => $match ) { if ( trim( strip_tags( $matches[ $i ][0] ) ) != false ) { $new_matches[ $i ] = $matches[ $i ]; } } $matches = $new_matches; return $matches; } /** * Whether or not the post has TOC items. * * @see ezTOC_Post::extractHeadings() * * @access public * @since 2.0 * * @return bool */ public function hasTOCItems() { return $this->hasTOCItems; } /** * Get the headings of the current page of the post. * * @access public * @since 2.0 * * @param int|null $page * * @return array */ public function getHeadings( $page = null ) { $headings = array(); if ( is_null( $page ) ) { $page = $this->getCurrentPage(); } if ( !empty( $this->pages ) || isset( $this->pages[ $page ] ) ) { $matches = $this->getHeadingsfromPageContents( $page ); foreach ( $matches as $i => $match ) { $headings[] = str_replace( array( $matches[ $i ][1], // start of heading '' // end of heading ), array( '>', '' ), apply_filters('ez_toc_content_heading_title',$matches[ $i ][0]) ); } } return $headings; } /** * Get the heading title id. * * @access public * @since 2.0.58 * * @param int|null $page * * @return array */ public function getTocTitleId( $page = null ) { $nav_data = array(); if ( is_null( $page ) ) { $page = $this->getCurrentPage(); } if ( !empty( $this->pages ) || isset( $this->pages[ $page ] ) ) { $matches = $this->getHeadingsfromPageContents( $page ); foreach ( $matches as $i => $match ) { $nav_data[$i]['title'] = strip_tags( $matches[ $i ][0] ); $nav_data[ $i ]['id'] = strtolower(str_replace( '_', '-', $matches[ $i ]['id'] )); } } return $nav_data; } /** * Get the heading with in page anchors of the current page of the post. * * @access public * @since 2.0 * * @param int|null $page * * @return array */ public function getHeadingsWithAnchors( $page = null ) { $headings = array(); if ( is_null( $page ) ) { $page = $this->getCurrentPage(); } if ( !empty( $this->pages ) || isset( $this->pages[ $page ] ) ) { $matches = $this->getHeadingsfromPageContents( $page ); foreach ( $matches as $i => $match ) { $anchor = $matches[ $i ]['id']; $headings[] = str_replace( array( $matches[ $i ][1], // start of heading '' // end of heading ), array( '>', '' ), apply_filters('ez_toc_content_heading_title_anchor',$matches[ $i ][0]) ); } } return $headings; } /** * getHeadingsfromPageContents function * * @access private * @since 2.0.50 * @param int $page * @return array|null */ private function getHeadingsfromPageContents( $page = 1 ) { $headings = []; $first_page = 1; foreach( $this->pages[ $first_page ] as $attributes ) { if( isset($attributes['headings'][0]['page']) && $page == $attributes['headings'][0]['page'] ) { foreach( $attributes['headings'] as $heading ) { array_push( $headings, $heading ); } } } return $headings; } /** * createTOCParent function * * @param string $prefix * @return void|mixed|string|null */ private function createTOCParent( $prefix = "ez-toc", $toc_more = array() ) { $html = ''; $first_page = 1; $headings = array(); foreach ( $this->pages[ $first_page ] as $attribute ) { $headings = array_merge( $headings, $attribute[ 'headings' ] ); } if( !empty( $headings ) ) { $html .= $this->createTOC( $first_page, $headings, $prefix, $toc_more ); } return $html; } /** * Get the post TOC list. * * @access public * @param string $prefix * @since 2.0 * * @return string */ public function getTOCList($prefix = "ez-toc", $options = []) { $html = ''; $toc_more = isset($options['view_more']) ? array( 'view_more' => $options['view_more'] ) : array(); if(isset($options['hierarchy'])){ $toc_more['hierarchy'] = true; }elseif(isset($options['no_hierarchy'])){ $toc_more['no_hierarchy'] = true; } if(isset($options['collapse_hd'])){ $toc_more['collapse_hd'] = true; }elseif(isset($options['no_collapse_hd'])){ $toc_more['no_collapse_hd'] = true; } if ( $this->hasTOCItems ) { $html = $this->createTOCParent($prefix, $toc_more); $visiblityClass = ''; if( ezTOC_Option::get( 'visibility_hide_by_default' ) && 'js' == ezTOC_Option::get( 'toc_loading' ) && ezTOC_Option::get( 'visibility' )) { $visiblityClass = "eztoc-toggle-hide-by-default"; } if( get_post_meta( $this->post->ID, '_ez-toc-visibility_hide_by_default', true ) && 'js' == ezTOC_Option::get( 'toc_loading' ) && ezTOC_Option::get( 'visibility' )) { $visiblityClass = "eztoc-toggle-hide-by-default"; } if(is_array($options) && key_exists( 'visibility_hide_by_default', $options ) && $options['visibility_hide_by_default'] == true && 'js' == ezTOC_Option::get( 'toc_loading' ) && ezTOC_Option::get( 'visibility' )){ $visiblityClass = "eztoc-toggle-hide-by-default"; }elseif(is_array($options) && key_exists( 'visibility_show_by_default', $options ) && $options['visibility_show_by_default'] == true && 'js' == ezTOC_Option::get( 'toc_loading' ) && ezTOC_Option::get( 'visibility' )){ $visiblityClass = ""; } $html = ""; } return $html; } /** * Get the post Sticky Toggle TOC content block. * * @access public * @return string * @since 2.0.32 * */ public function getStickyToggleTOC() { $classSticky = array( 'ez-toc-sticky-v' . str_replace( '.', '_', ezTOC::VERSION ) ); $htmlSticky = ''; if ( $this->hasTOCItems() ) { $classSticky[] = 'counter-flat'; if( ezTOC_Option::get( 'heading-text-direction', 'ltr' ) == 'ltr' ) { $classSticky[] = 'ez-toc-sticky-toggle-counter'; } if( ezTOC_Option::get( 'heading-text-direction', 'ltr' ) == 'rtl' ) { $classSticky[] = 'ez-toc-sticky-toggle-counter-rtl'; } $classSticky = array_filter( $classSticky ); $classSticky = array_map( 'trim', $classSticky ); $classSticky = array_map( 'sanitize_html_class', $classSticky ); $ezTocStickyToggleDirection = 'ez-toc-sticky-toggle-direction'; if ( ezTOC_Option::get( 'show_heading_text' ) ) { $toc_title = apply_filters('ez_toc_sticky_title', ezTOC_Option::get( 'heading_text' )); $toc_title_tag = ezTOC_Option::get( 'heading_text_tag' ); $toc_title_tag = $toc_title_tag?$toc_title_tag:'p'; if ( strpos( $toc_title, '%PAGE_TITLE%' ) !== false ) { $toc_title = str_replace( '%PAGE_TITLE%', get_the_title(), $toc_title ); } if ( strpos( $toc_title, '%PAGE_NAME%' ) !== false ) { $toc_title = str_replace( '%PAGE_NAME%', get_the_title(), $toc_title ); } $htmlSticky .= '
' . PHP_EOL; $htmlSticky .= '<'.esc_attr($toc_title_tag).' class="ez-toc-sticky-title">' . esc_html__( htmlentities( $toc_title, ENT_COMPAT, 'UTF-8' ), 'easy-table-of-contents' ) . '' . PHP_EOL; $htmlSticky .= '' . PHP_EOL; $htmlSticky .= '
' . PHP_EOL; } else { $htmlSticky .= '
' . PHP_EOL; $htmlSticky .= '' . PHP_EOL; $htmlSticky .= '
' . PHP_EOL; } $htmlSticky .= '
' . PHP_EOL; ob_start(); do_action( 'ez_toc_sticky_toggle_before' ); $htmlSticky .= ob_get_clean(); $htmlSticky .= ""; ob_start(); do_action( 'ez_toc_sticky_toggle_after' ); $htmlSticky .= ob_get_clean(); $htmlSticky .= '
' . PHP_EOL; } return $htmlSticky; } /** * Get the post TOC content block. * * @access public * @since 2.0 * * @return string */ public function getTOC($options = []) { $class = array( 'ez-toc-v' . str_replace( '.', '_', ezTOC::VERSION ) ); $html = ''; if ( $this->hasTOCItems() ) { $wrapping_class_add = ""; if(ezTOC_Option::get( 'toc_wrapping' )){ $wrapping_class_add='-text'; } $toc_align = get_post_meta( get_the_ID(), '_ez-toc-alignment', true ); if ( !$toc_align || empty( $toc_align ) || $toc_align == 'none' ) { $toc_align = ezTOC_Option::get( 'wrapping' ); } // wrapping css classes switch ( $toc_align ) { case 'left': $class[] = 'ez-toc-wrap-left'.esc_attr($wrapping_class_add); break; case 'right': $class[] = 'ez-toc-wrap-right'.esc_attr($wrapping_class_add); break; case 'center': $class[] = 'ez-toc-wrap-center'; break; case 'none': default: // do nothing } $show_counter = (isset($options['no_counter']) && $options['no_counter'] == true ) ? false : true; $post_hide_counter = get_post_meta( get_the_ID(), '_ez-toc-hide_counter', true ); if($post_hide_counter){ $show_counter = false; } if( $show_counter ){ $hierarchical = ezTOC_Option::get( 'show_hierarchy' ); if(isset($options['hierarchy'])){ $hierarchical = true; }elseif(isset($options['no_hierarchy'])){ $hierarchical = false; } if ( $hierarchical ) { $class[] = 'counter-hierarchy'; } else { $class[] = 'counter-flat'; } if( ezTOC_Option::get( 'heading-text-direction', 'ltr' ) == 'ltr' ) { $class[] = 'ez-toc-counter'; } if( ezTOC_Option::get( 'heading-text-direction', 'ltr' ) == 'rtl' ) { $class[] = 'ez-toc-counter-rtl'; } } // colour themes switch ( ezTOC_Option::get( 'theme' ) ) { case 'light-blue': $class[] = 'ez-toc-light-blue'; break; case 'white': $class[] = 'ez-toc-white'; break; case 'black': $class[] = 'ez-toc-black'; break; case 'transparent': $class[] = 'ez-toc-transparent'; break; case 'grey': $class[] = 'ez-toc-grey'; break; case 'custom': $class[] = 'ez-toc-custom'; break; } $custom_classes = ezTOC_Option::get( 'css_container_class', '' ); $class[] = 'ez-toc-container-direction'; if ( 0 < strlen( $custom_classes ) ) { $custom_classes = explode( ' ', $custom_classes ); $custom_classes = apply_filters( 'ez_toc_container_class', $custom_classes, $this ); if ( is_array( $custom_classes ) ) { $class = array_merge( $class, $custom_classes ); } } $class = array_filter( $class ); $class = array_map( 'trim', $class ); $class = array_map( 'sanitize_html_class', $class ); $html .= '
' . PHP_EOL; if( ezTOC_Option::get( 'toc_loading' ) == 'js' ){ $html .= $this->get_js_based_toc_heading($options); }else{ $html .= $this->get_css_based_toc_heading($options); } ob_start(); do_action( 'ez_toc_before' ); $html .= ob_get_clean(); $html .= ''; ob_start(); do_action( 'ez_toc_after' ); $html .= ob_get_clean(); $html .= '
' . PHP_EOL; } return $html; } private function get_js_based_toc_heading($options){ $html = ''; $html .= '
' . PHP_EOL; $header_label = ''; $show_header_text = ezTOC_Option::get( 'show_heading_text' ); if(isset($options['label'])){ $show_header_text = true; }elseif(isset($options['no_label'])){ $show_header_text = false; } $read_time = array(); if(isset($options['read_time'])){ $read_time['read_time'] = $options['read_time']; } if ( $show_header_text ) { $toc_title = get_post_meta( get_the_ID(), '_ez-toc-header-label', true ); if ( !$toc_title || empty( $toc_title ) ) { $toc_title = ezTOC_Option::get( 'heading_text' ); } $toc_title_tag = ezTOC_Option::get( 'heading_text_tag' ); $toc_title_tag = $toc_title_tag?$toc_title_tag:'p'; if ( strpos( $toc_title, '%PAGE_TITLE%' ) !== false ) { $toc_title = str_replace( '%PAGE_TITLE%', get_the_title(), $toc_title ); } if ( strpos( $toc_title, '%PAGE_NAME%' ) !== false ) { $toc_title = str_replace( '%PAGE_NAME%', get_the_title(), $toc_title ); } if(isset($options['header_label'])){ $toc_title = $options['header_label']; } $headerTextToggleClass = ''; $headerTextToggleStyle = ''; if ( ezTOC_Option::get( 'visibility_on_header_text' ) ) { $headerTextToggleClass = 'ez-toc-toggle'; $headerTextToggleStyle = 'style="cursor: pointer"'; } $header_label = '<'.esc_attr($toc_title_tag).' class="ez-toc-title ' . $headerTextToggleClass .'" ' . $headerTextToggleStyle . '>' . esc_html__( htmlentities( $toc_title, ENT_COMPAT, 'UTF-8' ), 'easy-table-of-contents' ). '' . PHP_EOL; $html .= $header_label; } $html .= ''; $label_below_html = ''; $show_toggle_view = ezTOC_Option::get( 'visibility' ); if(isset($options['toggle']) && $options['toggle'] == true){ $show_toggle_view = true; }elseif(isset($options['no_toggle']) && $options['no_toggle'] == true){ $show_toggle_view = false; } if ( $show_toggle_view ) { $icon = ezTOC::getTOCToggleIcon(); if( function_exists( 'ez_toc_pro_activation_link' ) ) { $icon = apply_filters('ez_toc_modify_icon',$icon); $label_below_html = apply_filters('ez_toc_label_below_html',$label_below_html, $read_time); } $html .= ''.$icon.''; } $html .= ''; $html .= '
' . PHP_EOL; $html .= $label_below_html; return $html; } //css based heaing function private function get_css_based_toc_heading($options){ $html = ''; $header_label = ''; $show_header_text = true; if(isset($options['no_label']) && $options['no_label'] == true){ $show_header_text = false; } if ( $show_header_text && ezTOC_Option::get( 'show_heading_text' ) ) { $toc_title = ezTOC_Option::get( 'heading_text' ); $toc_title_tag = ezTOC_Option::get( 'heading_text_tag' ); $toc_title_tag = $toc_title_tag?$toc_title_tag:'p'; if ( strpos( $toc_title, '%PAGE_TITLE%' ) !== false ) { $toc_title = str_replace( '%PAGE_TITLE%', get_the_title(), $toc_title ); } if ( strpos( $toc_title, '%PAGE_NAME%' ) !== false ) { $toc_title = str_replace( '%PAGE_NAME%', get_the_title(), $toc_title ); } if(isset($options['header_label'])){ $toc_title = $options['header_label']; } $header_label = '<'.esc_attr($toc_title_tag).' class="ez-toc-title">' . esc_html__( htmlentities( $toc_title, ENT_COMPAT, 'UTF-8' ), 'easy-table-of-contents' ). '' . PHP_EOL; if (!ezTOC_Option::get( 'visibility' ) ) { $html .='
'.$header_label.'
'; } } $show_toggle_view = true; if(isset($options['no_toggle']) && $options['no_toggle'] == true){ $show_toggle_view = false; } if ( $show_toggle_view && ezTOC_Option::get( 'visibility' ) ) { $cssIconID = uniqid(); $inputCheckboxExludeStyle = ""; if ( ezTOC_Option::get( 'exclude_css' ) ) { $inputCheckboxExludeStyle = "style='display:none'"; } $toggle_view=''; if(ezTOC_Option::get('visibility_hide_by_default')==true){ $toggle_view= "checked"; } if( true == get_post_meta( $this->post->ID, '_ez-toc-visibility_hide_by_default', true ) ){ $toggle_view= "checked"; } if( $options !== null && !empty( $options ) && is_array( $options ) && key_exists( 'visibility_hide_by_default', $options ) && true == $options['visibility_hide_by_default'] ) { $toggle_view= "checked"; } $toc_icon = ezTOC::getTOCToggleIcon(); $label_below_html = ''; $read_time = array(); if(isset($options['read_time']) && $options['read_time'] != ''){ $read_time['read_time'] = $options['read_time']; } if( function_exists( 'ez_toc_pro_activation_link' ) ) { $toc_icon = apply_filters('ez_toc_modify_icon',$toc_icon); $label_below_html = apply_filters('ez_toc_label_below_html',$label_below_html, $read_time); } if ( ezTOC_Option::get( 'visibility_on_header_text' ) ) { $html .= ''.$label_below_html.''; }else{ if(function_exists('ez_toc_pro_inline_css_func')){ $html .= '
'.$header_label.'
'.$label_below_html.''; }else{ $html .= $header_label.''.$label_below_html.''; } } } return $html; } /** * Displays the post's TOC. * * @access public * @since 2.0 */ public function toc() { echo $this->getTOC(); } /** * Generate the TOC list items for a given page within a post. * * @access private * @since 2.0 * * @param int $page The page of the post to create the TOC items for. * @param array $matches The heading from the post content extracted with preg_match_all(). * * @return string The HTML list of TOC items. */ private function createTOC( $page, $matches, $prefix = "ez-toc", $toc_more = array() ) { // Whether or not the TOC should be built flat or hierarchical. $hierarchical = ezTOC_Option::get( 'show_hierarchy' ); if(isset($toc_more['hierarchy'])){ $hierarchical = true; }elseif(isset($toc_more['no_hierarchy'])){ $hierarchical = false; } $html = $toc_type = $collapse_status = ''; if(isset($toc_more['collapse_hd'])){ $collapse_status = true; }elseif(isset($toc_more['no_collapse_hd'])){ $collapse_status = false; } $count_matches = is_array($matches) ? count($matches) : ''; $toc_type = ezTOC_Option::get( 'toc_loading' ); if ( $hierarchical ) { //To not show view more in Hierarchy unset($toc_more['view_more']); $current_depth = 100; // headings can't be larger than h6 but 100 as a default to be sure $numbered_items = array(); $numbered_items_min = null; // find the minimum heading to establish our baseline foreach ( $matches as $i => $match ) { if ( $current_depth > $matches[ $i ][2] ) { $current_depth = (int) $matches[ $i ][2]; } } $numbered_items[ $current_depth ] = 0; $numbered_items_min = $current_depth; foreach ( $matches as $i => $match ) { $level = $matches[ $i ][2]; $count = $i + 1; 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; //Hide Level 4 Headings $sub_active = ''; if($level > 3){ $sub_active = apply_filters('ez_toc_hierarchy_js_add_attr', $sub_active, $collapse_status); } $html .= "'; $numbered_items[ $current_depth ] = 0; } } if ( $current_depth == (int) @$matches[ $i + 1 ][2] ) { $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 .= ''; } } } } } else { if(isset($toc_more['view_more']) && $toc_more['view_more']>0){ //No. of Headings $no_of_headings = $toc_more['view_more']; if(is_array($matches)){ foreach ( $matches as $i => $match ) { $count = $i + 1; $title = isset( $matches[ $i ]['alternate'] ) ? $matches[ $i ]['alternate'] : $matches[ $i ][0]; $title = strip_tags( apply_filters( 'ez_toc_title', $title ), apply_filters( 'ez_toc_title_allowable_tags', '' ) ); if($count <= $no_of_headings){ $html .= "
  • "; $html .= $this->createTOCItemAnchor( $matches[ $i ]['page'], $matches[ $i ]['id'], $title, $count ); $html .= '
  • '; }else{ $detect = ''; $is_more_last = false; if('css' == $toc_type && $i == $no_of_headings && function_exists('ez_toc_non_amp') && ez_toc_non_amp()){ $html .= ''; } } } } }else{ if(is_array($matches)){ foreach ( $matches as $i => $match ) { $count = $i + 1; $title = isset( $matches[ $i ]['alternate'] ) ? $matches[ $i ]['alternate'] : $matches[ $i ][0]; $title = strip_tags( apply_filters( 'ez_toc_title', $title ), apply_filters( 'ez_toc_title_allowable_tags', '' ) ); $html .= "
  • "; $html .= $this->createTOCItemAnchor( $matches[ $i ]['page'], $matches[ $i ]['id'], $title, $count ); $html .= '
  • '; } } } } $html = apply_filters('ez_toc_pro_html_modifier', $html, $toc_more, $count_matches, $toc_type); return do_shortcode($html); } /** * @access private * @since 2.0 * * @param int $page * @param string $id * @param string $title * @param int $count * * @return string */ private function createTOCItemAnchor( $page, $id, $title, $count ) { if (ezTOC_Option::get( 'remove_special_chars_from_title' )) { $title = str_replace(':', '', $title); } $anch_name = 'href'; if(ezTOC_Option::get( 'toc_loading' ) == 'js' && ezTOC_Option::get( 'smooth_scroll' ) && ezTOC_Option::get( 'avoid_anch_jump' )){ $anch_name = 'href="#" data-href'; } return sprintf( '%3$s', esc_url( $this->createTOCItemURL( $id, $page ) ), esc_attr( strip_tags( $title ) ), $title ); } /** * @access private * @since 2.0 * * @param string $id * @param int $page * * @return string */ private function createTOCItemURL( $id, $page ) { $current_post = $this->post->ID === $this->queriedObjectID; $current_page = $this->getCurrentPage(); $anch_url = $this->permalink; //Ajax Load more //@since 2.0.61 if(ezTOC_Option::get( 'ajax_load_more' ) && isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'){ $anch_url = $_SERVER['HTTP_REFERER']; } if ( $page === $current_page && $current_post ) { return (ezTOC_Option::get( 'add_request_uri' ) ? $_SERVER['REQUEST_URI'] : '') . '#' . $id; } elseif ( 1 === $page ) { // Fix for wrong links on TOC on Wordpress category page if(is_category() || is_tax() || is_tag() || (function_exists('is_product_category') && is_product_category())){ return '#' . $id; } return trailingslashit( $anch_url ) . '#' . $id; } return trailingslashit( $anch_url ) . $page . '/#' . $id; } }