Easy Table of Contents - Version 2.0-rc7

Version Description

Download this release

Release Info

Developer shazahm1@hotmail.com
Plugin Icon 128x128 Easy Table of Contents
Version 2.0-rc7
Comparing to
See all releases

Code changes from version 2.0-rc6 to 2.0-rc7

README.txt CHANGED
@@ -2,7 +2,7 @@
2
  Contributors: shazahm1@hotmail.com
3
  Donate link: http://connections-pro.com/
4
  Tags: table of contents, toc
5
- Requires at least: 4.4
6
  Tested up to: 5.3
7
  Requires PHP: 5.6.20
8
  Stable tag: 1.7
@@ -46,7 +46,9 @@ Here are links to documentation pages for several of the premium templates for t
46
 
47
  = Roadmap =
48
  * Fragment caching for improved performance.
49
- * Customizer support.
 
 
50
 
51
  = Credit =
52
 
2
  Contributors: shazahm1@hotmail.com
3
  Donate link: http://connections-pro.com/
4
  Tags: table of contents, toc
5
+ Requires at least: 5.2
6
  Tested up to: 5.3
7
  Requires PHP: 5.6.20
8
  Stable tag: 1.7
46
 
47
  = Roadmap =
48
  * Fragment caching for improved performance.
49
+ * Improve SEO by adding options to add nofollow to TOC link and wrap TOC nav in noindex tag.
50
+ * Improve accessibility.
51
+ * Add Bullet and Arrow options for list counter style.
52
 
53
  = Credit =
54
 
easy-table-of-contents.php CHANGED
@@ -3,7 +3,7 @@
3
  * Plugin Name: Easy Table of Contents
4
  * Plugin URI: http://connections-pro.com/
5
  * Description: Adds a user friendly and fully automatic way to create and display a table of contents generated from the page content.
6
- * Version: 2.0-rc6
7
  * Author: Steven A. Zahm
8
  * Author URI: http://connections-pro.com/
9
  * Text Domain: easy-table-of-contents
@@ -26,9 +26,11 @@
26
  * @package Easy Table of Contents
27
  * @category Plugin
28
  * @author Steven A. Zahm
29
- * @version 2.0-rc6
30
  */
31
 
 
 
32
  // Exit if accessed directly
33
  if ( ! defined( 'ABSPATH' ) ) exit;
34
 
