PluginProbe
SimpleTOC – Table of Contents Block / 7.4.0
SimpleTOC – Table of Contents Block v7.4.0
7.4.0 7.3.1 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 All 161 releases
← All changes | plugin.php +186 -71 7.1.07.4.0 View file →
@@ -2,9 +2,9 @@
2 2 /**
3 3 * Plugin Name: SimpleTOC - Table of Contents Block
4 4 * Plugin URI: https://marc.tv/simpletoc-wordpress-inhaltsverzeichnis-plugin-gutenberg/
5 5 * Description: SEO-friendly Table of Contents Gutenberg block. No JavaScript or CSS by default.
6 - * Version: 7.1.0
6 + * Version: 7.4.0
7 7 * Requires at least: 6.2
8 8 * Requires PHP: 7.3
9 9 * Author: Marc Tönsing
10 10 * Author URI: https://toensing.com
@@ -20,8 +20,9 @@
20 20 require_once __DIR__ . '/simpletoc-admin-settings.php';
21 21 require_once __DIR__ . '/simpletoc-class-headline-ids.php';
22 22
23 23 const DEFAULT_BOX_COLOR = '#ebebeb';
24 +const SIMPLETOC_VERSION = '7.4.0';
24 25
25 26 /**
26 27 * Prevents direct execution of the plugin file.
27 28 * If a WordPress function does not exist, it means that the file has not been run by WordPress.
@@ -56,9 +57,10 @@
56 57 wp_add_inline_script(
57 58 'simpletoc-toc-editor-script',
58 59 'window.simpletocEditorSettings = ' . wp_json_encode(
59 60 array(
60 - 'settingsUrl' => admin_url( 'options-general.php?page=simpletoc' ),
61 + 'settingsUrl' => admin_url( 'options-general.php?page=simpletoc' ),
62 + 'scrollSpyEnabled' => simpletoc_scroll_spy_enabled(),
61 63 )
62 64 ) . ';',
63 65 'before'
64 66 );
@@ -76,8 +78,10 @@
76 78 */
77 79 function add_simpletoc_block_editor_settings( $editor_settings, $editor_context ) {
78 80 $editor_settings['simpletocSettingsUrl'] = admin_url( 'options-general.php?page=simpletoc' );
79 81
82 + $editor_settings['simpletocScrollSpyEnabled'] = simpletoc_scroll_spy_enabled();
83 +
80 84 return $editor_settings;
81 85 }
82 86
83 87 add_filter( 'block_editor_settings_all', __NAMESPACE__ . '\add_simpletoc_block_editor_settings', 10, 2 );
@@ -162,11 +166,11 @@
162 166 2
163 167 );
164 168
165 169 /**
166 - * Filter to add plugins to the TOC list for Rank Math plugin
170 + * Filter to add plugins to the TOC list for Rank Math plugin.
167 171 *
168 - * @param array TOC plugins.
172 + * @param array $toc_plugins TOC plugins.
169 173 */
170 174 add_filter(
171 175 'rank_math/researches/toc_plugins',
172 176 function ( $toc_plugins ) {
@@ -193,9 +197,9 @@
193 197
194 198 return $content;
195 199 }
196 200
197 -add_filter( 'the_content', __NAMESPACE__ . '\simpletoc_add_ids_to_content', 1 );
201 +add_filter( 'the_content', __NAMESPACE__ . '\simpletoc_add_ids_to_content', 1 );
198 202
199 203 /**
200 204 * Recursively adds IDs to the headings of a nested block structure.
201 205 *
@@ -216,9 +220,9 @@
216 220 * @param array $supported_blocks The array of supported blocks.
217 221 */
218 222 $supported_blocks = apply_filters( 'simpletoc_supported_blocks_for_ids', $supported_blocks );
219 223
220 - // Need two separate instances so that IDs aren't double coubnted.
224 + // Need two separate instances so that IDs aren't double counted.
221 225 $inner_html_id_instance = new SimpleTOC_Headline_Ids();
222 226 $inner_content_id_instance = new SimpleTOC_Headline_Ids();
223 227
224 228 foreach ( $blocks as &$block ) {
@@ -224,12 +228,8 @@
224 228 foreach ( $blocks as &$block ) {
225 229 if ( isset( $block['blockName'] ) && in_array( $block['blockName'], $supported_blocks, true ) && isset( $block['innerHTML'] ) && isset( $block['innerContent'] ) && isset( $block['innerContent'][0] ) ) {
226 230 $block['innerHTML'] = add_anchor_attribute( $block['innerHTML'], $inner_html_id_instance, $block );
227 231 $block['innerContent'][0] = add_anchor_attribute( $block['innerContent'][0], $inner_content_id_instance, $block );
228 - } elseif ( isset( $block['attrs']['ref'] ) ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedElseif
229 - // search in reusable blocks (this is not finished because I ran out of ideas.)
230 - // $reusable_block_id = $block['attrs']['ref'];
231 - // $reusable_block_content = parse_blocks(get_post($reusable_block_id)->post_content);.
232 232 } elseif ( ! empty( $block['innerBlocks'] ) ) {
233 233 // search in groups.
234 234 $block['innerBlocks'] = add_ids_to_blocks_recursive( $block['innerBlocks'] );
235 235 }
@@ -247,21 +247,30 @@
247 247 function render_callback_simpletoc( $attributes ) {
248 248 $is_backend = defined( 'REST_REQUEST' ) && REST_REQUEST && 'edit' === filter_input( INPUT_GET, 'context' );
249 249 $title_text = $attributes['title_text'] ? esc_html( trim( $attributes['title_text'] ) ) : __( 'Table of Contents', 'simpletoc' );
250 250 $alignclass = ! empty( $attributes['align'] ) ? 'align' . $attributes['align'] : '';
251 - $class_name = ! empty( $attributes['className'] ) ? wp_strip_all_tags( $attributes['className'] ) : '';
252 251 $title_level = $attributes['title_level'];
253 252 $global_box_style_enabled = apply_filters( 'simpletoc_box_style_enabled', false ) || true === (bool) get_option( 'simpletoc_box_style_enabled', false );
254 - $box_style_enabled = $global_box_style_enabled || ! empty( $attributes['box_style'] );
253 + $legacy_box_style_enabled = ! empty( $attributes['box_style'] );
254 + $typography_enabled = ! empty( $attributes['fontSize'] ) || ! empty( $attributes['style']['typography'] );
255 255 $wrapper_classes = array( 'simpletoc' );
256 256 $wrapper_style = '';
257 257
258 - if ( $box_style_enabled ) {
258 + if ( simpletoc_scroll_spy_enabled() || ! empty( $attributes['scroll_spy'] ) ) {
259 + $wrapper_classes[] = 'has-simpletoc-scroll-spy';
260 + }
261 +
262 + if ( $typography_enabled ) {
263 + $wrapper_classes[] = 'has-simpletoc-typography';
264 + }
265 +
266 + if ( $global_box_style_enabled || $legacy_box_style_enabled ) {
267 + $wrapper_classes[] = 'is-style-boxed';
259 268 $wrapper_classes[] = 'has-simpletoc-box-style';
260 269
261 270 if ( $global_box_style_enabled ) {
262 271 $wrapper_classes[] = 'has-background';
263 - $wrapper_style = safecss_filter_attr( 'background-color:' . DEFAULT_BOX_COLOR . ';' );
272 + $wrapper_style = safecss_filter_attr( 'background-color:' . DEFAULT_BOX_COLOR . ';' );
264 273 } elseif ( ! empty( $attributes['box_color'] ) ) {
265 274 $wrapper_classes[] = 'has-background';
266 275 $wrapper_style = safecss_filter_attr( 'background-color:' . $attributes['box_color'] . ';' );
267 276 } else {
@@ -269,9 +278,8 @@
269 278 $wrapper_style = safecss_filter_attr( 'background-color:' . DEFAULT_BOX_COLOR . ';' );
270 279 }
271 280 }
272 281
273 - $wrapper_enabled = apply_filters( 'simpletoc_wrapper_enabled', false ) || true === (bool) get_option( 'simpletoc_wrapper_enabled', false ) || true === (bool) get_option( 'simpletoc_accordion_enabled', false );
274 282 $wrapper_attrs = get_block_wrapper_attributes(
275 283 array(
276 284 'class' => implode( ' ', $wrapper_classes ),
277 285 'style' => $wrapper_style,
@@ -276,11 +284,10 @@
276 284 'class' => implode( ' ', $wrapper_classes ),
277 285 'style' => $wrapper_style,
278 286 )
279 287 );
280 - $has_wrapper = ! empty( $class_name ) || $wrapper_enabled || $attributes['accordion'] || $attributes['wrapper'] || $box_style_enabled;
281 - $pre_html = $has_wrapper ? '<div role="navigation" aria-label="' . __( 'Table of Contents', 'simpletoc' ) . '" ' . $wrapper_attrs . '>' : '';
282 - $post_html = $has_wrapper ? '</div>' : '';
288 + $pre_html = '<div role="navigation" aria-label="' . esc_attr__( 'Table of Contents', 'simpletoc' ) . '" ' . $wrapper_attrs . '>';
289 + $post_html = '</div>';
283 290
284 291 $post = get_post();
285 292 $blocks = ! is_null( $post ) && ! is_null( $post->post_content ) ? parse_blocks( $post->post_content ) : '';
286 293
@@ -289,17 +296,17 @@
289 296 $headings_clean = array_map( 'trim', $headings );
290 297 $toc_html = generate_toc( $headings_clean, $attributes );
291 298
292 299 if ( empty( $blocks ) ) {
293 - return get_empty_blocks_message( $is_backend, $attributes, $title_level, $alignclass, $title_text, __( 'No blocks found.', 'simpletoc' ), __( 'Save or update post first.', 'simpletoc' ), $wrapper_attrs, $has_wrapper );
300 + return get_empty_blocks_message( $is_backend, $attributes, $title_level, $alignclass, $title_text, __( 'No blocks found.', 'simpletoc' ), __( 'Save or update post first.', 'simpletoc' ), $wrapper_attrs );
294 301 }
295 302
296 303 if ( empty( $headings_clean ) ) {
297 - return get_empty_blocks_message( $is_backend, $attributes, $title_level, $alignclass, $title_text, __( 'No headings found.', 'simpletoc' ), __( 'Save or update post first.', 'simpletoc' ), $wrapper_attrs, $has_wrapper );
304 + return get_empty_blocks_message( $is_backend, $attributes, $title_level, $alignclass, $title_text, __( 'No headings found.', 'simpletoc' ), __( 'Save or update post first.', 'simpletoc' ), $wrapper_attrs );
298 305 }
299 306
300 307 if ( empty( $toc_html ) ) {
301 - return get_empty_blocks_message( $is_backend, $attributes, $title_level, $alignclass, $title_text, __( 'No headings found.', 'simpletoc' ), __( 'Check minimal and maximum level block settings.', 'simpletoc' ), $wrapper_attrs, $has_wrapper );
308 + return get_empty_blocks_message( $is_backend, $attributes, $title_level, $alignclass, $title_text, __( 'No headings found.', 'simpletoc' ), __( 'Check minimal and maximum level block settings.', 'simpletoc' ), $wrapper_attrs );
302 309 }
303 310
304 311 return $pre_html . $toc_html . $post_html;
305 312 }
@@ -313,27 +320,20 @@
313 320 * @param string $alignclass The CSS class for alignment of the Table of Contents block.
314 321 * @param string $title_text The text for the Table of Contents title.
315 322 * @param string $warning_text1 The first part of the warning message to be displayed.
316 323 * @param string $warning_text2 The second part of the warning message to be displayed.
317 - * @param string $wrapper_attrs Wrapper attributes for the optional block wrapper.
318 - * @param bool $has_wrapper Indicates if the wrapper should be rendered.
324 + * @param string $wrapper_attrs Block wrapper attributes.
319 325 *
320 326 * @return string The HTML output for the empty blocks message.
321 327 */
322 -function get_empty_blocks_message( $is_backend, $attributes, $title_level, $alignclass, $title_text, $warning_text1, $warning_text2, $wrapper_attrs = '', $has_wrapper = false ) {
328 +function get_empty_blocks_message( $is_backend, $attributes, $title_level, $alignclass, $title_text, $warning_text1, $warning_text2, $wrapper_attrs = '' ) {
323 329 $html = '';
324 330
325 331 if ( $is_backend ) {
326 - if ( $has_wrapper ) {
327 - $html .= '<div role="navigation" aria-label="' . esc_attr__( 'Table of Contents', 'simpletoc' ) . '" ' . $wrapper_attrs . '>';
328 - }
329 -
332 + $html .= '<div role="navigation" aria-label="' . esc_attr__( 'Table of Contents', 'simpletoc' ) . '" ' . $wrapper_attrs . '>';
330 333 $html .= sprintf( '<h%d class="%s">%s</h%d>', $title_level, esc_attr( trim( 'simpletoc-title ' . $alignclass ) ), $title_text, $title_level );
331 - $html .= sprintf( '<p class="components-notice is-warning %s">%s %s</p>', $alignclass, $warning_text1, $warning_text2 );
332 -
333 - if ( $has_wrapper ) {
334 - $html .= '</div>';
335 - }
334 + $html .= sprintf( '<p class="components-notice is-warning %s">%s %s</p>', esc_attr( $alignclass ), esc_html( $warning_text1 ), esc_html( $warning_text2 ) );
335 + $html .= '</div>';
336 336 }
337 337
338 338 return $html;
339 339 }
@@ -361,9 +361,9 @@
361 361 if ( isset( $blocks[ $block ]['blockName'] ) && 'core/heading' === $blocks[ $block ]['blockName'] ) {
362 362 // make sure its a headline.
363 363 foreach ( $headings as $heading => &$inner_heading ) {
364 364 if ( $inner_heading === $blocks[ $block ]['innerHTML'] ) {
365 - $inner_heading = preg_replace( '/(<h1|<h2|<h3|<h4|<h5|<h6)/i', '$1 data-page="' . $pages . '"', $blocks[ $block ]['innerHTML'] );
365 + $inner_heading = simpletoc_add_page_number_to_headline( $blocks[ $block ]['innerHTML'], $pages );
366 366 }
367 367 }
368 368 }
369 369 }
@@ -405,13 +405,10 @@
405 405 // search in groups.
406 406 $arr = array_merge( filter_headings_recursive( $inner_block ), $arr );
407 407 }
408 408 } else {
409 - if ( isset( $blocks['blockName'] ) && ( 'core/heading' === $blocks['blockName'] ) && 'core/heading' !== $inner_block ) {
410 - // make sure it's a headline.
411 - if ( preg_match( '/(<h1|<h2|<h3|<h4|<h5|<h6)/i', $inner_block ) ) {
412 - $arr[] = $inner_block;
413 - }
409 + if ( isset( $blocks['blockName'] ) && ( 'core/heading' === $blocks['blockName'] ) && 'core/heading' !== $inner_block && simpletoc_is_heading_html( $inner_block ) ) {
410 + $arr[] = $inner_block;
414 411 }
415 412
416 413 $supported_third_party_blocks = array(
417 414 'generateblocks/headline', /* GenerateBlocks 1.x */
@@ -428,14 +425,11 @@
428 425 'simpletoc_supported_third_party_blocks',
429 426 $supported_third_party_blocks
430 427 );
431 428
432 - if ( isset( $blocks['blockName'] ) && in_array( $blocks['blockName'], $supported_third_party_blocks, true ) && 'core/heading' !== $inner_block ) {
433 - // make sure it's a headline.
434 - if ( preg_match( '/(<h1|<h2|<h3|<h4|<h5|<h6)/i', $inner_block ) ) {
435 - $inner_block = simpletoc_maybe_replace_generateblocks_dynamic_tags( $inner_block, $blocks );
436 - $arr[] = $inner_block;
437 - }
429 + if ( isset( $blocks['blockName'] ) && in_array( $blocks['blockName'], $supported_third_party_blocks, true ) && 'core/heading' !== $inner_block && simpletoc_is_heading_html( $inner_block ) ) {
430 + $inner_block = simpletoc_maybe_replace_generateblocks_dynamic_tags( $inner_block, $blocks );
431 + $arr[] = $inner_block;
438 432 }
439 433 }
440 434 }
441 435
@@ -488,12 +482,11 @@
488 482 $html_wo_nbs = str_replace( '&nbsp;', ' ', $zero_punctuation );
489 483 // remove umlauts and accents.
490 484 $string_without_accents = remove_accents( $html_wo_nbs );
491 485 // Sanitizes a title, replacing whitespace and a few other characters with dashes.
492 - $sanitized_string = sanitize_title_with_dashes( $string_without_accents );
493 - // Encode for use in an url.
494 - $urlencoded = rawurlencode( $sanitized_string );
495 - return $urlencoded;
486 + // Already returns a URL-safe, percent-encoded slug for non-ASCII input, so no
487 + // further rawurlencode() is needed (that would double-encode the string).
488 + return sanitize_title_with_dashes( $string_without_accents );
496 489 }
497 490
498 491 /**
499 492 * Add additional plugin meta links to the SimpleTOC plugin page.
@@ -534,8 +527,113 @@
534 527 return class_exists( '\WP_HTML_Tag_Processor' );
535 528 }
536 529
537 530 /**
531 + * Creates an HTML Tag Processor for a valid HTML fragment.
532 + *
533 + * Parsed block content can contain non-string placeholders for nested blocks.
534 + * The WordPress HTML API accepts strings only.
535 + *
536 + * @param mixed $html The HTML fragment to inspect.
537 + * @return \WP_HTML_Tag_Processor|null The processor, or null when unavailable or invalid.
538 + */
539 +function simpletoc_get_html_tag_processor( $html ) {
540 + if ( ! is_string( $html ) || ! simpletoc_load_html_tag_processor() ) {
541 + return null;
542 + }
543 +
544 + return new \WP_HTML_Tag_Processor( $html );
545 +}
546 +
547 +/**
548 + * Returns true when the provided HTML contains a heading tag.
549 + *
550 + * @param string $html The HTML to inspect.
551 + * @return bool True when the HTML contains a heading tag.
552 + */
553 +function simpletoc_is_heading_html( $html ) {
554 + return false !== simpletoc_get_heading_depth( $html );
555 +}
556 +
557 +/**
558 + * Gets the first heading depth from an HTML fragment.
559 + *
560 + * @param string $html The HTML to inspect.
561 + * @return int|false The heading depth, or false when no heading was found.
562 + */
563 +function simpletoc_get_heading_depth( $html ) {
564 + $processor = simpletoc_get_html_tag_processor( $html );
565 +
566 + if ( $processor ) {
567 +
568 + while ( $processor->next_tag() ) {
569 + $tag_name = $processor->get_tag();
570 +
571 + if ( in_array( $tag_name, array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), true ) ) {
572 + return (int) substr( $tag_name, 1 );
573 + }
574 + }
575 + }
576 +
577 + return false;
578 +}
579 +
580 +/**
581 + * Adds a data-page attribute to the first heading in an HTML fragment.
582 + *
583 + * @param string $html The heading HTML.
584 + * @param int $page_number The page number to set.
585 + * @return string The updated HTML.
586 + */
587 +function simpletoc_add_page_number_to_headline( $html, $page_number ) {
588 + $processor = simpletoc_get_html_tag_processor( $html );
589 +
590 + if ( $processor ) {
591 +
592 + while ( $processor->next_tag() ) {
593 + if ( ! in_array( $processor->get_tag(), array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), true ) ) {
594 + continue;
595 + }
596 +
597 + $processor->set_attribute( 'data-page', (string) $page_number );
598 + return $processor->get_updated_html();
599 + }
600 + }
601 +
602 + return $html;
603 +}
604 +
605 +/**
606 + * Checks whether the first heading in an HTML fragment has the provided class.
607 + *
608 + * @param string $html The heading HTML.
609 + * @param string $class_name The class name to find.
610 + * @return bool True when the class exists.
611 + */
612 +function simpletoc_heading_has_class( $html, $class_name ) {
613 + $processor = simpletoc_get_html_tag_processor( $html );
614 +
615 + if ( $processor ) {
616 +
617 + while ( $processor->next_tag() ) {
618 + if ( ! in_array( $processor->get_tag(), array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), true ) ) {
619 + continue;
620 + }
621 +
622 + $class_attribute = $processor->get_attribute( 'class' );
623 +
624 + if ( ! is_string( $class_attribute ) ) {
625 + return false;
626 + }
627 +
628 + return in_array( $class_name, preg_split( '/\s+/', trim( $class_attribute ) ), true );
629 + }
630 + }
631 +
632 + return false;
633 +}
634 +
635 +/**
538 636 * Adds an ID attribute to all Heading tags in the provided HTML.
539 637 *
540 638 * @param string $html The HTML content to modify.
541 639 * @param SimpleTOC_Headline_Ids $headline_class_instance The instance of the SimpleTOC_Headline_Ids class.
@@ -542,8 +640,11 @@
542 640 * @param array $block The parsed block data.
543 641 * @return string The modified HTML content with ID attributes added to the Heading tags
544 642 */
545 643 function add_anchor_attribute( $html, $headline_class_instance = null, $block = array() ) {
644 + if ( ! is_string( $html ) ) {
645 + return $html;
646 + }
546 647
547 648 // remove non-breaking space entites from input HTML.
548 649 $html_wo_nbs = str_replace( '&nbsp;', ' ', $html );
549 650
@@ -551,11 +652,12 @@
551 652 if ( ! $html_wo_nbs ) {
552 653 return $html;
553 654 }
554 655
555 - if ( simpletoc_load_html_tag_processor() ) {
556 - $processor = new \WP_HTML_Tag_Processor( $html_wo_nbs );
656 + $processor = simpletoc_get_html_tag_processor( $html_wo_nbs );
557 657
658 + if ( $processor ) {
659 +
558 660 while ( $processor->next_tag() ) {
559 661 if ( ! in_array( $processor->get_tag(), array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), true ) ) {
560 662 continue;
561 663 }
@@ -666,13 +768,9 @@
666 768 * @param int $this_depth The depth level of the headline.
667 769 * @return bool True if the headline should be excluded, false otherwise.
668 770 */
669 771 function should_exclude_headline( $headline, $attributes, $this_depth ) {
670 - $exclude_headline = false;
671 - preg_match( '/class="([^"]+)"/', $headline, $matches );
672 - if ( ! empty( $matches[1] ) && strpos( $matches[1], 'simpletoc-hidden' ) !== false ) {
673 - $exclude_headline = true;
674 - }
772 + $exclude_headline = simpletoc_heading_has_class( $headline, 'simpletoc-hidden' );
675 773
676 774 return ( $this_depth > $attributes['max_level'] || $exclude_headline || $this_depth < $attributes['min_level'] );
677 775 }
678 776
@@ -687,9 +785,14 @@
687 785 $toc_headings = array();
688 786 $headline_ids = new SimpleTOC_Headline_Ids();
689 787
690 788 foreach ( $headings as $headline ) {
691 - $this_depth = (int) $headline[2];
789 + $this_depth = simpletoc_get_heading_depth( $headline );
790 +
791 + if ( false === $this_depth ) {
792 + continue;
793 + }
794 +
692 795 $title = trim( wp_strip_all_tags( $headline ) );
693 796 $custom_id = extract_id( $headline );
694 797 $link = $custom_id ? $custom_id : $headline_ids->get_headline_anchor( $title );
695 798
@@ -739,10 +842,12 @@
739 842 }
740 843 $list .= "</li>\n<li>";
741 844 }
742 845
743 - $page = get_page_number_from_headline( $toc_heading['headline'] );
744 - $list .= '<a href="' . $absolute_url . $page . '#' . $toc_heading['link'] . '">' . $toc_heading['title'] . '</a>' . PHP_EOL;
846 + $page = get_page_number_from_headline( $toc_heading['headline'] );
847 + $url = $absolute_url . $page . '#' . $toc_heading['link'];
848 + $href = $absolute_url ? esc_url( $url ) : esc_attr( $url );
849 + $list .= '<a href="' . $href . '">' . esc_html( $toc_heading['title'] ) . '</a>' . PHP_EOL;
745 850 }
746 851
747 852 if ( null !== $current_depth ) {
748 853 for ( $current_depth; $current_depth > $min_depth; $current_depth-- ) {
@@ -776,9 +881,9 @@
776 881 wp_enqueue_script(
777 882 'simpletoc-accordion',
778 883 plugin_dir_url( __FILE__ ) . 'assets/accordion.js',
779 884 array(),
780 - '6.9.0',
885 + SIMPLETOC_VERSION,
781 886 true
782 887 );
783 888
784 889 wp_enqueue_style(
@@ -784,9 +889,9 @@
784 889 wp_enqueue_style(
785 890 'simpletoc-accordion',
786 891 plugin_dir_url( __FILE__ ) . 'assets/accordion.css',
787 892 array(),
788 - '6.9.0'
893 + SIMPLETOC_VERSION
789 894 );
790 895 }
791 896
792 897 /**
@@ -802,9 +907,9 @@
802 907
803 908 if ( $is_hidden_enabled ) {
804 909 $title_text = $attributes['title_text'] ? esc_html( trim( $attributes['title_text'] ) ) : esc_html__( 'Table of Contents', 'simpletoc' );
805 910 $hidden_start = '<details class="simpletoc">
806 - <summary style="cursor: pointer;">' . $title_text . '</summary>';
911 + <summary>' . $title_text . '</summary>';
807 912 $html .= $hidden_start;
808 913 }
809 914
810 915 // If there are no items in the table of contents, return an empty string.
@@ -849,9 +954,9 @@
849 954 // Start and end HTML for accordion, if enabled.
850 955 $accordion_start = '';
851 956 if ( $is_accordion_enabled ) {
852 957 enqueue_accordion_frontend();
853 - $accordion_start = '<h2 style="margin: 0;"><button type="button" aria-expanded="false" aria-controls="simpletoc-content-container" class="simpletoc-collapsible">' . $title_text . '<span class="simpletoc-icon" aria-hidden="true"></span></button></h2><div id="simpletoc-content-container" class="simpletoc-content">';
958 + $accordion_start = '<h2 class="simpletoc-accordion-heading"><button type="button" aria-expanded="false" aria-controls="simpletoc-content-container" class="simpletoc-collapsible">' . $title_text . '<span class="simpletoc-icon" aria-hidden="true"></span></button></h2><div id="simpletoc-content-container" class="simpletoc-content">';
854 959 }
855 960
856 961 // Add the accordion start HTML to the output.
857 962 $html .= $accordion_start;
@@ -899,17 +1004,26 @@
899 1004 /**
900 1005 * Extracts the ID value from the provided heading HTML string.
901 1006 *
902 1007 * @param string $headline The heading HTML string to extract the ID value from.
903 - * @return mixed Returns the extracted ID value, or false if no ID value is found.
1008 + * @return string|false Returns the extracted ID value, or false if no ID value is found.
904 1009 */
905 1010 function extract_id( $headline ) {
906 - $pattern = '/id="([^"]*)"/';
907 - preg_match( $pattern, $headline, $matches );
908 - $id_value = $matches[1] ?? false;
1011 + $processor = simpletoc_get_html_tag_processor( $headline );
909 1012
910 - if ( false !== $id_value ) {
911 - return $id_value;
1013 + if ( $processor ) {
1014 +
1015 + while ( $processor->next_tag() ) {
1016 + if ( ! in_array( $processor->get_tag(), array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), true ) ) {
1017 + continue;
1018 + }
1019 +
1020 + $id_value = $processor->get_attribute( 'id' );
1021 +
1022 + if ( is_string( $id_value ) ) {
1023 + return $id_value;
1024 + }
1025 + }
912 1026 }
913 1027
914 1028 return false;
915 1029 }
@@ -920,10 +1034,11 @@
920 1034 * @param string $headline The headline string.
921 1035 * @return string The page number (in the format "X/") if it exists and is greater than 1, or an empty string otherwise.
922 1036 */
923 1037 function get_page_number_from_headline( $headline ) {
924 - if ( simpletoc_load_html_tag_processor() ) {
925 - $processor = new \WP_HTML_Tag_Processor( $headline );
1038 + $processor = simpletoc_get_html_tag_processor( $headline );
1039 +
1040 + if ( $processor ) {
926 1041
927 1042 while ( $processor->next_tag() ) {
928 1043 if ( ! in_array( $processor->get_tag(), array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), true ) ) {
929 1044 continue;