PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.2.3
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.2.3
4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.5.2 All 199 releases
betterdocs / includes / Shortcodes / ToC.php

ToC.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.2.3, at includes/Shortcodes/ToC.php

179 lines 6.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPDeveloper\BetterDocs\Shortcodes;
4
5 use WPDeveloper\BetterDocs\Utils\Node;
6 use WPDeveloper\BetterDocs\Core\Shortcode;
7
8 class ToC extends Shortcode {
9 protected $html_attributes = [];
10
11 public function get_name() {
12 return 'betterdocs_toc';
13 }
14
15 public function get_style_depends() {
16 return [ 'betterdocs-toc' ];
17 }
18
19 /**
20 * Summary of default_attributes
21 * @return array
22 */
23 public function default_attributes() {
24 return [
25 'post_type' => 'docs',
26 'post_id' => get_the_ID(),
27 'htags' => '1,2,3,4,5,6',
28 'hierarchy' => '',
29 'list_number' => '',
30 'collapsible_on_mobile' => $this->settings->get( 'collapsible_toc_mobile', false ),
31 'toc_title' => ''
32 ];
33 }
34
35 public function render( $atts, $content = null ) {
36 // Check if post is password protected and user hasn't provided correct password
37 if ( post_password_required( $this->attributes['post_id'] ) ) {
38 // Don't show ToC for password-protected posts until password is provided
39 return '';
40 }
41
42 $this->views( 'shortcodes/toc' );
43 }
44
45 public function view_params() {
46 return [
47 'post' => get_post( $this->attributes['post_id'] )
48 ];
49 }
50
51 /**
52 * Process content for TOC generation without triggering heavy content filters
53 * This handles special characters while avoiding memory issues from plugins like WPML
54 *
55 * @param string $content Raw post content
56 * @return string Processed content
57 */
58 public function process_content_for_toc( $content ) {
59 // Check if we should use the full content filter (for backward compatibility)
60 $use_full_filter = apply_filters( 'betterdocs_toc_use_full_content_filter', false );
61
62 if ( $use_full_filter ) {
63 // Use the full content filter if explicitly enabled
64 return apply_filters( 'the_content', $content );
65 }
66
67 // Apply only essential content processing filters that handle special characters
68 // without triggering heavy processing from plugins like WPML, page builders, etc.
69
70 // Handle shortcodes first (but don't execute them, just remove them to avoid conflicts)
71 $content = strip_shortcodes( $content );
72
73 // Decode HTML entities to handle special characters properly
74 $content = html_entity_decode( $content, ENT_QUOTES, 'UTF-8' );
75
76 // Convert line breaks to proper HTML if needed
77 $content = wpautop( $content );
78
79 // Apply specific filters that are safe and necessary for TOC generation
80 // These are lightweight filters that handle character encoding and basic formatting
81 $content = apply_filters( 'betterdocs_toc_content_processing', $content );
82
83 // Additional safety: limit content size to prevent memory issues
84 $max_content_length = apply_filters( 'betterdocs_toc_max_content_length', 500000 ); // 500KB default
85 if ( strlen( $content ) > $max_content_length ) {
86 $content = substr( $content, 0, $max_content_length );
87 }
88
89 return $content;
90 }
91
92 /**
93 * This method is responsible for re-arranging the TOC data based on hierarchy or non-hierarchy
94 *
95 * @param string $post_content
96 * @param string $htag_support
97 * @return Node|null
98 */
99 public function format_toc_data( $post_content, $htag_support, $toc_hierarchy ) {
100 $matches = [];
101
102 if ( $htag_support != '' ) {
103 preg_match_all( '/(<h([' . $htag_support . ']{1})[^>]*>).*<\/h\2>/msuU', $post_content, $matches, PREG_SET_ORDER );
104 }
105
106 if ( ! empty( $matches ) ) {
107 /*
108 |--------------------------------------------------------------------------
109 | Backtracking Algorithm Using Iteration | Main Login For Hierarchy TOC
110 |--------------------------------------------------------------------------
111 |
112 | Initially an object with key null and empty item of arrays are inserted into the stack of arrays.
113 | When inside the loop condition for the first time, the last stack value is assigned in a variable $last_data.
114 | And a new node object $new_data which is instantiated and the tag number is inserted for comparison as key, and empty items
115 | as a array are inserted into items property of the new node object. On the 'if' condition it checks, if the current tag number
116 | is smaller or equal to the last stack number. If the condition is true, the it enters into the while loop condition, which also
117 | checks if the current last stack tag number is greater or equal to the current tag number. It pops values from the stack until and unless the
118 | condition becomes false. The while loop condition becomes false only when the last stack value tag number becomes null and the last stack number is not
119 | greater or equal to the current node tag number.
120 |
121 | Backtracking occurs when the current tag_number $number[2] is less than or equal to the stacks last tag_number which is assigned as $last_data
122 |
123 | And then the last value is inserted as the new last_data, and the new_data node is inserted into the last_data node items.
124 | Additionally the $new_data is inserted into the stack to keep track of the used node. Somehow if the if condition becomes false
125 | then the stack last data is taken, and the new_data is inserted into the last_data->items and the new_data is inserted into the stack.
126 |
127 */
128
129 $dynamic_toc_title_switch = $this->settings->get( 'toc_dynamic_title' );
130 $stack = [];
131 $root = new Node();
132 $root->key = null;
133 $root->items = [];
134 $tag_counter = 0;
135
136 array_push( $stack, $root );
137
138 foreach ( $matches as $number ) {
139 $last_data = $stack[ count( $stack ) - 1 ];
140 $current_tag_number = isset( $number[2] ) ? $number[2] : '';
141 $current_title = isset( $number[0] ) ? $number[0] : '';
142 $current_tag = isset( $number[1] ) ? $number[1] : '';
143
144 $heading_name = preg_replace( '/<[^<]+?>/', '', $current_title );
145 $heading_name = ! empty( $heading_name ) ? strtolower( str_replace( ' ', '-', preg_replace( '/[^\p{L}\p{N}\s]/u', '', $heading_name ) ) ) : '';
146 preg_match( '/id="(.+?)"/', $current_title, $matches_id );
147 $heading_id = isset( $matches_id[1] ) ? strtolower( $matches_id[1] ) : '';
148 $tag_number = ! empty( $heading_id ) ? $heading_id : ( ! empty( $heading_name ) && $dynamic_toc_title_switch ? $heading_name : $tag_counter . '-toc-title' );
149
150 $new_data = new Node();
151 $new_data->key = $current_tag_number;
152 $new_data->tag = $current_tag;
153 $new_data->title = $current_title;
154 $new_data->tag_number = $tag_number;
155 $new_data->items = [];
156
157 if ( $last_data->key != null && $current_tag_number <= $last_data->key ) {
158 while ( $stack[ count( $stack ) - 1 ]->key != null && $stack[ count( $stack ) - 1 ]->key >= $current_tag_number ) {
159 array_pop( $stack );
160 }
161 $last_data = $stack[ count( $stack ) - 1 ];
162 }
163
164 array_push( $last_data->items, $new_data );
165
166 if ( $toc_hierarchy != 'off' && $toc_hierarchy != '' ) {
167 array_push( $stack, $new_data );
168 }
169
170 ++$tag_counter;
171 }
172
173 return $root;
174 }
175
176 return null;
177 }
178 }
179