@@ -45,7 +47,7 @@ if ( ! class_exists( 'ezTOC' ) ) {
45
  * @since 1.0
46
  * @var string
47
  */
48
- const VERSION = '2.0-rc6';
49
 
50
  /**
51
  * Stores the instance of this class.
@@ -130,6 +132,7 @@ if ( ! class_exists( 'ezTOC' ) ) {
130
  require_once( EZ_TOC_PATH . 'includes/class.post.php' );
131
  require_once( EZ_TOC_PATH . 'includes/class.widget-toc.php' );
132
  require_once( EZ_TOC_PATH . 'includes/inc.functions.php' );
 
133
 
134
  require_once( EZ_TOC_PATH . 'includes/inc.plugin-compatibility.php' );
135
  }
@@ -323,63 +326,6 @@ if ( ! class_exists( 'ezTOC' ) ) {
323
  }
324
  }
325
 
326
- /**
327
- * Returns a string with all items from the $find array replaced with their matching
328
- * items in the $replace array. This does a one to one replacement (rather than globally).
329
- *
330
- * This function is multibyte safe.
331
- *
332
- * $find and $replace are arrays, $string is the haystack. All variables are passed by reference.
333
- *
334
- * @access private
335
- * @since 1.0
336
- * @static
337
- *
338
- * @param bool $find
339
- * @param bool $replace
340
- * @param string $string
341
- *
342
- * @return mixed|string
343
- */
344
- private static function mb_find_replace( &$find = false, &$replace = false, &$string = '' ) {
345
-
346
- if ( is_array( $find ) && is_array( $replace ) && $string ) {
347
-
348
- // check if multibyte strings are supported
349
- if ( function_exists( 'mb_strpos' ) ) {
350
-
351
- for ( $i = 0; $i < count( $find ); $i ++ ) {
352
-
353
- $string = mb_substr(
354
- $string,
355
- 0,
356
- mb_strpos( $string, $find[ $i ] )
357
- ) . // everything before $find
358
- $replace[ $i ] . // its replacement
359
- mb_substr(
360
- $string,
361
- mb_strpos( $string, $find[ $i ] ) + mb_strlen( $find[ $i ] )
362
- ) // everything after $find
363
- ;
364
- }
365
-
366
- } else {
367
-
368
- for ( $i = 0; $i < count( $find ); $i ++ ) {
369
-
370
- $string = substr_replace(
371
- $string,
372
- $replace[ $i ],
373
- strpos( $string, $find[ $i ] ),
374
- strlen( $find[ $i ] )
375
- );
376
- }
377
- }
378
- }
379
-
380
- return $string;
381
- }
382
-
383
  /**
384
  * Array search deep.
385
  *
@@ -633,29 +579,29 @@ if ( ! class_exists( 'ezTOC' ) ) {
633
  // if shortcode used or post not eligible, return content with anchored headings
634
  if ( strpos( $content, 'ez-toc-container' ) || ! $is_eligible ) {
635
 
636
- return self::mb_find_replace( $find, $replace, $content );
637
  }
638
 
639
  // else also add toc to content
640
  switch ( ezTOC_Option::get( 'position' ) ) {
641
 
642
  case 'top':
643
- $content = $html . self::mb_find_replace( $find, $replace, $content );
644
  break;
645
 
646
  case 'bottom':
647
- $content = self::mb_find_replace( $find, $replace, $content ) . $html;
648
  break;
649
 
650
  case 'after':
651
  $replace[0] = $replace[0] . $html;
652
- $content = self::mb_find_replace( $find, $replace, $content );
653
  break;
654
 
655
  case 'before':
656
  default:
657
  $replace[0] = $html . $replace[0];
658
- $content = self::mb_find_replace( $find, $replace, $content );
659
  }
660
 
661
  return $content;
3
  * Plugin Name: Easy Table of Contents
4
  * Plugin URI: http://connections-pro.com/
5
  * Description: Adds a user friendly and fully automatic way to create and display a table of contents generated from the page content.
6
+ * Version: 2.0-rc7
7
  * Author: Steven A. Zahm
8
  * Author URI: http://connections-pro.com/
9
  * Text Domain: easy-table-of-contents
26
  * @package Easy Table of Contents
27
  * @category Plugin
28
  * @author Steven A. Zahm
29
+ * @version 2.0-rc7
30
  */
31
 
32
+ use function Easy_Plugins\Table_Of_Contents\String\mb_find_replace;
33
+
34
  // Exit if accessed directly
35
  if ( ! defined( 'ABSPATH' ) ) exit;
36
 
47
  * @since 1.0
48
  * @var string
49
  */
50
+ const VERSION = '2.0-rc7';
51
 
52
  /**
53
  * Stores the instance of this class.
132
  require_once( EZ_TOC_PATH . 'includes/class.post.php' );
133
  require_once( EZ_TOC_PATH . 'includes/class.widget-toc.php' );
134
  require_once( EZ_TOC_PATH . 'includes/inc.functions.php' );
135
+ require_once( EZ_TOC_PATH . 'includes/inc.string-functions.php' );
136
 
137
  require_once( EZ_TOC_PATH . 'includes/inc.plugin-compatibility.php' );
138
  }
326
  }
327
  }
328
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  /**
330
  * Array search deep.
331
  *
579
  // if shortcode used or post not eligible, return content with anchored headings
580
  if ( strpos( $content, 'ez-toc-container' ) || ! $is_eligible ) {
581
 
582
+ return mb_find_replace( $find, $replace, $content );
583
  }
584
 
585
  // else also add toc to content
586
  switch ( ezTOC_Option::get( 'position' ) ) {
587
 
588
  case 'top':
589
+ $content = $html . mb_find_replace( $find, $replace, $content );
590
  break;
591
 
592
  case 'bottom':
593
+ $content = mb_find_replace( $find, $replace, $content ) . $html;
594
  break;
595
 
596
  case 'after':
597
  $replace[0] = $replace[0] . $html;
598
+ $content = mb_find_replace( $find, $replace, $content );
599
  break;
600
 
601
  case 'before':
602
  default:
603
  $replace[0] = $html . $replace[0];
604
+ $content = mb_find_replace( $find, $replace, $content );
605
  }
606
 
607
  return $content;
includes/class.post.php CHANGED
@@ -292,7 +292,8 @@ class ezTOC_Post {
292
  *
293
  * @see wpautop()
294
  */
295
- $eligibleContent = str_replace( array( '<br>', '<br/>' ), array( '<br />' ), $eligibleContent );
 
296
 
297
  $pages[ $page ] = array(
298
  'headings' => $this->extractHeadings( $eligibleContent ),
292
  *
293
  * @see wpautop()
294
  */
295
+ //$eligibleContent = str_replace( array( '<br>', '<br/>' ), array( '<br />' ), $eligibleContent );
296
+ $eligibleContent = \Easy_Plugins\Table_Of_Contents\String\force_balance_tags( $eligibleContent );
297
 
298
  $pages[ $page ] = array(
299
  'headings' => $this->extractHeadings( $eligibleContent ),
includes/inc.string-functions.php ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Easy_Plugins\Table_Of_Contents\String;
4
+
5
+ /**
6
+ * Pulled from WordPress formatting functions.
7
+ *
8
+ * Edited to add space before self closing tags.
9
+ *
10
+ * @since 2.0
11
+ *
12
+ * @param string $text
13
+ *
14
+ * @return string|string[]
15
+ */
16
+ function force_balance_tags( $text ) {
17
+ $tagstack = array();
18
+ $stacksize = 0;
19
+ $tagqueue = '';
20
+ $newtext = '';
21
+ // Known single-entity/self-closing tags
22
+ $single_tags = array( 'area', 'base', 'basefont', 'br', 'col', 'command', 'embed', 'frame', 'hr', 'img', 'input', 'isindex', 'link', 'meta', 'param', 'source' );
23
+ // Tags that can be immediately nested within themselves
24
+ $nestable_tags = array( 'blockquote', 'div', 'object', 'q', 'span' );
25
+
26
+ // WP bug fix for comments - in case you REALLY meant to type '< !--'
27
+ $text = str_replace( '< !--', '< !--', $text );
28
+ // WP bug fix for LOVE <3 (and other situations with '<' before a number)
29
+ $text = preg_replace( '#<([0-9]{1})#', '&lt;$1', $text );
30
+
31
+ /**
32
+ * Matches supported tags.
33
+ *
34
+ * To get the pattern as a string without the comments paste into a PHP
35
+ * REPL like `php -a`.
36
+ *
37
+ * @see https://html.spec.whatwg.org/#elements-2
38
+ * @see https://w3c.github.io/webcomponents/spec/custom/#valid-custom-element-name
39
+ *
40
+ * @example
41
+ * ~# php -a
42
+ * php > $s = [paste copied contents of expression below including parentheses];
43
+ * php > echo $s;
44
+ */
45
+ $tag_pattern = (
46
+ '#<' . // Start with an opening bracket.
47
+ '(/?)' . // Group 1 - If it's a closing tag it'll have a leading slash.
48
+ '(' . // Group 2 - Tag name.
49
+ // Custom element tags have more lenient rules than HTML tag names.
50
+ '(?:[a-z](?:[a-z0-9._]*)-(?:[a-z0-9._-]+)+)' .
51
+ '|' .
52
+ // Traditional tag rules approximate HTML tag names.
53
+ '(?:[\w:]+)' .
54
+ ')' .
55
+ '(?:' .
56
+ // We either immediately close the tag with its '>' and have nothing here.
57
+ '\s*' .
58
+ '(/?)' . // Group 3 - "attributes" for empty tag.
59
+ '|' .
60
+ // Or we must start with space characters to separate the tag name from the attributes (or whitespace).
61
+ '(\s+)' . // Group 4 - Pre-attribute whitespace.
62
+ '([^>]*)' . // Group 5 - Attributes.
63
+ ')' .
64
+ '>#' // End with a closing bracket.
65
+ );
66
+
67
+ while ( preg_match( $tag_pattern, $text, $regex ) ) {
68
+ $full_match = $regex[0];
69
+ $has_leading_slash = ! empty( $regex[1] );
70
+ $tag_name = $regex[2];
71
+ $tag = strtolower( $tag_name );
72
+ $is_single_tag = in_array( $tag, $single_tags, true );
73
+ $pre_attribute_ws = isset( $regex[4] ) ? $regex[4] : '';
74
+ $attributes = trim( isset( $regex[5] ) ? $regex[5] : $regex[3] );
75
+ $has_self_closer = '/' === substr( $attributes, -1 );
76
+
77
+ $newtext .= $tagqueue;
78
+
79
+ $i = strpos( $text, $full_match );
80
+ $l = strlen( $full_match );
81
+
82
+ // Clear the shifter.
83
+ $tagqueue = '';
84
+ if ( $has_leading_slash ) { // End Tag.
85
+ // If too many closing tags.
86
+ if ( $stacksize <= 0 ) {
87
+ $tag = '';
88
+ // Or close to be safe $tag = '/' . $tag.
89
+
90
+ // If stacktop value = tag close value, then pop.
91
+ } elseif ( $tagstack[ $stacksize - 1 ] === $tag ) { // Found closing tag.
92
+ $tag = '</' . $tag . '>'; // Close Tag.
93
+ array_pop( $tagstack );
94
+ $stacksize--;
95
+ } else { // Closing tag not at top, search for it.
96
+ for ( $j = $stacksize - 1; $j >= 0; $j-- ) {
97
+ if ( $tagstack[ $j ] === $tag ) {
98
+ // Add tag to tagqueue.
99
+ for ( $k = $stacksize - 1; $k >= $j; $k-- ) {
100
+ $tagqueue .= '</' . array_pop( $tagstack ) . '>';
101
+ $stacksize--;
102
+ }
103
+ break;
104
+ }
105
+ }
106
+ $tag = '';
107
+ }
108
+ } else { // Begin Tag.
109
+ if ( $has_self_closer ) { // If it presents itself as a self-closing tag...
110
+ // ...but it isn't a known single-entity self-closing tag, then don't let it be treated as such and
111
+ // immediately close it with a closing tag (the tag will encapsulate no text as a result)
112
+ if ( ! $is_single_tag ) {
113
+ $attributes = trim( substr( $attributes, 0, -1 ) ) . "></$tag";
114
+ }
115
+ } elseif ( $is_single_tag ) { // ElseIf it's a known single-entity tag but it doesn't close itself, do so
116
+ $pre_attribute_ws = ' ';
117
+ $attributes .= 0 < strlen( $attributes ) ? ' /' : '/'; // EDIT: If there are attributes, add space before closing tag to match how WP insert br, hr and img tags.
118
+ } else { // It's not a single-entity tag.
119
+ // If the top of the stack is the same as the tag we want to push, close previous tag.
120
+ if ( $stacksize > 0 && ! in_array( $tag, $nestable_tags, true ) && $tagstack[ $stacksize - 1 ] === $tag ) {
121
+ $tagqueue = '</' . array_pop( $tagstack ) . '>';
122
+ $stacksize--;
123
+ }
124
+ $stacksize = array_push( $tagstack, $tag );
125
+ }
126
+
127
+ // Attributes.
128
+ if ( $has_self_closer && $is_single_tag ) {
129
+ // We need some space - avoid <br/> and prefer <br />.
130
+ $pre_attribute_ws = ' ';
131
+ }
132
+
133
+ $tag = '<' . $tag . $pre_attribute_ws . $attributes . '>';
134
+ // If already queuing a close tag, then put this tag on too.
135
+ if ( ! empty( $tagqueue ) ) {
136
+ $tagqueue .= $tag;
137
+ $tag = '';
138
+ }
139
+ }
140
+ $newtext .= substr( $text, 0, $i ) . $tag;
141
+ $text = substr( $text, $i + $l );
142
+ }
143
+
144
+ // Clear Tag Queue.
145
+ $newtext .= $tagqueue;
146
+
147
+ // Add remaining text.
148
+ $newtext .= $text;
149
+
150
+ while ( $x = array_pop( $tagstack ) ) {
151
+ $newtext .= '</' . $x . '>'; // Add remaining tags to close.
152
+ }
153
+
154
+ // WP fix for the bug with HTML comments.
155
+ $newtext = str_replace( '< !--', '<!--', $newtext );
156
+ $newtext = str_replace( '< !--', '< !--', $newtext );
157
+
158
+ return $newtext;
159
+ }
160
+
161
+ /**
162
+ * Multibyte substr_replace(). The mbstring library does not come with a multibyte equivalent of substr_replace().
163
+ * This function behaves exactly like substr_replace() even when the arguments are arrays.
164
+ *
165
+ * @link https://gist.github.com/stemar/8287074
166
+ *
167
+ * @since 2.0
168
+ *
169
+ * @param $string
170
+ * @param $replacement
171
+ * @param $start
172
+ * @param null $length
173
+ *
174
+ * @return array|string
175
+ */
176
+ function mb_substr_replace( $string, $replacement, $start, $length = null ) {
177
+
178
+ if ( is_array( $string ) ) {
179
+
180
+ $num = count( $string );
181
+
182
+ // $replacement
183
+ $replacement = is_array( $replacement ) ? array_slice( $replacement, 0, $num ) : array_pad( array( $replacement ), $num, $replacement );
184
+
185
+ // $start
186
+ if ( is_array( $start ) ) {
187
+ $start = array_slice( $start, 0, $num );
188
+ foreach ( $start as $key => $value ) {
189
+ $start[ $key ] = is_int( $value ) ? $value : 0;
190
+ }
191
+ } else {
192
+ $start = array_pad( array( $start ), $num, $start );
193
+ }
194
+
195
+ // $length
196
+ if ( ! isset( $length ) ) {
197
+ $length = array_fill( 0, $num, 0 );
198
+ } elseif ( is_array( $length ) ) {
199
+ $length = array_slice( $length, 0, $num );
200
+ foreach ( $length as $key => $value ) {
201
+ $length[ $key ] = isset( $value ) ? ( is_int( $value ) ? $value : $num ) : 0;
202
+ }
203
+ } else {
204
+ $length = array_pad( array( $length ), $num, $length );
205
+ }
206
+
207
+ // Recursive call
208
+ return array_map( __FUNCTION__, $string, $replacement, $start, $length );
209
+ }
210
+
211
+ preg_match_all( '/./us', (string) $string, $smatches );
212
+ preg_match_all( '/./us', (string) $replacement, $rmatches );
213
+
214
+ if ( $length === null ) {
215
+
216
+ $length = mb_strlen( $string );
217
+ }
218
+
219
+ array_splice( $smatches[0], $start, $length, $rmatches[0] );
220
+
221
+ return join( $smatches[0] );
222
+ }
223
+
224
+ /**
225
+ * Returns a string with all items from the $find array replaced with their matching
226
+ * items in the $replace array. This does a one to one replacement (rather than globally).
227
+ *
228
+ * This function is multibyte safe.
229
+ *
230
+ * $find and $replace are arrays, $string is the haystack. All variables are passed by reference.
231
+ *
232
+ * @since 1.0
233
+ *
234
+ * @param bool $find
235
+ * @param bool $replace
236
+ * @param string $string
237
+ *
238
+ * @return mixed|string
239
+ */
240
+ function mb_find_replace( &$find = false, &$replace = false, &$string = '' ) {
241
+
242
+ if ( is_array( $find ) && is_array( $replace ) && $string ) {
243
+
244
+ // check if multibyte strings are supported
245
+ if ( function_exists( 'mb_strpos' ) ) {
246
+
247
+ //for ( $i = 0; $i < count( $find ); $i ++ ) {
248
+ //
249
+ // $string = mb_substr(
250
+ // $string,
251
+ // 0,
252
+ // mb_strpos( $string, $find[ $i ] )
253
+ // ) . // everything before $find
254
+ // $replace[ $i ] . // its replacement
255
+ // mb_substr(
256
+ // $string,
257
+ // mb_strpos( $string, $find[ $i ] ) + mb_strlen( $find[ $i ] )
258
+ // ) // everything after $find
259
+ // ;
260
+ //}
261
+
262
+ for ( $i = 0; $i < count( $find ); $i ++ ) {
263
+
264
+ $start = mb_strpos( $string, $find[ $i ] );
265
+ $length = mb_strlen( $find[ $i ] );
266
+
267
+ /*
268
+ * `mb_strpos()` can return `false`. Only process `mb_substr_replace()` if position in string is found.
269
+ */
270
+ if ( is_int( $start ) ) {
271
+
272
+ $string = mb_substr_replace( $string, $replace[ $i ], $start, $length );
273
+ }
274
+
275
+ }
276
+
277
+ } else {
278
+
279
+ for ( $i = 0; $i < count( $find ); $i ++ ) {
280
+
281
+ $start = strpos( $string, $find[ $i ] );
282
+ $length = strlen( $find[ $i ] );
283
+
284
+ /*
285
+ * `strpos()` can return `false`. Only process `substr_replace()` if position in string is found.
286
+ */
287
+ if ( is_int( $start ) ) {
288
+
289
+ $string = substr_replace( $string, $replace[ $i ], $start, $length );
290
+ }
291
+ }
292
+ }
293
+ }
294
+
295
+ return $string;
296
+ }