>} */ function zeitfresser_build_toc_payload( $post_id ) { static $cache = array(); $post_id = (int) $post_id; if ( isset( $cache[ $post_id ] ) ) { return $cache[ $post_id ]; } $payload = array( 'content' => apply_filters( 'the_content', get_post_field( 'post_content', $post_id ) ), 'items' => array(), ); // Early exit conditions if ( ! $post_id || ! is_singular( 'post' ) || ! zeitfresser_show_article_toc() ) { return $cache[ $post_id ] = $payload; } $content = trim( (string) $payload['content'] ); if ( '' === $content || ! class_exists( 'DOMDocument' ) ) { return $cache[ $post_id ] = $payload; } libxml_use_internal_errors( true ); $dom = new DOMDocument(); $loaded = $dom->loadHTML( '
' . $content . '
', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD ); if ( ! $loaded ) { libxml_clear_errors(); return $cache[ $post_id ] = $payload; } $container = $dom->getElementById( 'zeitfresser-toc-root' ); if ( ! $container ) { libxml_clear_errors(); return $cache[ $post_id ] = $payload; } $index = 1; $toc_items = array(); $xpath = new DOMXPath( $dom ); $headings = $xpath->query( './/h2 | .//h3 | .//h4', $container ); if ( $headings instanceof DOMNodeList ) { foreach ( $headings as $heading ) { $text = trim( wp_strip_all_tags( $heading->textContent ) ); if ( '' === $text ) { continue; } $tag_name = strtolower( $heading->nodeName ); $id = $heading->getAttribute( 'id' ); if ( '' === $id ) { $base_id = sanitize_title( $text ); $id = $base_id ? $base_id : 'section-' . $index; while ( $dom->getElementById( $id ) ) { $id = $base_id . '-' . $index; $index++; } $heading->setAttribute( 'id', $id ); } $toc_items[] = array( 'id' => $id, 'text' => $text, 'level' => (int) substr( $tag_name, 1 ), ); $index++; } } libxml_clear_errors(); // Respect minimum threshold if ( count( $toc_items ) < zeitfresser_get_article_toc_min_headlines() ) { return $cache[ $post_id ] = array( 'content' => zeitfresser_extract_toc_inner_html( $container ), 'items' => array(), ); } return $cache[ $post_id ] = array( 'content' => zeitfresser_extract_toc_inner_html( $container ), 'items' => $toc_items, ); } /** * Extract container inner HTML. * * @param DOMNode $node Source node. * @return string */ function zeitfresser_extract_toc_inner_html( $node ) { $html = ''; if ( ! $node || ! $node->hasChildNodes() ) { return $html; } foreach ( $node->childNodes as $child_node ) { $html .= $node->ownerDocument->saveHTML( $child_node ); } return $html; } /** * Return whether the current singular post has a TOC. * * @param int|null $post_id Optional post ID. * @return bool */ function zeitfresser_has_floating_toc( $post_id = null ) { $post_id = $post_id ? (int) $post_id : get_the_ID(); if ( ! $post_id ) { return false; } $payload = zeitfresser_build_toc_payload( $post_id ); return ! empty( $payload['items'] ); } /** * Render floating TOC markup. * * @param int|null $post_id Optional post ID. * @return void */ function zeitfresser_render_floating_toc( $post_id = null ) { $post_id = $post_id ? (int) $post_id : get_the_ID(); if ( ! $post_id ) { return; } $payload = zeitfresser_build_toc_payload( $post_id ); if ( empty( $payload['items'] ) ) { return; } ?>