PluginProbe
Advanced Excerpt / 3.0
Advanced Excerpt v3.0
trunk 0.2.2 3.0 3.1 4.0 4.1 4.1.1 4.2 4.2.1 4.2.2 4.2.3 4.2.4 4.2.5 4.2.6 4.2.7 4.2.8 4.2.9 4.3.0 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.3.7 All 30 releases
advanced-excerpt / advanced-excerpt.php

advanced-excerpt.php in Advanced Excerpt 3.0, at advanced-excerpt.php

507 lines 19.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
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
10 Copyright 2007 Bas van Doren
11
12 This program is free software; you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation; either version 3 of the License, or
15 (at your option) any later version.
16
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with this program. If not, see <http://www.gnu.org/licenses/>.
24 */
25
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;