OwlCyberSecurity - MANAGER
Edit File: class-eztoc-post.php
<?php use function Easy_Plugins\Table_Of_Contents\Cord\br2; // Exit if accessed directly if ( ! defined( 'ABSPATH' ) ) exit; class ezTOC_Post { /** * @since 2.0 * @var int */ private $queriedObjectID; /** * @since 2.0 * @var WP_Post */ private $post; /** * @since 2.0 * @var false|string */ private $permalink; /** * The post content broken into pages by user inserting `<!--nextpage-->` 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); global $eztoc_disable_the_content; if($eztoc_disable_the_content){ $apply_content_filter = false; $eztoc_disable_the_content = false; } 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 ); $enable_memory_fix = ezTOC_Option::get('enable_memory_fix'); if ( $enable_memory_fix ) { /* * Strip the shortcodes but retain their inner content for processing TOC. * This issues happens with builder themes which adds shortcodes for sections , rows and columns etc * This is required to prevent an Infinite loop when the `the_content` filter is applied and the post content contains the ezTOC shortcode. * * @see https://github.com/ahmedkaludi/Easy-Table-of-Contents/issues/749 */ $this->post->post_content = $this->stripShortcodesButKeepContent($this->post->post_content); } $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', 'ez-toc-widget-sticky', 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. //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason : Nonce verification is not required here. } elseif ( isset( $_REQUEST[ 'page' ] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason : Nonce verification is not required here. 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]; } } } $pages = array(); $split = preg_split( '/<!--nextpage-->/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 ); $selectors = ! is_array( $selectors ) ? [] : $selectors; // In case we get string instead of array $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 `<br>` while WP normalizes to `<br />`. * 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' ) ) || function_exists( 'koyfin_setup' )) { $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([1-6]{1})[^>]*>)(.*)<\/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 ) { $match[3] = apply_filters( 'ez_toc_filter_headings_from_exclude_nodes', $match[3]); if ( $this->inExcludedNode( "{$match[3]}</h$match[2]>" ) ) { 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' ) ) ) ? wp_strip_all_tags( str_replace( array( "\r", "\n" ), ' ', $matches[ $i ][0] ) ) : wptexturize(wp_strip_all_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' ) ); $against = trim($against); 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 = wp_strip_all_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( wp_strip_all_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( wp_strip_all_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 '</h' . $matches[ $i ][2] . '>' // end of heading ), array( '>', '</h' . $matches[ $i ][2] . '>' ), 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'] = wp_strip_all_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 '</h' . $matches[ $i ][2] . '>' // end of heading ), array( '><span class="ez-toc-section" id="' . $anchor . '"></span>', '<span class="ez-toc-section-end"></span></h' . $matches[ $i ][2] . '>' ), apply_filters('ez_toc_content_heading_title_anchor',$matches[ $i ][0]) ); } } return $headings; } /** * Parse the post content and headings. * only use when filter "ez_toc_modify_process_page_content" is not fetching correct content * mostly in case of custom post types. * * @access public * @since 2.0 */ public function setContent($content){ $pages = array(); $split = preg_split( '/<!--nextpage-->/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; } /** * 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 = ''; $visibilty_by_device = ezTOC_Option::get( 'visibility_hide_by_device' , ['mobile', 'desktop']); if(get_post_meta( $this->post->ID, '_ez-toc-visibility_hide_by_device', true )){ $visibilty_by_device = get_post_meta( $this->post->ID, '_ez-toc-visibility_hide_by_device', true ); } 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 = ""; }elseif(is_array($options) && key_exists( 'visibility_hide_by_default', $options ) && $options['visibility_hide_by_default'] == false){ $visiblityClass = ""; } if("eztoc-toggle-hide-by-default" == $visiblityClass){ if( function_exists('wp_is_mobile') && wp_is_mobile() ){ $visiblityClass = (in_array('mobile', $visibilty_by_device)) ? "eztoc-toggle-hide-by-default" : ""; }else{ $visiblityClass = (in_array('desktop', $visibilty_by_device)) ? "eztoc-toggle-hide-by-default" : ""; } } $html = apply_filters('ez_toc_add_custom_links',$html); $html = "<ul class='{$prefix}-list {$prefix}-list-level-1 $visiblityClass' >" . $html . "</ul>"; } return $html; } /** * Get the post Sticky Toggle TOC content block. * * @access public * @return string * @since 2.0.32 * */ public function get_sticky_toggle_toc() { $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' ) ) { $htmlSticky .= '<div class="ez-toc-sticky-title-container">' . PHP_EOL; $htmlSticky .= $this->get_toc_title_tag( 'sticky' ); $htmlSticky .= '<a class="ez-toc-close-icon" href="#" onclick="ezTOC_hideBar(event)" aria-label="'.esc_attr__('×','easy-table-of-contents').'"><span aria-hidden="true">×</span></a>' . PHP_EOL; $htmlSticky .= '</div>' . PHP_EOL; } else { $htmlSticky .= '<div class="ez-toc-sticky-title-container">' . PHP_EOL; $htmlSticky .= '<a class="ez-toc-close-icon" href="#" onclick="ezTOC_hideBar(event)" aria-label="'.esc_attr__('Close','easy-table-of-contents').'"><span aria-hidden="true">×</span></a>' . PHP_EOL; $htmlSticky .= '</div>' . PHP_EOL; } $htmlSticky .= '<div id="ez-toc-sticky-container" class="ez-toc-sticky-container ' . implode( ' ', $classSticky ) . '">' . PHP_EOL; ob_start(); do_action( 'ez_toc_sticky_toggle_before' ); $htmlSticky .= ob_get_clean(); $htmlSticky .= "<nav class='$ezTocStickyToggleDirection'>" . $this->getTOCList( "ez-toc-sticky" ) . "</nav>"; ob_start(); do_action( 'ez_toc_sticky_toggle_after' ); $htmlSticky .= ob_get_clean(); $htmlSticky .= '</div>' . 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' ); } if( isset( $options['wrapping'] ) ){ $toc_align = $options['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 } if( isset($options['class']) && !empty($options['class']) ) { $class_from_attr = explode(' ', $options['class']); $class = array_merge($class, $class_from_attr); } $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 .= '<div id="ez-toc-container" class="' . implode( ' ', $class ) . '">' . 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 .= '<nav>' . $this->getTOCList('ez-toc', $options) . '</nav>'; ob_start(); do_action( 'ez_toc_after' ); $html .= ob_get_clean(); $html .= '</div>' . PHP_EOL; } return apply_filters('eztoc_autoinsert_final_toc_html',$html); } private function get_js_based_toc_heading($options){ $html = ''; $html .= '<div class="ez-toc-title-container">' . 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 ) { $html .= $this->get_toc_title_tag( 'js' , $options ); $html .= $header_label; } $html .= '<span class="ez-toc-title-toggle">'; $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::get_toc_toggle_icon(); 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 .= '<a href="#" class="ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle" aria-label="'.esc_attr__('Toggle Table of Content','easy-table-of-contents').'"><span class="ez-toc-js-icon-con">'.$icon.'</span></a>'; } $html .= '</span>'; $html .= '</div>' . 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' ) ) { $header_label = $this->get_toc_title_tag( 'css' , $options ); if (!ezTOC_Option::get( 'visibility' ) ) { $html .='<div class="ez-toc-title-container">'.$header_label.'</div>'; } } $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"; } if( $options !== null && !empty( $options ) && is_array( $options ) && key_exists( 'visibility_hide_by_default', $options ) && false == $options['visibility_hide_by_default'] ) { $toggle_view= ''; } $toc_icon = ezTOC::get_toc_toggle_icon(); $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 for="ez-toc-cssicon-toggle-item-' . $cssIconID . '" class="ez-toc-cssicon-toggle-label">' .$header_label. $toc_icon . '</label>'.$label_below_html.'<input type="checkbox" ' . $inputCheckboxExludeStyle . ' id="ez-toc-cssicon-toggle-item-' . $cssIconID . '" '.$toggle_view.' />'; }else{ if(function_exists('ez_toc_pro_inline_css_func')){ $html .= '<div class="ez-toc-cssicon-toggle-label">'.$header_label.'<label for="ez-toc-cssicon-toggle-item-' . $cssIconID . '">' . $toc_icon . '</label></div>'.$label_below_html.'<input type="checkbox" ' . $inputCheckboxExludeStyle . ' id="ez-toc-cssicon-toggle-item-' . $cssIconID . '" '.$toggle_view.' aria-label="'.esc_attr__('Toggle','easy-table-of-contents').'" />'; }else{ $html .= $header_label.'<label for="ez-toc-cssicon-toggle-item-' . $cssIconID . '" class="ez-toc-cssicon-toggle-label">' . $toc_icon . '</label>'.$label_below_html.'<input type="checkbox" ' . $inputCheckboxExludeStyle . ' id="ez-toc-cssicon-toggle-item-' . $cssIconID . '" '.$toggle_view.' aria-label="'.esc_attr__('Toggle','easy-table-of-contents').'" />'; } } } return $html; } /** * Displays the post's TOC. * * @access public * @since 2.0 */ public function toc() { //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is escaped in getTOC() 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 .= "<li class='{$prefix}-page-" . $page . " {$prefix}-heading-level-" . $current_depth . "'>"; } // 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 .= "<ul class='{$prefix}-list-level-" . $level . "' ".$sub_active."><li class='{$prefix}-heading-level-" . $level . "'>"; } } $title = isset( $matches[ $i ]['alternate'] ) ? $matches[ $i ]['alternate'] : $matches[ $i ][0]; //check for line break if(!ezTOC_Option::get( 'prsrv_line_brk' )){ $title = br2( $title, ' ' ); } $title = ez_toc_wp_strip_all_tags( apply_filters( 'ez_toc_title', $title ) ); $html .= $this->createTOCItemAnchor( $matches[ $i ]['page'], $matches[ $i ]['id'], $title, $count ); // end lists if ( $i != count( $matches ) - 1 ) { if ( $current_depth > (int) $matches[ $i + 1 ][2] ) { for ( $current_depth; $current_depth > (int) $matches[ $i + 1 ][2]; $current_depth-- ) { $html .= '</li></ul>'; $numbered_items[ $current_depth ] = 0; } } if ( $current_depth == (int) $matches[ $i + 1 ][2] ) { $html .= '</li>'; } } else { // this is the last item, make sure we close off all tags for ( $current_depth; $current_depth >= $numbered_items_min; $current_depth-- ) { $html .= '</li>'; if ( $current_depth != $numbered_items_min ) { $html .= '</ul>'; } } } } } 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 = ez_toc_wp_strip_all_tags( apply_filters( 'ez_toc_title', $title ) ); if($count <= $no_of_headings){ $html .= "<li class='{$prefix}-page-" . $page . "'>"; $html .= $this->createTOCItemAnchor( $matches[ $i ]['page'], $matches[ $i ]['id'], $title, $count ); $html .= '</li>'; }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 .= '</ul><input type="checkbox" id="ez-toc-more-toggle-css"/><ul class="ez-toc-more-wrp" style="--start: '.$i.'">'; } if($i == count($matches)-1){ $detect = 'm-last'; $is_more_last = true; } $html .= "<li class='{$prefix}-page-" . $page . " ez-toc-more-link " . $detect . "'>"; $html .= $this->createTOCItemAnchor( $matches[ $i ]['page'], $matches[ $i ]['id'], $title, $count ); $html .= '</li>'; if($is_more_last && 'css' == $toc_type && function_exists('ez_toc_non_amp') && ez_toc_non_amp()){ $html .= '</ul>'; } } } } }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 = ez_toc_wp_strip_all_tags( apply_filters( 'ez_toc_title', $title ) ); $html .= "<li class='{$prefix}-page-" . $page . "'>"; $html .= $this->createTOCItemAnchor( $matches[ $i ]['page'], $matches[ $i ]['id'], $title, $count ); $html .= '</li>'; } } } } $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'; } if(ezTOC_Option::get( 'disable_toc_links' ,false ) ){ return sprintf( '<a class=" ez-toc-heading-' . $count . '" role="button" >%2$s</a>', $title ); } return sprintf( '<a class="ez-toc-link ez-toc-heading-' . $count . '" '.$anch_name.'="%1$s" >%2$s</a>', esc_url( $this->createTOCItemURL( $id, $page ) ), $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 ) { /** * Check if self reference link option is enabled if yes then append full url in href * @since 2.0.73 */ if ( ezTOC_Option::get( 'add_self_reference_link' ) ) { return trailingslashit( $anch_url ) . '#' . $id; } 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; } /** * Strip Shortcodes but keeping its content. * * @access private * @since 2.0.67 * * @param string $content The post content. * * @return string The post content without shortcodes. */ private function stripShortcodesButKeepContent($content) { // Regex pattern to match the specific shortcodes $shortcodes = apply_filters('ez_toc_strip_shortcodes_with_inner_content',[]); if(!empty($shortcodes) && is_array($shortcodes)){ $pattern = '/\[('.implode('|',$shortcodes).')(?:\s[^\]]*)?\](.*?)\[\/\1\]|\[('.implode('|',$shortcodes).')(?:\s[^\]]*)?\/?\]/s'; // Function to recursively strip shortcodes while (preg_match($pattern, $content)) { $content = preg_replace_callback($pattern, function($matches) { if (isset($matches[2])) { return $matches[2]; // Keep content inside shortcode } return ''; // Remove self-closing shortcode }, $content); } } return $content; } /** * Get the TOC Title Tag content. * * @since 2.0.70 * * @param string Title tag. * @param string Title content. * @param string TOC type. * @param array Options. * * @return string The TOC Title Tag content. */ private function get_toc_title_tag( $toc_type = 'js', $options = [] ) { if($toc_type == 'sticky'){ $toc_title = apply_filters('ez_toc_sticky_title', ezTOC_Option::get( 'heading_text' )); }else{ $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 ); } // Allow the TOC Title to be overridden on a per-post basis if set. $post_heading_label = get_post_meta( get_the_ID(), '_ez-toc-header-label', true ); if ( !empty( $post_heading_label ) ) { $toc_title = $post_heading_label; } if(isset($options['header_label'])){ $toc_title = $options['header_label']; } $tag_classes = 'ez-toc-title'; $header_text_toggle_style = 'cursor:inherit'; $tag_html = ''; if( $toc_type == 'sticky' ){ $tag_classes = 'ez-toc-sticky-title'; } if( $toc_type == 'js' ){ if ( ezTOC_Option::get( 'visibility_on_header_text' ) ) { $tag_classes .= ' ez-toc-toggle'; $header_text_toggle_style = 'cursor:pointer'; } } switch($toc_title_tag){ case 'div': $tag_html = '<div class="' . esc_attr( $tag_classes ) . '" style="'. esc_attr( $header_text_toggle_style ) .'">' . esc_html( $toc_title ) . '</div>' . PHP_EOL; break; case 'label': $tag_html = '<label class="' . esc_attr( $tag_classes ) . '" style="'. esc_attr( $header_text_toggle_style ) .'">' . esc_html( $toc_title ) . '</label>' . PHP_EOL; break; case 'span': $tag_html = '<span class="' . esc_attr( $tag_classes ) . '" style="'. esc_attr( $header_text_toggle_style ) .'">' . esc_html( $toc_title ) . '</span>' . PHP_EOL; break; default: $tag_html = '<p class="' . esc_attr( $tag_classes ) . '" style="'. esc_attr( $header_text_toggle_style ) .'">' . esc_html( $toc_title ) . '</p>' . PHP_EOL; break; } return $tag_html; } }