PluginProbe
SimpleTOC – Table of Contents Block / 5.0.3
SimpleTOC – Table of Contents Block v5.0.3
7.2.0 7.3.0 7.1.1 7.1.0 7.0.10 7.0.9 7.0.8 7.0.7 7.0.6 7.0.4 7.0.5 5.0.21.1 5.0.22 5.0.23 5.0.24 5.0.25 5.0.25.1 5.0.27 5.0.28 5.0.29 5.0.3 5.0.30 5.0.31 5.0.32 5.0.33 All 159 releases
simpletoc / plugin.php

plugin.php in SimpleTOC – Table of Contents Block 5.0.3, at plugin.php

310 lines 8.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Plugin Name: SimpleTOC - Table of Contents Block
5 * Plugin URI: https://marc.tv/simpletoc-wordpress-inhaltsverzeichnis-plugin-gutenberg/
6 * Description: Adds a basic "Table of Contents" Gutenberg block.
7 * Version: 5.0.3
8 * Author: Marc Tönsing
9 * Author URI: https://marc.tv
10 * Text Domain: simpletoc
11 * License: GPL v2 or later
12 * License URI: http://www.gnu.org/licenses/gpl-2.0.html
13 *
14 */
15
16 /**
17 * Initalise frontend and backend and register block
18 **/
19
20 function register_simpletoc_block()
21 {
22
23 wp_set_script_translations( 'simpletoc-toc-editor-script', 'simpletoc' );
24
25 add_filter('plugin_row_meta', __NAMESPACE__ . '\\simpletoc_plugin_meta', 10, 2);
26
27 register_block_type( __DIR__ . '/build' , [
28 'render_callback' => __NAMESPACE__ . '\\render_callback'
29 ]);
30
31 }
32
33 add_action( 'init', 'register_simpletoc_block' );
34
35 /**
36 * Filter to add plugins to the TOC list for Rank Math plugin
37 *
38 * @param array TOC plugins.
39 */
40
41 add_filter('rank_math/researches/toc_plugins', function ($toc_plugins) {
42 $toc_plugins['simpletoc/plugin.php'] = 'SimpleTOC';
43 return $toc_plugins;
44 });
45
46 /**
47 * Render block output
48 *
49 */
50
51 function render_callback( $attributes )
52 {
53 //add only if block is used in this post.
54 add_filter('render_block', __NAMESPACE__ . '\\filter_block', 10, 2);
55
56 $alignclass = '';
57 if ( isset ($attributes['align']) ) {
58 $align = $attributes['align'];
59 $alignclass = 'align' . $align;
60 }
61
62 $className = '';
63 if ( isset( $attributes['className'] ) ) {
64 $className = strip_tags(htmlspecialchars($attributes['className']));
65 }
66
67 $post = get_post();
68 $blocks = parse_blocks($post->post_content);
69
70 if (empty($blocks)) {
71 $html = '';
72 if ($attributes['no_title'] == false) {
73 $html = '<h2 class="simpletoc-title">' . __('Table of Contents2', 'simpletoc') . '</h2>';
74 }
75 $html .= '<p class="components-notice is-warning ' . $alignclass . '">' . __('No blocks found.', 'simpletoc') . ' ' . __('Save or update post first.', 'simpletoc') . '</p>';
76 return $html;
77 }
78
79 $headings = array_reverse(filter_headings_recursive($blocks));
80
81 $headings_clean = array_map('trim', $headings);
82
83 if (empty($headings_clean)) {
84 $html = '';
85 if ($attributes['no_title'] == false) {
86 $html = '<h2 class="simpletoc-title">' . __('Table of Contents', 'simpletoc') . '</h2>';
87 }
88 $html .= '<p class="components-notice is-warning ' . $alignclass . '">' . __('No headings found.', 'simpletoc') . ' ' . __('Save or update post first.', 'simpletoc') . '</p>';
89 return $html;
90 }
91
92 $output = generateToc($headings_clean, $attributes);
93
94 $pre_html = '<div class="simpletoc ' . $className . ' ' . $alignclass . '">';
95 $post_html = '</div>';
96 $output = $pre_html . $output . $post_html;
97
98 return $output;
99 }
100
101 /**
102 * Return all headings with a recursive walk through all blocks.
103 * This includes groups and reusable block with groups within reusable blocks.
104 */
105
106 function filter_headings_recursive($blocks)
107 {
108
109 $arr = array();
110
111 foreach ($blocks as $block => $innerBlock) {
112
113 if (is_array($innerBlock)) {
114
115 if (isset($innerBlock['attrs']['ref'])) {
116 // search in reusable blocks
117 $e_arr = parse_blocks(get_post($innerBlock['attrs']['ref'])->post_content);
118 $arr = array_merge(filter_headings_recursive($e_arr), $arr);
119 } else {
120 // search in groups
121 $arr = array_merge(filter_headings_recursive($innerBlock), $arr);
122 }
123 } else {
124
125 if (isset($blocks['blockName']) && $blocks['blockName'] === 'core/heading' && $innerBlock !== 'core/heading') {
126 // make sure its a headline.
127 if (preg_match("/(<h1|<h2|<h3|<h4|<h5|<h6)/i", $innerBlock)) {
128 $arr[] = $innerBlock;
129 }
130 }
131 }
132 }
133
134 return $arr;
135 }
136
137 /**
138 * Remove all problematic characters for toc links
139 */
140
141 function simpletoc_sanitize_string($string)
142 {
143
144 // remove punctuation
145 $zero_punctuation = preg_replace("/\p{P}/u", "", $string);
146 // remove non-breaking spaces
147 $html_wo_nbs = str_replace("&nbsp;", " ", $zero_punctuation);
148 // remove umlauts and accents
149 $string_without_accents = remove_accents($html_wo_nbs);
150 // Sanitizes a title, replacing whitespace and a few other characters with dashes.
151 $sanitized_string = sanitize_title_with_dashes($string_without_accents);
152 // Encode for use in an url
153 $urlencoded = urlencode($sanitized_string);
154
155 return $urlencoded;
156 }
157
158 function simpletoc_plugin_meta($links, $file)
159 {
160
161 if (false !== strpos($file, 'simpletoc')) {
162 $links = array_merge($links, array('<a href="https://wordpress.org/support/plugin/simpletoc">' . __('Support', 'simpletoc') . '</a>'));
163 $links = array_merge($links, array('<a href="https://marc.tv/out/donate">' . __('Donate', 'simpletoc') . '</a>'));
164 $links = array_merge($links, array('<a href="https://wordpress.org/support/plugin/simpletoc/reviews/#new-post">' . __('Write a review', 'simpletoc') . '&nbsp;⭐️⭐️⭐️⭐️⭐️</a>'));
165 }
166
167 return $links;
168 }
169
170 function addAnchorAttribute($html)
171 {
172
173 // remove non-breaking space entites from input HTML
174 $html_wo_nbs = str_replace("&nbsp;", " ", $html);
175
176 // Thank you Nick Diego
177 if (!$html_wo_nbs) {
178 return $html;
179 }
180
181 libxml_use_internal_errors(TRUE);
182 $dom = new \DOMDocument();
183 @$dom->loadHTML($html_wo_nbs, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
184
185 // use xpath to select the Heading html tags.
186 $xpath = new \DOMXPath($dom);
187 $tags = $xpath->evaluate("//*[self::h1 or self::h2 or self::h3 or self::h4 or self::h5 or self::h6]");
188
189 // Loop through all the found tags
190 foreach ($tags as $tag) {
191
192 // Set id attribute
193 $heading_text = strip_tags($html);
194 $anchor = simpletoc_sanitize_string($heading_text);
195 $tag->setAttribute("id", $anchor);
196 }
197
198 // Save the HTML changes
199 $content = utf8_decode($dom->saveHTML($dom->documentElement));
200
201 return $content;
202 }
203
204 function filter_block($block_content, $block)
205 {
206 $className = '';
207
208 if ($block['blockName'] !== 'core/heading') {
209 return $block_content;
210 }
211
212 $block_content = addAnchorAttribute($block_content);
213
214 return $block_content;
215 }
216
217 function generateToc($headings, $attributes)
218 {
219
220 $list = '';
221 $html = '';
222 $min_depth = 6;
223 $listtype = 'ul';
224 $absolute_url = '';
225 $inital_depth = 6;
226 $link_class = '';
227 $styles = '';
228
229 if ($attributes['remove_indent'] == true) {
230 $styles = 'style="padding-left:0;list-style:none;"';
231 }
232
233 if ($attributes['add_smooth'] == true) {
234 $link_class = 'class="smooth-scroll"';
235 }
236
237 if ($attributes['use_ol'] == true) {
238 $listtype = 'ol';
239 }
240
241 if ($attributes['use_absolute_urls'] == true) {
242 $absolute_url = get_permalink();
243 }
244
245 foreach ($headings as $line => $headline) {
246 if ($min_depth > $headings[$line][2]) {
247 // search for lowest level
248 $min_depth = (int) $headings[$line][2];
249 $inital_depth = $min_depth;
250 }
251 }
252
253 foreach ($headings as $line => $headline) {
254
255 $title = strip_tags($headline);
256 $link = simpletoc_sanitize_string($title);
257 $this_depth = (int) $headings[$line][2];
258 if (isset($headings[$line + 1][2])) {
259 $next_depth = (int) $headings[$line + 1][2];
260 } else {
261 $next_depth = '';
262 }
263
264 // skip this heading because a max depth is set.
265 if ($this_depth > $attributes['max_level'] or strpos($headline, 'class="simpletoc-hidden') > 0) {
266 goto closelist;
267 }
268
269 // start list
270 if ($this_depth == $min_depth) {
271 $list .= "<li>\n";
272 } else {
273 // we are not as base level. Start opening levels until base is reached.
274 for ($min_depth; $min_depth < $this_depth; $min_depth++) {
275 $list .= "\n\t\t<" . $listtype . "><li>\n";
276 }
277 }
278
279 $list .= "<a " . $link_class . " href=\"" . $absolute_url . "#" . $link . "\">" . $title . "</a>";
280
281 closelist:
282 // close lists
283 // check if this is not the last heading
284 if ($line != count($headings) - 1) {
285 // do we need to close the door behind us?
286 if ($min_depth > $next_depth) {
287 // If yes, how many times?
288 for ($min_depth; $min_depth > $next_depth; $min_depth--) {
289 $list .= "</li></" . $listtype . ">\n";
290 }
291 }
292 if ($min_depth == $next_depth) {
293 $list .= "</li>";
294 }
295 // last heading
296 } else {
297 for ($inital_depth; $inital_depth < $this_depth; $inital_depth++) {
298 $list .= "</li></" . $listtype . ">\n";
299 }
300 }
301 }
302
303 if ($attributes['no_title'] == false) {
304 $html = "<h2 class=\"simpletoc-title\">" . __("Table of Contents", "simpletoc") . "</h2>";
305 }
306 $html .= "<" . $listtype . " class=\"simpletoc-list\" " . $styles .">\n" . $list . "</li></" . $listtype . ">";
307
308 return $html;
309 }
310