Advanced Excerpt - Version 3.0

Version Description

  • Major release since 0.2.2 (also removed the 0. prefix from the version number)
  • Feature: Shortcodes can be removed from the excerpt
  • Feature: Virtually any HTML tag may now be stripped
  • Feature: A read-more link with custom text can be added
  • Fix: Word-based trimming speed improved
  • Fix: Template tag function improved
  • Fix: Improved ellipsis placement
Download this release

Release Info

Developer basvd
Plugin Icon wp plugin Advanced Excerpt
Version 3.0
Comparing to
See all releases

Code changes from version 0.2.2 to 3.0

Files changed (3) hide show
  1. advanced-excerpt.js +70 -0
  2. advanced-excerpt.php +467 -300
  3. readme.txt +26 -6
advanced-excerpt.js ADDED
@@ -0,0 +1,70 @@
1
+ // Advanced Excerpt options script
2
+ jQuery(function($)
3
+ {
4
+ var plugin_prefix = 'advancedexcerpt';
5
+ var tag_cols = $('#' + plugin_prefix + '_tags_table tr:eq(1) td').length;
6
+
7
+ var tag_list = new Array();
8
+ $('#' + plugin_prefix + '_tags_table input').each(function(i, el)
9
+ {
10
+ tag_list.push($(el).val());
11
+ });
12
+
13
+ // Add a tag to the checkbox table
14
+ $('#' + plugin_prefix + '_add_tag').click(function(event)
15
+ {
16
+ event.preventDefault();
17
+
18
+ var tag = $('#' + plugin_prefix + '_more_tags option:selected').val();
19
+
20
+ // No duplicate tags in the table
21
+ if($.inArray(tag, tag_list) > -1)
22
+ {
23
+ return;
24
+ }
25
+ tag_list.push(tag);
26
+
27
+ var last_row = $('#' + plugin_prefix + '_tags_table tr:last-child');
28
+ var tag_count = last_row.find('input').length;
29
+ var tag_cell = $(
30
+ '<td>' +
31
+ '<input name="' + plugin_prefix + '_allowed_tags[]" type="checkbox" value="' + tag + '" checked="checked" />' +
32
+ '<code>' + tag + '</code>' +
33
+ '</td>'
34
+ );
35
+
36
+ if(tag_count < tag_cols)
37
+ {
38
+ // Add to last row
39
+ var span = last_row.find('td[colspan]');
40
+ if(span.attr('colspan') > 1)
41
+ {
42
+ span.attr('colspan', span.attr('colspan') - 1);
43
+ tag_cell.insertBefore(span);
44
+ }
45
+ else
46
+ {
47
+ span.replaceWith(tag_cell);
48
+ }
49
+ }
50
+ else
51
+ {
52
+ // New row
53
+ $('<tr><td colspan="' + (tag_cols - 1) + '">&nbsp;</td></tr>').insertAfter(last_row).prepend(tag_cell);
54
+ }
55
+ });
56
+
57
+ // Check all boxes
58
+ $('#' + plugin_prefix + '_select_all').click(function(event)
59
+ {
60
+ event.preventDefault();
61
+ $('input[name="' + plugin_prefix + '_allowed_tags[]"]:gt(0)').attr('checked', 'checked');
62
+ });
63
+
64
+ // Uncheck all boxes
65
+ $('#' + plugin_prefix + '_select_none').click(function(event)
66
+ {
67
+ event.preventDefault();
68
+ $('input[name="' + plugin_prefix + '_allowed_tags[]"]:gt(0)').removeAttr('checked');
69
+ });
70
+ });
advanced-excerpt.php CHANGED
@@ -3,7 +3,7 @@
3
Plugin Name: Advanced Excerpt
4
Plugin URI: http://sparepencil.com/code/advanced-excerpt/
5
Description: Several improvements over WP's default excerpt. The size of the excerpt can be limited using character or word count, and HTML markup is not removed.
6
- Version: 0.2.2
7
Author: Bas van Doren
8
Author URI: http://sparepencil.com/
9
@@ -26,315 +26,482 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
26
if(!class_exists('AdvancedExcerpt')) :
27
class AdvancedExcerpt
28
{
29
- var $name;
30
- var $text_domain;
31
- var $mb;
32
-
33
- var $skip_next_call;
34
-
35
- function AdvancedExcerpt()
36
- {
37
- $this->name = strtolower(get_class($this));
38
- $this->text_domain = $this->name;
39
- $this->skip_next_call = false;
40
- $this->charset = get_bloginfo('charset');
41
-
42
- // Carefully support multibyte languages
43
- if(extension_loaded('mbstring') && function_exists('mb_list_encodings'))
44
- $this->mb = in_array($this->charset, mb_list_encodings());
45
-
46
- load_plugin_textdomain($this->text_domain, PLUGINDIR . '/advanced-excerpt/');
47
-
48
- register_activation_hook(__FILE__, array(&$this, 'install'));
49
-
50
- // Nothing to do on deactivation
51
- //register_deactivation_hook(__FILE__, array(&$this, 'uninstall'));
52
-
53
- add_action('admin_menu', array(&$this, 'add_pages'));
54
-
55
- // Replace the default filter (see /wp-includes/default-filters.php)
56
- remove_filter('get_the_excerpt', 'wp_trim_excerpt');
57
- add_filter('get_the_excerpt', array(&$this, 'filter'));
58
- }
59
-
60
- function __construct()
61
- {
62
- self::AdvancedExcerpt();
63
- }
64
-
65
- function filter($text, $length = null, $use_words = null, $ellipsis = null, $allowed_tags = null, $no_custom = null, $api_used = false)
66
- {
67
- global $id, $post;
68
-
69
- // Avoid trouble when the API (aka template tag) was used
70
- if($this->skip_next_call)
71
- {
72
- $this->skip_next_call = false;
73
- return $text;
74
- }
75
- $this->skip_next_call = $api_used;
76
-
77
- $no_custom = (!is_null($no_custom)) ? (int) (bool) $no_custom : get_option($this->name . '_no_custom');
78
-
79
- // Only make the excerpt if it does not exist or 'No Custom Excerpt' is set to true
80
- if('' == $text || $no_custom)
81
- {
82
- $length = (!is_null($length)) ? (int) $length : get_option($this->name . '_length');
83
- $use_words = (!is_null($use_words)) ? (int) (bool) $use_words : get_option($this->name . '_use_words');
84
- $ellipsis = (!is_null($ellipsis)) ? $ellipsis : get_option($this->name . '_ellipsis');
85
-
86
- $allowed_tags = (is_array($allowed_tags)) ? $allowed_tags : get_option($this->name . '_allowed_tags');
87
- $allowed_tags = '<' . implode('><', $allowed_tags) . '>';
88
-
89
- $text = apply_filters('the_content', get_the_content(''));
90
-
91
- // From the default wp_trim_excerpt():
92
- // Some kind of precaution against malformed CDATA in RSS feeds I suppose
93
- $text = str_replace(']]>', ']]&gt;', $text);
94
- $text = strip_tags($text, $allowed_tags);
95
-
96
- if(1 == $use_words)
97
- {
98
- // Count words, not HTML tags
99
- if($length > count(preg_split('/[\s]+/', strip_tags($text), -1)))
100
- return $text;
101
-
102
- // Now we start counting
103
- $text_bits = preg_split('/([\s]+)/', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
104
- $in_tag = false;
105
- $n_words = 0;
106
- $text = '';
107
- foreach($text_bits as $chunk)
108
- {
109
- // Determine whether a tag is opened (and not immediately closed) in this chunk
110
- if(0 < preg_match('/<[^>]*#x2F;s', $chunk))
111
- $in_tag = true;
112
- elseif(0 < preg_match('/>[^<]*#x2F;s', $chunk))
113
- $in_tag = false;
114
-
115
- // This should check if there is a word before the tag
116
- // but it doesn't work very well
117
- /*
118
- if($in_tag && $this->substr($chunk, 0, 1) != '<')
119
- $n_words++;
120
- */
121
-
122
- // Is there a word?
123
- if(!$in_tag && '' != trim($chunk) && $this->substr($chunk, -1, 1) != '>')
124
- $n_words++;
125
-
126
- $text .= $chunk;
127
-
128
- if($n_words >= $length && !$in_tag)
129
- break;
130
- }
131
- $text = $text . $ellipsis;
132
- }
133
- else
134
- {
135
- // Count characters, not whitespace, not those belonging to HTML tags
136
- if($length > $this->strlen(strip_tags($text)))
137
- return $text;
138
-
139
- $in_tag = false;
140
- $n_chars = 0;
141
- for($i = 0; $n_chars < $length || $in_tag; $i++)
142
- {
143
- // Is the character worth counting (ie. not part of an HTML tag)
144
- if($this->substr($text, $i, 1) == '<')
145
- $in_tag = true;
146
- elseif($this->substr($text, $i, 1) == '>')
147
- $in_tag = false;
148
- elseif(!$in_tag && '' != trim($this->substr($text, $i, 1)))
149
- $n_chars++;
150
151
- // Prevent eternal loops (this could happen with incomplete HTML tags)
152
- if($i >= $this->strlen($text) - 1)
153
- break;
154
- }
155
- $text = $this->substr($text, 0, $i) . $ellipsis;
156
- }
157
- $text = force_balance_tags($text);
158
- }
159
- return $text;
160
- }
161
-
162
- function update_options()
163
- {
164
- $length = (int) $_POST[$this->name . '_length'];
165
- $use_words = ('on' == $_POST[$this->name . '_use_words']) ? 1 : 0 ;
166
- $no_custom = ('on' == $_POST[$this->name . '_no_custom']) ? 1 : 0 ;
167
-
168
- $ellipsis = (get_magic_quotes_gpc() == 1) ? stripslashes($_POST[$this->name . '_ellipsis']) : $_POST[$this->name . '_ellipsis'];
169
- $ellipsis = $ellipsis;
170
-
171
- $allowed_tags = (array) $_POST[$this->name . '_allowed_tags'];
172
-
173
- update_option($this->name . '_length', $length);
174
- update_option($this->name . '_use_words', $use_words);
175
- update_option($this->name . '_no_custom', $no_custom);
176
- update_option($this->name . '_ellipsis', $ellipsis);
177
- update_option($this->name . '_allowed_tags', $allowed_tags);
178
- ?>
179
- <div id="message" class="updated fade"><p>Options saved.</p></div>
180
- <?php
181
- }
182
-
183
-
184
- function page_options()
185
- {
186
- global $allowedposttags;
187
-
188
- if ('POST' == $_SERVER['REQUEST_METHOD'])
189
- {
190
- check_admin_referer($this->name . '_update_options');
191
- $this->update_options();
192
- }
193
-
194
- $length = get_option($this->name . '_length');
195
- $use_words = get_option($this->name . '_use_words');
196
- $no_custom = get_option($this->name . '_no_custom');
197
- $ellipsis = htmlentities(get_option($this->name . '_ellipsis'));
198
- $allowed_tags = get_option($this->name . '_allowed_tags');
199
?>
200
<div class="wrap">
201
- <div id="icon-options-general" class="icon32"><br /></div>
202
- <h2><?php _e("Advanced Excerpt Options", $this->text_domain); ?></h2>
203
- <form method="post" action="">
204
- <?php
205
- if ( function_exists('wp_nonce_field') )
206
- wp_nonce_field($this->name . '_update_options'); ?>
207
-
208
- <table class="form-table">
209
- <tr valign="top">
210
- <th scope="row"><label for="<?php echo $this->name ?>_length"><?php _e("Excerpt Length:", $this->text_domain); ?></label></th>
211
- <td>
212
- <input name="<?php echo $this->name ?>_length" type="text" id="<?php echo $this->name ?>_length" value="<?php echo $length; ?>" size="2" />
213
- <input name="<?php echo $this->name ?>_use_words" type="checkbox" id="<?php echo $this->name ?>_use_words" value="on" <?php echo (1 == $use_words) ? 'checked="checked" ': ''; ?>/> <?php _e("Use words?", $this->text_domain); ?>
214
- </td>
215
- </tr>
216
- <tr valign="top">
217
- <th scope="row"><label for="<?php echo $this->name ?>_ellipsis"><?php _e("Ellipsis:", $this->text_domain); ?></label></th>
218
- <td>
219
- <input name="<?php echo $this->name ?>_ellipsis" type="text" id="<?php echo $this->name ?>_ellipsis" value="<?php echo $ellipsis; ?>" size="5" /> <?php _e('(use <a href="http://www.w3schools.com/tags/ref_entities.asp">HTML entities</a>)', $this->text_domain); ?>
220
- <br />
221
- <?php _e("Will substitute the part of the post that is omitted in the excerpt.", $this->text_domain); ?>
222
- </td>
223
- </tr>
224
- <tr valign="top">
225
- <th scope="row"><label for="<?php echo $this->name ?>_no_custom"><?php _e("No Custom Excerpts:", $this->text_domain); ?></label></th>
226
- <td>
227
- <input name="<?php echo $this->name ?>_no_custom" type="checkbox" id="<?php echo $this->name ?>_no_custom" value="on" <?php echo (1 == $no_custom) ? 'checked="checked" ': ''; ?>/>
228
- <?php _e("Generate excerpts even if a post has a custom excerpt attached.", $this->text_domain); ?>
229
- </td>
230
- </tr>
231
- <tr valign="top">
232
- <th scope="row"><?php _e("Keep Markup:", $this->text_domain); ?></th>
233
- <td>
234
- <table>
235
<?php
236
- $i = 0;
237
- foreach($allowedposttags as $tag => $spec) :
238
- if(1 == $i / 4) : ?><tr><?php endif;
239
- $i++;
240
- ?>
241
- <td><input name="<?php echo $this->name ?>_allowed_tags[]" type="checkbox" id="<?php echo $this->name ?>_allow_<?php echo $tag; ?>" value="<?php echo $tag; ?>" <?php echo (in_array($tag, $allowed_tags)) ? 'checked="checked" ': ''; ?>/> <code><?php echo $tag; ?></code></td><?php if(1 == $i / 4) : $i = 0; ?></tr><?php endif;?>
242
<?php
243
- endforeach;
244
- if(1 != $i / 4) : ?><td colspan="<?php echo (4 - $i); ?>">&nbsp;</td></tr><?php endif;?>
245
- </table>
246
- <!--<?php _e("See <a href=\"http://xref.redalt.com/wptrunk/nav.htm?wp-includes/kses.php.source.htm\"><code>wp-includes/kses.php</code></a> if you want to control more tags.", $this->text_domain); ?>-->
247
- </td>
248
- </tr>
249
- </table>
250
- <p class="submit"><input type="submit" name="Submit" class="button-primary" value="<?php _e("Save Changes", $this->text_domain); ?>" /></p>
251
- </form>
252
</div>
253
- <?php
254
- }
255
-
256
- function add_pages()
257
- {
258
- add_options_page(__("Advanced Excerpt Options", $this->text_domain), __("Excerpt", $this->text_domain), 'manage_options', 'options-' . $this->name, array(&$this, 'page_options'));
259
- }
260
-
261
- function install()
262
- {
263
- global $allowedposttags;
264
- foreach($allowedposttags as $tag => $spec)
265
- $allowed_tags[] = $tag;
266
- add_option($this->name . '_length', 40);
267
- add_option($this->name . '_use_words', 1);
268
- add_option($this->name . '_no_custom', 0);
269
- add_option($this->name . '_ellipsis', '&hellip;');
270
- add_option($this->name . '_allowed_tags', $allowed_tags);
271
- }
272
-
273
- function uninstall()
274
- {
275
- // Nothing to do (note: deactivation hook is also disabled)
276
- }
277
-
278
- // Careful multibyte support (fallback to normal functions if not available)
279
- function substr($str, $start, $length = null)
280
- {
281
- $length = (is_null($length)) ? $this->strlen($str) : $length;
282
- if($this->mb)
283
- return mb_substr($str, $start, $length, $this->charset);
284
- else
285
- return substr($str, $start, $length);
286
- }
287
-
288
- function strlen($str)
289
- {
290
- if($this->mb)
291
- return mb_strlen($str, $this->charset);
292
- else
293
- return strlen($str);
294
- }
295
-
296
- // PHP seems to lack a simple complement function for arrays
297
- function array_complement($a, $b)
298
- {
299
- $a = array_unique($a);
300
- $b = array_unique($b);
301
- $c = array();
302
- foreach($a as $t)
303
- {
304
- if(!in_array($t, $b))
305
- $c[] = $t;
306
- }
307
- }
308
}
309
310
$advancedexcerpt = new AdvancedExcerpt();
311
312
- function the_advanced_excerpt($args = '')
313
{
314
- global $advancedexcerpt;
315
- $defaults = array(
316
- 'length' => get_option($advancedexcerpt->name . '_length'),
317
- 'use_words' => get_option($advancedexcerpt->name . '_use_words'),
318
- 'no_custom' => get_option($advancedexcerpt->name . '_no_custom'),
319
- // URL encode, because URL decode is used on this setting later
320
- 'ellipsis' => urlencode(get_option($advancedexcerpt->name . '_ellipsis')),
321
- 'allow_tags' => implode(',', get_option($advancedexcerpt->name . '_allowed_tags')),
322
- 'exclude_tags' => '');
323
-
324
- $r = wp_parse_args($args, $defaults);
325
-
326
- extract($r, EXTR_SKIP);
327
-
328
- $allow_tags = preg_split('/[\s,]+/', $allow_tags);
329
- $exclude_tags = preg_split('/[\s,]+/', $exclude_tags);
330
- $ellipsis = urldecode($ellipsis);
331
-
332
- // {allow_tags} - {exclude_tags}
333
- $allow_tags = $advancedexcerpt->array_complement($allow_tags, $exclude_tags);
334
-
335
- // All filters are applied, however, the advanced excerpt now behaves like a priorite 1 filter, instead of the default priority 10
336
- $text = $advancedexcerpt->filter('', $length, $use_words, $ellipsis, $allow_tags, $no_custom, true);
337
- $text = apply_filters('get_the_excerpt', $text);
338
- echo apply_filters('the_excerpt', $text);
339
}
340
endif;
3
Plugin Name: Advanced Excerpt
4
Plugin URI: http://sparepencil.com/code/advanced-excerpt/
5
Description: Several improvements over WP's default excerpt. The size of the excerpt can be limited using character or word count, and HTML markup is not removed.
6
+ Version: 3.0
7
Author: Bas van Doren
8
Author URI: http://sparepencil.com/
9
26
if(!class_exists('AdvancedExcerpt')) :
27
class AdvancedExcerpt
28
{
29
+ // Plugin configuration
30
+ var $name;
31
+ var $text_domain;
32
+ var $mb;
33
+
34
+ var $default_options;
35
+ var $custom_options;
36
+
37
+ // Tricky variable
38
+ var $skip_next_call;
39
+
40
+ // Reference arrays
41
+ // Basic HTML tags (determines which tags are in the checklist)
42
+ var $options_basic_tags = array(
43
+ 'a', 'abbr', 'acronym',
44
+ 'b', 'big', 'blockquote', 'br',
45
+ 'center', 'cite', 'code',
46
+ 'dd', 'del', 'div', 'dl', 'dt',
47
+ 'em',
48
+ 'form',
49
+ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr',
50
+ 'i', 'img', 'ins',
51
+ 'li',
52
+ 'ol',
53
+ 'p', 'pre',
54
+ 'q',
55
+ 's', 'small', 'span', 'strike', 'strong', 'sub', 'sup',
56
+ 'table', 'td', 'th', 'tr',
57
+ 'u', 'ul'
58
+ );
59
+
60
+ // HTML tags allowed in <body>
61
+ // <style> is <head>-only, but usage is often non-standard, so it's included here
62
+ var $options_body_tags = array(
63
+ 'a', 'abbr', 'acronym', 'address', 'applet', 'area',
64
+ 'b', 'bdo', 'big', 'blockquote', 'br', 'button',
65
+ 'caption', 'center', 'cite', 'code', 'col', 'colgroup',
66
+ 'dd', 'del', 'dfn', 'dir', 'div', 'dl', 'dt',
67
+ 'em',
68
+ 'fieldset', 'font', 'form', 'frame', 'frameset',
69
+ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr',
70
+ 'i', 'iframe', 'img', 'input', 'ins', 'isindex',
71
+ 'kbd',
72
+ 'label', 'legend', 'li',
73
+ 'map', 'menu',
74
+ 'noframes', 'noscript',
75
+ 'object', 'ol', 'optgroup', 'option',
76
+ 'p', 'param', 'pre',
77
+ 'q',
78
+ 's', 'samp', 'script', 'select', 'small', 'span', 'strike', 'strong', 'style', 'sub', 'sup',
79
+ 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'tr', 'tt',
80
+ 'u', 'ul',
81
+ 'var'
82
+ );
83
+
84
+ // (not used) HTML tags which may have content that should not be considered actual text
85
+ // TODO: Implement a way to remove tag + content if tag is not allowed (low priority)
86
+ var $non_text_tags = array(
87
+ 'applet', 'noframes', 'noscript', 'object', 'select', 'script', 'style'
88
+ );
89
+
90
91
+ function AdvancedExcerpt()
92
+ {
93
+ $this->name = strtolower(get_class($this));
94
+ $this->text_domain = $this->name;
95
+ $this->skip_next_call = false;
96
+ $this->charset = get_bloginfo('charset');
97
+
98
+ $this->load_options();
99
+
100
+ // Carefully support multibyte languages
101
+ if(extension_loaded('mbstring') && function_exists('mb_list_encodings'))
102
+ $this->mb = in_array($this->charset, mb_list_encodings());
103
+
104
+ load_plugin_textdomain($this->text_domain, PLUGINDIR . '/advanced-excerpt/');
105
+
106
+ register_activation_hook(__FILE__, array(&$this, 'install'));
107
+
108
+ //register_deactivation_hook(__FILE__, array(&$this, 'uninstall'));
109
+
110
+ add_action('admin_menu', array(&$this, 'add_pages'));
111
+
112
+ // Replace the default filter (see /wp-includes/default-filters.php)
113
+ remove_filter('get_the_excerpt', 'wp_trim_excerpt');
114
+ add_filter('get_the_excerpt', array(&$this, 'filter'));
115
+ }
116
+
117
+ function __construct()
118
+ {
119
+ self::AdvancedExcerpt();
120
+ }
121
+
122
+ function filter($text)
123
+ {
124
+ // Merge custom parameters
125
+ if(is_array($this->custom_options))
126
+ $r = array_merge($this->custom_options, $this->default_options);
127
+ else
128
+ $r = $this->default_options;
129
+
130
+ extract($r, EXTR_SKIP);
131
+
132
+ // Only make the excerpt if it does not exist or 'No Custom Excerpt' is set to true
133
+ if('' == $text || $no_custom)
134
+ {
135
+ // Get the full content and filter it
136
+ $text = get_the_content('');
137
+ if(1 == $no_shortcode)
138
+ $text = strip_shortcodes($text);
139
+ $text = apply_filters('the_content', $text);
140
+
141
+ // From the default wp_trim_excerpt():
142
+ // Some kind of precaution against malformed CDATA in RSS feeds I suppose
143
+ $text = str_replace(']]>', ']]&gt;', $text);
144
+
145
+ // Strip HTML if allow-all is not set
146
+ if(!in_array('_all', $allowed_tags))
147
+ {
148
+ if(count($allowed_tags) > 0)
149
+ $tag_string = '<' . implode('><', $allowed_tags) . '>';
150
+ else
151
+ $tag_string = '';
152
+ $text = strip_tags($text, $tag_string);
153
+ }
154
+
155
+ if(1 == $use_words)
156
+ {
157
+ // Words
158
+
159
+ // Skip if text is already within limit
160
+ if($length >= count(preg_split('/[\s]+/', strip_tags($text))))
161
+ return $text;
162
+
163
+ // Split on whitespace and start counting (for real)
164
+ $text_bits = preg_split('/([\s]+)/', $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
165
+ $in_tag = false;
166
+ $n_words = 0;
167
+ $text = '';
168
+ foreach($text_bits as $chunk)
169
+ {
170
+ if(!in_tag || strpos($chunk, '>') !== false)
171
+ $in_tag = (strrpos($chunk, '>') < strrpos($chunk, '<'));
172
+
173
+ // Whitespace outside tags is word separator
174
+ if(!$in_tag && '' == trim($chunk))
175
+ $n_words++;
176
+
177
+ if($n_words >= $length && !$in_tag)
178
+ break;
179
+
180
+ $text .= $chunk;
181
+ }
182
+ }
183
+ else
184
+ {
185
+ // Characters
186
+
187
+ // Count characters, not whitespace, not those belonging to HTML tags
188
+ if($length > $this->strlen(strip_tags($text)))
189
+ return $text;
190
+
191
+ $in_tag = false;
192
+ $n_chars = 0;
193
+ for($i = 0; $n_chars < $length || $in_tag; $i++)
194
+ {
195
+ // TODO: Improve this loop
196
+
197
+ $char = $this->substr($text, $i, 1);
198
+ // Is the character worth counting (ie. not part of an HTML tag)
199
+ if($char == '<')
200
+ $in_tag = true;
201
+ elseif($char == '>')
202
+ $in_tag = false;
203
+ elseif(!$in_tag && '' != trim($char))
204
+ $n_chars++;
205
+
206
+ // Prevent eternal loops (this could happen with incomplete HTML tags)
207
+ if($i >= $this->strlen($text) - 1)
208
+ break;
209
+ }
210
+ $text = $this->substr($text, 0, $i);
211
+ }
212
+ $text = force_balance_tags($text);
213
+
214
+ // New filter in WP2.9, seems unnecessary for now
215
+ //$ellipsis = apply_filters('excerpt_more', $ellipsis);
216
+
217
+ // Read more
218
+ if(1 == $add_link)
219
+ {
220
+ $ellipsis = $ellipsis . sprintf(' <a href="%s" class="read_more">%s</a>', get_permalink(), $read_more);
221
+ }
222
+
223
+ // Adding the ellipsis
224
+ if(($pos = strpos($text, '</p>', strlen($text) - 6)) !== false)
225
+ {
226
+ // Stay inside the last paragraph (if it's in the last 6 characters)
227
+ $text = substr_replace($text, $ellipsis, $pos, 0);
228
+ }
229
+ else
230
+ {
231
+ // If <p> is an allowed tag,
232
+ // wrap the ellipsis for consistency with excerpt markup
233
+ if(in_array('_all', $allowed_tags) || in_array('p', $allowed_tags))
234
+ $ellipsis = '<p>' . $ellipsis . '</p>';
235
+
236
+ $text = $text . $ellipsis;
237
+ }
238
+ }
239
+
240
+ return $text;
241
+ }
242
+
243
+ function install()
244
+ {
245
+ add_option($this->name . '_length', 40);
246
+ add_option($this->name . '_use_words', 1);
247
+ add_option($this->name . '_no_custom', 0);
248
+ add_option($this->name . '_no_shortcode', 1);
249
+ add_option($this->name . '_ellipsis', '&hellip;');
250
+ add_option($this->name . '_read_more', 'Read the rest');
251
+ add_option($this->name . '_add_link', 0);
252
+ add_option($this->name . '_allowed_tags', $this->options_basic_tags);
253
+
254
+ //$this->load_options();
255
+ }
256
+
257
+ function uninstall()
258
+ {
259
+ // Nothing to do (note: deactivation hook is also disabled)
260
+ }
261
+
262
+ function load_options()
263
+ {
264
+ $this->default_options = array(
265
+ 'length' => get_option($this->name . '_length'),
266
+ 'use_words' => get_option($this->name . '_use_words'),
267
+ 'no_custom' => get_option($this->name . '_no_custom'),
268
+ 'no_shortcode' => get_option($this->name . '_no_shortcode'),
269
+ 'ellipsis' => get_option($this->name . '_ellipsis'),
270
+ 'read_more' => get_option($this->name . '_read_more'),
271
+ 'add_link' => get_option($this->name . '_add_link'),
272
+ 'allowed_tags' => get_option($this->name . '_allowed_tags')
273
+ );
274
+ }
275
+
276
+
277
+ function update_options()
278
+ {
279
+ $length = (int) $_POST[$this->name . '_length'];
280
+ $use_words = ('on' == $_POST[$this->name . '_use_words']) ? 1 : 0 ;
281
+ $no_custom = ('on' == $_POST[$this->name . '_no_custom']) ? 1 : 0 ;
282
+ $no_shortcode = ('on' == $_POST[$this->name . '_no_shortcode']) ? 1 : 0 ;
283
+ $add_link = ('on' == $_POST[$this->name . '_add_link']) ? 1 : 0 ;
284
+
285
+ $ellipsis = (get_magic_quotes_gpc() == 1) ? stripslashes($_POST[$this->name . '_ellipsis']) : $_POST[$this->name . '_ellipsis'];
286
+ $read_more = (get_magic_quotes_gpc() == 1) ? stripslashes($_POST[$this->name . '_read_more']) : $_POST[$this->name . '_read_more'];
287
+
288
+ $allowed_tags = array_unique((array) $_POST[$this->name . '_allowed_tags']);
289
+
290
+ update_option($this->name . '_length', $length);
291
+ update_option($this->name . '_use_words', $use_words);
292
+ update_option($this->name . '_no_custom', $no_custom);
293
+ update_option($this->name . '_no_shortcode', $no_shortcode);
294
+ update_option($this->name . '_ellipsis', $ellipsis);
295
+ update_option($this->name . '_read_more', $read_more);
296
+ update_option($this->name . '_add_link', $add_link);
297
+ update_option($this->name . '_allowed_tags', $allowed_tags);
298
+
299
+ $this->load_options();
300
+ ?>
301
+ <div id="message" class="updated fade"><p>Options saved.</p></div>
302
+ <?php
303
+ }
304
+
305
+ function page_options()
306
+ {
307
+ if ('POST' == $_SERVER['REQUEST_METHOD'])
308
+ {
309
+ check_admin_referer($this->name . '_update_options');
310
+ $this->update_options();
311
+ }
312
+
313
+ extract($this->default_options, EXTR_SKIP);
314
+
315
+ // HTML entities for textbox
316
+ $ellipsis = htmlentities($ellipsis);
317
+ $read_more = htmlentities($read_more);
318
+
319
+ // Basic tags + enabled tags
320
+ $tag_list = $this->set_union($this->options_basic_tags, $allowed_tags);
321
+ sort($tag_list);
322
+ $tag_cols = 5;
323
?>
324
<div class="wrap">
325
+ <div id="icon-options-general" class="icon32"><br /></div>
326
+ <h2><?php _e("Advanced Excerpt Options", $this->text_domain); ?></h2>
327
+ <form method="post" action="">
328
+ <?php
329
+ if ( function_exists('wp_nonce_field') )
330
+ wp_nonce_field($this->name . '_update_options'); ?>
331
+
332
+ <table class="form-table">
333
+ <tr valign="top">
334
+ <th scope="row"><label for="<?php echo $this->name ?>_length"><?php _e("Excerpt Length:", $this->text_domain); ?></label></th>
335
+ <td>
336
+ <input name="<?php echo $this->name ?>_length" type="text" id="<?php echo $this->name ?>_length" value="<?php echo $length; ?>" size="2" />
337
+ <input name="<?php echo $this->name ?>_use_words" type="checkbox" id="<?php echo $this->name ?>_use_words" value="on" <?php echo (1 == $use_words) ? 'checked="checked" ': ''; ?>/> <?php _e("Use words?", $this->text_domain); ?>
338
+ </td>
339
+ </tr>
340
+ <tr valign="top">
341
+ <th scope="row"><label for="<?php echo $this->name ?>_ellipsis"><?php _e("Ellipsis:", $this->text_domain); ?></label></th>
342
+ <td>
343
+ <input name="<?php echo $this->name ?>_ellipsis" type="text" id="<?php echo $this->name ?>_ellipsis" value="<?php echo $ellipsis; ?>" size="5" /> <?php _e('(use <a href="http://www.w3schools.com/tags/ref_entities.asp">HTML entities</a>)', $this->text_domain); ?>
344
+ <br />
345
+ <?php _e("Will substitute the part of the post that is omitted in the excerpt.", $this->text_domain); ?>
346
+ </td>
347
+ </tr>
348
+ <tr valign="top">
349
+ <th scope="row"><label for="<?php echo $this->name ?>_read_more"><?php _e("&lsquo;Read-more&rsquo; Text:", $this->text_domain); ?></label></th>
350
+ <td>
351
+ <input name="<?php echo $this->name ?>_read_more" type="text" id="<?php echo $this->name ?>_read_more" value="<?php echo $read_more; ?>" />
352
+ <input name="<?php echo $this->name ?>_add_link" type="checkbox" id="<?php echo $this->name ?>_add_link" value="on" <?php echo (1 == $add_link) ? 'checked="checked" ': ''; ?>/> <?php _e("Add link to excerpt", $this->text_domain); ?>
353
+ </td>
354
+ </tr>
355
+ <tr valign="top">
356
+ <th scope="row"><label for="<?php echo $this->name ?>_no_custom"><?php _e("No Custom Excerpts:", $this->text_domain); ?></label></th>
357
+ <td>
358
+ <input name="<?php echo $this->name ?>_no_custom" type="checkbox" id="<?php echo $this->name ?>_no_custom" value="on" <?php echo (1 == $no_custom) ? 'checked="checked" ': ''; ?>/>
359
+ <?php _e("Generate excerpts even if a post has a custom excerpt attached.", $this->text_domain); ?>
360
+ </td>
361
+ </tr>
362
+ <tr valign="top">
363
+ <th scope="row"><label for="<?php echo $this->name ?>_no_shortcode"><?php _e("Strip Shortcodes:", $this->text_domain); ?></label></th>
364
+ <td>
365
+ <input name="<?php echo $this->name ?>_no_shortcode" type="checkbox" id="<?php echo $this->name ?>_no_shortcode" value="on" <?php echo (1 == $no_shortcode) ? 'checked="checked" ': ''; ?>/>
366
+ <?php _e("Remove shortcodes from the excerpt. <em>(recommended)</em>", $this->text_domain); ?>
367
+ </td>
368
+ </tr>
369
+ <tr valign="top">
370
+ <th scope="row"><?php _e("Keep Markup:", $this->text_domain); ?></th>
371
+ <td>
372
+ <table id="<?php echo $this->name ?>_tags_table">
373
+ <tr>
374
+ <td colspan="<?php echo $tag_cols; ?>">
375
+ <input name="<?php echo $this->name ?>_allowed_tags[]" type="checkbox" value="_all" <?php echo (in_array('_all', $allowed_tags)) ? 'checked="checked" ': ''; ?>/>
376
+ <?php _e("Don't remove any markup", $this->text_domain); ?>
377
+ </td>
378
+ </tr>
379
+ <?php
380
+ $i = 0;
381
+ foreach($tag_list as $tag) :
382
+ if($tag == '_all') continue;
383
+ if(0 == $i % $tag_cols) : ?>
384
+ <tr>
385
+ <?php
386
+ endif;
387
+ $i++;
388
+ ?>
389
+ <td>
390
+ <input name="<?php echo $this->name ?>_allowed_tags[]" type="checkbox" value="<?php echo $tag; ?>" <?php echo (in_array($tag, $allowed_tags)) ? 'checked="checked" ': ''; ?>/>
391
+ <code><?php echo $tag; ?></code>
392
+ </td>
393
+ <?php
394
+ if(0 == $i % $tag_cols) : $i = 0; ?>
395
+ </tr>
396
<?php
397
+ endif;
398
+ endforeach;
399
+ if(0 != $i % $tag_cols) : ?><td colspan="<?php echo ($tag_cols - $i); ?>">&nbsp;</td></tr><?php endif;?>
400
+ </table>
401
+ <a href="" id="<?php echo $this->name ?>_select_all">Select all</a> / <a href="" id="<?php echo $this->name ?>_select_none">Select none</a><br />
402
+ More tags:
403
+ <select name="<?php echo $this->name ?>_more_tags" id="<?php echo $this->name ?>_more_tags">
404
<?php
405
+ foreach($this->options_body_tags as $tag) :
406
+ ?>
407
+ <option value="<?php echo $tag; ?>"><?php echo $tag; ?></option>
408
+ <?php
409
+ endforeach;
410
+ ?>
411
+ </select>
412
+ <input type="button" name="<?php echo $this->name ?>_add_tag" id="<?php echo $this->name ?>_add_tag" class="button" value="Add tag" />
413
+ </td>
414
+ </tr>
415
+ </table>
416
+ <p class="submit"><input type="submit" name="Submit" class="button-primary" value="<?php _e("Save Changes", $this->text_domain); ?>" /></p>
417
+ </form>
418
</div>
419
+ <?php
420
+ }
421
+
422
+ function page_script()
423
+ {
424
+ wp_enqueue_script($this->name . '_script', WP_PLUGIN_URL . '/advanced-excerpt/advanced-excerpt.js', array('jquery'));
425
+ }
426
+
427
+ function add_pages()
428
+ {
429
+ $options_page = add_options_page(__("Advanced Excerpt Options", $this->text_domain), __("Excerpt", $this->text_domain), 'manage_options', 'options-' . $this->name, array(&$this, 'page_options'));
430
+
431
+ // Scripts
432
+ add_action('admin_print_scripts-' . $options_page, array(&$this, 'page_script'));
433
+ }
434
+
435
+ // Careful multibyte support (fallback to normal functions if not available)
436
+
437
+ function substr($str, $start, $length = null)
438
+ {
439
+ $length = (is_null($length)) ? $this->strlen($str) : $length;
440
+ if($this->mb)
441
+ return mb_substr($str, $start, $length, $this->charset);
442
+ else
443
+ return substr($str, $start, $length);
444
+ }
445
+
446
+ function strlen($str)
447
+ {
448
+ if($this->mb)
449
+ return mb_strlen($str, $this->charset);
450
+ else
451
+ return strlen($str);
452
+ }
453
+
454
+ // Some utility functions
455
+
456
+ function set_complement($a, $b)
457
+ {
458
+ $c = array_diff($a, $b);
459
+ return array_unique($c);
460
+ }
461
+
462
+ function set_union($a, $b)
463
+ {
464
+ $c = array_merge($a, $b);
465
+ return array_unique($c);
466
+ }
467
}
468
469
$advancedexcerpt = new AdvancedExcerpt();
470
471
+ // Do not use outside the Loop!
472
+ function the_advanced_excerpt($args = '', $get = true)
473
{
474
+ global $advancedexcerpt;
475
+
476
+ $r = wp_parse_args($args);
477
+
478
+ if(isset($r['ellipsis']))
479
+ $r['ellipsis'] = urldecode($r['ellipsis']);
480
+
481
+ // TODO: Switch to 'allowed_tags' (compatibility code)
482
+ if(isset($r['allow_tags']))
483
+ {
484
+ $r['allowed_tags'] = $r['allow_tags'];
485
+ unset($r['allow_tags']);
486
+ }
487
+
488
+ if(isset($r['allowed_tags']))
489
+ $r['allowed_tags'] = preg_split('/[\s,]+/', $r['allow_tags']);
490
+
491
+ if(isset($r['exclude_tags']))
492
+ {
493
+ $r['exclude_tags'] = preg_split('/[\s,]+/', $r['exclude_tags']);
494
+ // {all_tags} - {exclude_tags}
495
+ $r['allowed_tags'] = $advancedexcerpt->set_complement($advancedexcerpt->options_body_tags, $r['exclude_tags']);
496
+ unset($r['exclude_tags']);
497
+ }
498
+
499
+ // Set custom options (discard after use)
500
+ $advancedexcerpt->custom_options = $r;
501
+ if($get)
502
+ echo get_the_excerpt();
503
+ else
504
+ the_excerpt();
505
+ $advancedexcerpt->custom_options = null;
506
}
507
endif;
readme.txt CHANGED
@@ -3,8 +3,8 @@ Contributors: basvd
3
Tags: excerpt, advanced, post, posts, template, formatting
4
Donate link: http://sparepencil.com/code/advanced-excerpt/
5
Requires at least: 2.2
6
- Tested up to: 2.7
7
- Stable tag: trunk
8
9
Several improvements over WP's default excerpt. The size of the excerpt can be limited using character or word count, and HTML markup is not removed.
10
@@ -15,12 +15,17 @@ This plugin adds several improvements to WordPress' default way of creating exce
15
1. It can keep HTML markup in the excerpt (and you get to choose which tags are included)
16
2. It trims the excerpt to a given length using either character count or word count
17
3. You can customise the excerpt length and the ellipsis character that will be used when trimming
18
4. The excerpt length is *real* (everything belonging to HTML tags is not counted)
19
- 5. Possibility to ignore custom excerpts and use the generated one instead
20
6. Theme developers can use `the_advanced_excerpt()` for even more control (see the FAQ)
21
22
In addition to keeping HTML markup in the excerpt, the plugin also corrects HTML that might have been broken due to the trimming process.
23
24
Version 0.2.1 adds support for multibyte characters (eg. Chinese and Japanese). This is slightly experimental, more details in the FAQ.
25
Plugin translations are fully supported and language files are included for translation. The FAQ provides more info on this, also.
26
@@ -41,7 +46,7 @@ The default excerpt created by WordPress removes all HTML. If your theme uses `t
41
42
= Does it work for WordPress version x.x.x? =
43
44
- I haven't had the chance to test the plugin on many versions of WordPress. It has been tested on 2.2 through 2.7, but it might work on other versions, too. You can safely try it yourself, because the plugin is unlikely to break anything (it's only an output filter). Please let me know if you succesfully tested it on another version of WordPress.
45
46
= Is this plugin available in my language? / How do I translate this plugin? =
47
@@ -67,9 +72,12 @@ The following parameters can be set:
67
* `length`, an integer that determines the length of the excerpt
68
* `use_words`, if set to `1`, the excerpt length will be in words; if set to `0`, characters will be used for the count
69
* `no_custom`, if set to `1`, an excerpt will be generated even if the post has a custom excerpt; if set to `0`, the custom excerpt will be used
70
* `ellipsis`, the string that will substitute the omitted part of the post; if you want to use HTML entities in the string, use `%26` instead of the `&` prefix to avoid breaking the query
71
- * `exclude_tags`, a comma-separated list of HTML tags that must be removed from the excerpt
72
- * `allow_tags`, a comma-separated list of HTML tags that are allowed in the excerpt
73
74
A custom, advanced excerpt call could look like this:
75
@@ -79,3 +87,15 @@ A custom, advanced excerpt call could look like this:
79
80
No, this plugin fetches the post from The Loop and there is currently no way to pass a post ID or anything custom of that kind to it.
81
You can, however, consider to [start The Loop manually](http://codex.wordpress.org/The_Loop#Multiple_Loops).
3
Tags: excerpt, advanced, post, posts, template, formatting
4
Donate link: http://sparepencil.com/code/advanced-excerpt/
5
Requires at least: 2.2
6
+ Tested up to: 2.9
7
+ Stable tag: 0.2.2
8
9
Several improvements over WP's default excerpt. The size of the excerpt can be limited using character or word count, and HTML markup is not removed.
10
15
1. It can keep HTML markup in the excerpt (and you get to choose which tags are included)
16
2. It trims the excerpt to a given length using either character count or word count
17
3. You can customise the excerpt length and the ellipsis character that will be used when trimming
18
+ 4. A read-more link can be added automatically
19
4. The excerpt length is *real* (everything belonging to HTML tags is not counted)
20
+ 5. Can ignore custom excerpts and use the generated one instead
21
6. Theme developers can use `the_advanced_excerpt()` for even more control (see the FAQ)
22
23
In addition to keeping HTML markup in the excerpt, the plugin also corrects HTML that might have been broken due to the trimming process.
24
25
+ This plugin is also compatible with Shortcodes.
26
+
27
+ Version 3.0 may not be backwards compatible. Check and re-apply the settings after you upgrade to make sure everything works correctly.
28
+
29
Version 0.2.1 adds support for multibyte characters (eg. Chinese and Japanese). This is slightly experimental, more details in the FAQ.
30
Plugin translations are fully supported and language files are included for translation. The FAQ provides more info on this, also.
31
46
47
= Does it work for WordPress version x.x.x? =
48
49
+ I haven't had the chance to test the plugin on many versions of WordPress. It has recently been tested on 2.9, but it might work on other versions, too. You can safely try it yourself, because the plugin is unlikely to break anything (it's only an output filter). Please let me know if you succesfully tested it on another version of WordPress.
50
51
= Is this plugin available in my language? / How do I translate this plugin? =
52
72
* `length`, an integer that determines the length of the excerpt
73
* `use_words`, if set to `1`, the excerpt length will be in words; if set to `0`, characters will be used for the count
74
* `no_custom`, if set to `1`, an excerpt will be generated even if the post has a custom excerpt; if set to `0`, the custom excerpt will be used
75
+ * `no_shortcode`, if set to `1`, shortcodes are removed from the excerpt; if set to `0`, shortcodes will be parsed
76
* `ellipsis`, the string that will substitute the omitted part of the post; if you want to use HTML entities in the string, use `%26` instead of the `&` prefix to avoid breaking the query
77
+ * `read_more`, the text used in the read-more link
78
+ * `add_link`, if set to `1`, the read-more link will be appended; if `0`, no link will be added
79
+ * `allowed_tags`, a comma-separated list of HTML tags that are allowed in the excerpt. Entering `_all` will preserve all tags.
80
+ * `exclude_tags`, a comma-separated list of HTML tags that must be removed from the excerpt. Using this setting in combination with `allowed_tags` makes no sense
81
82
A custom, advanced excerpt call could look like this:
83
87
88
No, this plugin fetches the post from The Loop and there is currently no way to pass a post ID or anything custom of that kind to it.
89
You can, however, consider to [start The Loop manually](http://codex.wordpress.org/The_Loop#Multiple_Loops).
90
+
91
+ == Changelog ==
92
+
93
+ = 3.0 =
94
+
95
+ * Major release since 0.2.2 (also removed the `0.` prefix from the version number)
96
+ * Feature: Shortcodes can be removed from the excerpt
97
+ * Feature: Virtually any HTML tag may now be stripped
98
+ * Feature: A read-more link with custom text can be added
99
+ * Fix: Word-based trimming speed improved
100
+ * Fix: Template tag function improved
101
+ * Fix: Improved ellipsis placement