PluginProbe
Advanced Excerpt / 4.2
Advanced Excerpt v4.2
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 / class / advanced-excerpt.php

advanced-excerpt.php in Advanced Excerpt 4.2, at class/advanced-excerpt.php

402 lines 16.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 class Advanced_Excerpt {
3 public $options;
4
5 /*
6 * Some of the following options below are linked to checkboxes on the plugin's option page.
7 * If any checkbox options are added/removed/modified in the future please ensure you also update
8 * the $checkbox_options variable in the update_options() method.
9 */
10 public $default_options = array(
11 'length' => 40,
12 'length_type' => 'words',
13 'no_custom' => 1,
14 'no_shortcode' => 1,
15 'finish' => 'exact',
16 'ellipsis' => '&hellip;',
17 'read_more' => 'Read the rest',
18 'add_link' => 0,
19 'allowed_tags' => array(),
20 'the_excerpt' => 1,
21 'the_content' => 1,
22 'the_content_no_break' => 0,
23 'exclude_pages' => array(),
24 'allowed_tags_option' => 'dont_remove_any',
25 );
26
27 public $options_basic_tags; // Basic HTML tags (determines which tags are in the checklist by default)
28 public $options_all_tags; // Almost all HTML tags (extra options)
29
30 function __construct( $plugin_file_path ) {
31 $this->load_options();
32
33 $this->plugin_version = $GLOBALS['advanced_excerpt_version'];
34 $this->plugin_file_path = $plugin_file_path;
35 $this->plugin_dir_path = plugin_dir_path( $plugin_file_path );
36 $this->plugin_folder_name = basename( $this->plugin_dir_path );
37 $this->plugin_basename = plugin_basename( $plugin_file_path );
38 $this->plugin_base ='options-general.php?page=advanced-excerpt';
39
40 if ( 'POST' == $_SERVER['REQUEST_METHOD'] && isset( $_REQUEST['page'] ) && 'advanced-excerpt' === $_REQUEST['page'] ) {
41 check_admin_referer( 'advanced_excerpt_update_options' );
42 $this->update_options();
43 }
44
45 $this->options_basic_tags = apply_filters( 'advanced_excerpt_basic_tags', array(
46 'a', 'abbr', 'acronym', 'address', 'article', 'aside', 'audio', 'b', 'big',
47 'blockquote', 'br', 'canvas', 'center', 'cite', 'code', 'dd', 'del', 'div', 'dl', 'dt',
48 'em', 'embed', 'form', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hr', 'i', 'img', 'ins',
49 'li', 'nav', 'ol', 'p', 'pre', 'q', 's', 'section', 'small', 'span', 'strike', 'strong', 'sub',
50 'sup', 'svg', 'table', 'td', 'template', 'th', 'time', 'tr', 'u', 'ul', 'video'
51 ) );
52
53 $this->options_all_tags = apply_filters( 'advanced_excerpt_all_tags', array(
54 'a', 'abbr', 'acronym', 'address', 'applet', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big',
55 'blockquote', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'data',
56 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption',
57 'figure', 'font', 'footer', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hr',
58 'i', 'iframe', 'img', 'input', 'ins', 'isindex', 'kbd', 'keygen', 'label', 'legend', 'li', 'main', 'map',
59 'mark', 'math', 'menu', 'menuitem', 'meter', 'nav', 'noframes', 'noscript', 'object', 'ol', 'optgroup',
60 'option', 'output', 'p', 'param', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section',
61 'select', 'small', 'source', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'svg', 'table',
62 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var',
63 'video', 'wbr'
64 ) );
65
66 if ( is_admin() ) {
67 $this->admin_init();
68 }
69
70 add_action( 'loop_start', array( $this, 'hook_content_filters' ) );
71 }
72
73 function hook_content_filters() {
74 /*
75 * Allow developers to skip running the advanced excerpt filters on certain page types.
76 * They can do so by using the "Disable On" checkboxes on the options page or
77 * by passing in an array of page types they'd like to skip
78 * e.g. array( 'search', 'author' );
79 * The filter, when implemented, takes precedence over the options page selection.
80 *
81 * WordPress default themes (and others) do not use the_excerpt() or get_the_excerpt()
82 * instead they use the_content(). As such, we also need to hook into the_content().
83 * To ensure we're not changing the content of posts / pages we first check if is_singular().
84 */
85 $page_types = $this->get_current_page_types();
86 $skip_page_types = array_unique( array_merge( array( 'singular' ), $this->options['exclude_pages'] ) );
87 $skip_page_types = apply_filters( 'advanced_excerpt_skip_page_types', $skip_page_types );
88 $page_type_matches = array_intersect( $page_types, $skip_page_types );
89 if ( !empty( $page_types ) && !empty( $page_type_matches ) ) return;
90
91 if( 1 == $this->options['the_excerpt'] ) {
92 remove_all_filters( 'get_the_excerpt' );
93 add_filter( 'get_the_excerpt', array( $this, 'filter' ) );
94 }
95
96 if( 1 == $this->options['the_content'] ) {
97 add_filter( 'the_content', array( $this, 'filter' ) );
98 }
99 }
100
101 function admin_init() {
102 add_action( 'admin_menu', array( $this, 'add_pages' ) );
103 add_filter( 'plugin_action_links_' . $this->plugin_basename, array( $this, 'plugin_action_links' ) );
104 }
105
106 function load_options() {
107 /*
108 * An older version of this plugin used to individually store each of it's options as a row in wp_options (1 row per option).
109 * The code below checks if their installations once used an older version of this plugin and attempts to update
110 * the option storage to the new method (all options stored in a single row in the DB as an array)
111 */
112 $update_options = false;
113 $update_from_legacy = false;
114 if ( false !== get_option( 'advancedexcerpt_length' ) ) {
115 $legacy_options = array( 'length', 'use_words', 'no_custom', 'no_shortcode', 'finish_word', 'finish_sentence', 'ellipsis', 'read_more', 'add_link', 'allowed_tags' );
116
117 foreach ( $legacy_options as $legacy_option ) {
118 $option_name = 'advancedexcerpt_' . $legacy_option;
119 $this->options[$legacy_option] = get_option( $option_name );
120 delete_option( $option_name );
121 }
122
123 // filtering the_content() is disabled by default when migrating from version 4.1.1 of the plugin
124 $this->options['the_excerpt'] = 1;
125 $this->options['the_content'] = 0;
126
127 $update_options = true;
128 $update_from_legacy = true;
129 } else {
130 $this->options = get_option( 'advanced_excerpt' );
131 }
132
133 // convert legacy option use_words to it's udpated equivalent
134 if ( isset( $this->options['use_words'] ) ) {
135 $this->options['length_type'] = ( 1 == $this->options['use_words'] ) ? 'words' : 'characters';
136 unset( $this->options['use_words'] );
137 $update_options = true;
138 }
139
140 // convert legacy options finish_word & finish_sentence to their udpated equivalents
141 if ( isset( $this->options['finish_sentence'] ) ) {
142 if ( 0 == $this->options['finish_word'] && 0 == $this->options['finish_sentence'] ) {
143 $this->options['finish'] = 'exact';
144 } else if ( 1 == $this->options['finish_word'] && 1 == $this->options['finish_sentence'] ) {
145 $this->options['finish'] = 'sentence';
146 } else if ( 0 == $this->options['finish_word'] && 1 == $this->options['finish_sentence'] ) {
147 $this->options['finish'] = 'sentence';
148 } else {
149 $this->options['finish'] = 'word';
150 }
151 unset( $this->options['finish_word'] );
152 unset( $this->options['finish_sentence'] );
153 $update_options = true;
154 }
155
156 // convert legacy option '_all' in the allowed_tags option to it's updated equivalent
157 if ( isset( $this->options['allowed_tags'] ) ) {
158 if ( false !== ( $all_key = array_search( '_all', $this->options['allowed_tags'] ) ) ) {
159 unset( $this->options['allowed_tags'][$all_key] );
160 $this->options['allowed_tags_option'] = 'dont_remove_any';
161 } elseif( $update_from_legacy ) {
162 $this->options['allowed_tags_option'] = 'remove_all_tags_except';
163 }
164 }
165
166 // if no options exist then this is a fresh install, set up some default options
167 if ( empty( $this->options ) ) {
168 $this->options = $this->default_options;
169 $update_options = true;
170 }
171
172 $this->options = wp_parse_args( $this->options, $this->default_options );
173
174 if ( $update_options ) {
175 update_option( 'advanced_excerpt', $this->options );
176 }
177 }
178
179 function add_pages() {
180 $options_page = add_options_page( __( "Advanced Excerpt Options", 'advanced-excerpt' ), __( "Excerpt", 'advanced-excerpt' ), 'manage_options', 'advanced-excerpt', array( $this, 'page_options' ) );
181 // Scripts
182 add_action( 'admin_print_scripts-' . $options_page, array( $this, 'page_assets' ) );
183 }
184
185 function page_assets() {
186 $version = defined('SCRIPT_DEBUG') && SCRIPT_DEBUG ? time() : $this->plugin_version;
187 $plugins_url = trailingslashit( plugins_url() ) . trailingslashit( $this->plugin_folder_name );
188
189 // css
190 $src = $plugins_url . 'asset/css/styles.css';
191 wp_enqueue_style( 'advanced-excerpt-styles', $src, array(), $version );
192
193 $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
194
195 // js
196 $src = $plugins_url . 'asset/js/advanced-excerpt' . $suffix . '.js';
197 wp_enqueue_script( 'advanced-excerpt-script', $src, array( 'jquery' ), $version, true );
198 }
199
200 function plugin_action_links( $links ) {
201 $link = sprintf( '<a href="%s">%s</a>', admin_url( $this->plugin_base ), __( 'Settings', 'advanced-excerpt' ) );
202 array_unshift( $links, $link );
203 return $links;
204 }
205
206 function filter( $text ) {
207 global $post;
208 extract( wp_parse_args( $this->options, $this->default_options ), EXTR_SKIP );
209 $original_excerpt = $text;
210
211 // Avoid custom excerpts
212 if ( !empty( $text ) && !$no_custom ) {
213 return $text;
214 }
215
216 // Get the full content and filter it
217 $text = $post->post_content;
218 if ( 1 == $no_shortcode ) {
219 $text = strip_shortcodes( $text );
220 }
221 if( 1 == $this->options['the_content'] ) {
222 remove_filter( 'the_content', array( $this, 'filter' ) ); // prevent recursion
223 }
224 $text = apply_filters( 'the_content', $text );
225 if( 1 == $this->options['the_content'] ) {
226 add_filter( 'the_content', array( $this, 'filter' ) ); // add our filter back in
227 }
228
229 if ( $the_content_no_break && false !== strpos( $text, '<!--more-->' ) ) {
230 return $original_excerpt;
231 }
232
233 // From the default wp_trim_excerpt():
234 // Some kind of precaution against malformed CDATA in RSS feeds I suppose
235 $text = str_replace( ']]>', ']]&gt;', $text );
236
237 $original_post_content = $text;
238
239 // Determine allowed tags
240 if ( !isset( $allowed_tags ) ) {
241 $allowed_tags = $this->options_all_tags;
242 }
243
244 if ( isset( $exclude_tags ) ) {
245 $allowed_tags = array_diff( $allowed_tags, $exclude_tags );
246 }
247
248 // Strip HTML if $allowed_tags_option is set to 'remove_all_tags_except'
249 if ( 'remove_all_tags_except' === $allowed_tags_option ) {
250 if ( count( $allowed_tags ) > 0 ) {
251 $tag_string = '<' . implode( '><', $allowed_tags ) . '>';
252 } else {
253 $tag_string = '';
254 }
255 $text = strip_tags( $text, $tag_string );
256 }
257
258 // Create the excerpt
259 $text = $this->text_excerpt( $text, $length, $length_type, $finish );
260
261 // Add the ellipsis or link
262 if ( !apply_filters( 'advanced_excerpt_disable_add_more', false, $original_post_content, $this->options ) ) {
263 $text = $this->text_add_more( $text, $ellipsis, ( $add_link ) ? $read_more : false );
264 }
265
266 return apply_filters( 'advanced_excerpt_content', $text );
267 }
268
269 function text_excerpt( $text, $length, $length_type, $finish ) {
270 $tokens = array();
271 $out = '';
272 $w = 0;
273
274 // Divide the string into tokens; HTML tags, or words, followed by any whitespace
275 // (<[^>]+>|[^<>\s]+\s*)
276 preg_match_all( '/(<[^>]+>|[^<>\s]+)\s*/u', $text, $tokens );
277 foreach ( $tokens[0] as $t ) { // Parse each token
278 if ( $w >= $length && 'sentence' != $finish ) { // Limit reached
279 break;
280 }
281 if ( $t[0] != '<' ) { // Token is not a tag
282 if ( $w >= $length && 'sentence' == $finish && preg_match( '/[\?\.\!]\s*$/uS', $t ) == 1 ) { // Limit reached, continue until ? . or ! occur at the end
283 $out .= trim( $t );
284 break;
285 }
286 if ( 'words' == $length_type ) { // Count words
287 $w++;
288 } else { // Count/trim characters
289 $chars = trim( $t ); // Remove surrounding space
290 $c = strlen( $chars );
291 if ( $c + $w > $length && 'sentence' != $finish ) { // Token is too long
292 $c = ( 'word' == $finish ) ? $c : $length - $w; // Keep token to finish word
293 $t = substr( $t, 0, $c );
294 }
295 $w += $c;
296 }
297 }
298 // Append what's left of the token
299 $out .= $t;
300 }
301
302 return trim( force_balance_tags( $out ) );
303 }
304
305 public function text_add_more( $text, $ellipsis, $read_more ) {
306 if ( $read_more ) {
307 $link_template = apply_filters( 'advanced_excerpt_read_more_link_template', ' <a href="%1$s" class="read-more">%2$s</a>', get_permalink(), $read_more );
308 $ellipsis .= sprintf( $link_template, get_permalink(), $read_more );
309 }
310
311 $pos = strrpos( $text, '</' );
312
313 if ( $pos !== false ) {
314 // get the "clean" name of the last closing tag in the text, e.g. p, a, strong, div
315 $last_tag = strtolower( trim( str_replace( array( '<', '/', '>' ), '', substr( $text, $pos ) ) ) );
316
317 /*
318 * There was previously a problem where our 'read-more' links were being appending incorrectly into unsuitable HTML tags.
319 * As such we're now maintaining a whitelist of HTML tags that are suitable for being appended into.
320 */
321 $allow_tags_to_append_into = apply_filters( 'advanced_excerpt_allow_tags_to_append_into', array( 'p', 'div', 'article', 'section' ) );
322
323 if( !in_array( $last_tag, $allow_tags_to_append_into ) ) {
324 // After the content
325 $text .= $ellipsis;
326 return $text;
327 }
328 // Inside last HTML tag
329 $text = substr_replace( $text, $ellipsis, $pos, 0 );
330 return $text;
331 }
332
333 // After the content
334 $text .= $ellipsis;
335 return $text;
336 }
337
338 function update_options() {
339 $_POST = stripslashes_deep( $_POST );
340 $this->options['length'] = (int) $_POST['length'];
341
342 $checkbox_options = array( 'no_custom', 'no_shortcode', 'add_link', 'the_excerpt', 'the_content', 'the_content_no_break' );
343
344 foreach ( $checkbox_options as $checkbox_option ) {
345 $this->options[$checkbox_option] = ( isset( $_POST[$checkbox_option] ) ) ? 1 : 0;
346 }
347
348 $this->options['length_type'] = $_POST['length_type'];
349 $this->options['finish'] = $_POST['finish'];
350 $this->options['ellipsis'] = $_POST['ellipsis'];
351 $this->options['read_more'] = isset( $_POST['read_more'] ) ? $_POST['read_more'] : $this->options['read_more'];
352 $this->options['allowed_tags'] = ( isset( $_POST['allowed_tags'] ) ) ? array_unique( (array) $_POST['allowed_tags'] ) : array();
353 $this->options['exclude_pages'] = ( isset( $_POST['exclude_pages'] ) ) ? array_unique( (array) $_POST['exclude_pages'] ) : array();
354 $this->options['allowed_tags_option'] = $_POST['allowed_tags_option'];
355
356 update_option( 'advanced_excerpt', $this->options );
357
358 wp_redirect( admin_url( $this->plugin_base ) . '&settings-updated=1' );
359 exit;
360 }
361
362 function page_options() {
363 extract( $this->options, EXTR_SKIP );
364
365 $ellipsis = htmlentities( $ellipsis );
366 $read_more = htmlentities( $read_more );
367
368 $tag_list = array_unique( array_merge( $this->options_basic_tags, $allowed_tags ) );
369 sort( $tag_list );
370 $tag_cols = 5;
371
372 // provides a set of checkboxes allowing the user to exclude the excerpt filter on certain page types
373 $exclude_pages_list = array(
374 'home' => __( 'Home Page', 'advanced-excerpt' ),
375 'feed' => __( 'Posts RSS Feed', 'advanced-excerpt' ),
376 'search' => __( 'Search Archive', 'advanced-excerpt' ),
377 'author' => __( 'Author Archive', 'advanced-excerpt' ),
378 'category' => __( 'Category Archive', 'advanced-excerpt' ),
379 'tag' => __( 'Tag Archive', 'advanced-excerpt' ),
380 );
381 $exclude_pages_list = apply_filters( 'advanced_excerpt_exclude_pages_list', $exclude_pages_list );
382
383 require_once $this->plugin_dir_path . 'template/options.php';
384 }
385
386 function get_current_page_types() {
387 global $wp_query;
388 if ( ! isset( $wp_query ) ) return false;
389 $wp_query_object_vars = get_object_vars( $wp_query );
390
391 $page_types = array();
392 foreach( $wp_query_object_vars as $key => $value ) {
393 if ( false === strpos( $key, 'is_' ) ) continue;
394 if ( true === $value ) {
395 $page_types[] = str_replace( 'is_', '', $key );
396 }
397 }
398
399 return $page_types;
400 }
401
402 }