POST TEASER" MENU ****
**** NOTE: PLEASE DO NOT EMAIL JONATHAN LEIGHTON FOR SUPPORT. Instead email for support. ****
Post Teaser -- A teaser plugin for WordPress
Copyright (C) Jonathan Leighton (j@jonathanleighton.com)
Copyright (C) WeyHan Ng (han@sandboxblogger.com)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with WordPress; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
class post_teaser {
var $version = "4.1.2";
var $debug = false;
var $meta = "";
/*** Default option values ***/
var $default_options = array();
/*** Anything that doesn't really need to be configurable ***/
var $static_options = array
(
'blocks' => 'p|li|dt|dd|address|form|pre|tr'
);
/*** Called once before any teasering. Puts the options into properties. ***/
function post_teaser() {
load_plugin_textdomain('post-teaser');
$this->default_options['full_template'] = __('
viewing the latest documentation or the FAQ.', 'post-teaser') ?>
debug)
$this->debug_message .= $message . "\n";
}
/*** Replaces placeholders with the relevant values ***/
function placeholders($template) {
$template = str_replace('%title%', the_title('', '', false), $template);
$template = str_replace('%plain_title%', strip_tags(str_replace('"', '"', the_title('', '', false))), $template);
$template = str_replace('%permalink%', get_permalink(), $template);
$template = str_replace('%word_image_count%', $this->word_image_count, $template);
$template = str_replace('%reading_time%', $this->reading_time, $template);
return $template;
}
/*** Counts words. PHP's str_word_count() only works for alphabetic characters ***/
function word_count($text) {
$text = strip_tags($text);
$text = preg_split("/\s+/", $text);
$count = count($text);
return $count;
}
/*** Where all the conditions we don't want to tease are checked here ***/
function is_disabled() {
global $post, $cookiehash, $action, $sem_home_page;
if (is_singular())
return true;
elseif (is_feed())
return true;
elseif ((!empty($post->post_password)) && ($_COOKIE['wp-postpass_' . $cookiehash] != $post->post_password))
return true;
elseif ($action == 'editpost' || $action == 'edit')
return true;
elseif ($this->meta == 'disable')
return true;
elseif (isset($sem_home_page) && is_home() && function_exists('sem_static_front')) // Semilogic static front page
return true;
elseif ($this->home_control && is_home()) { // Must be last in chain of elseif because of nested if
if ($this->home_disable)
return true;
else
return false; // Skip the rest of the test if it is homepage and showall is selected
}
if ($this->use_disable_filter) {
if ($this->disable_cat &&
(($this->disable_cat_always == '1' && in_category(array($this->disable_cat))) ||
is_category(array($this->disable_cat)) ) ) {
if ($this->disable_inverse)
return false;
else
return true;
} elseif ($this->disable_tag &&
(($this->disable_tag_always == '1' && has_tag(array($this->disable_tag))) ||
is_tag(array($this->disable_tag)) ) ) {
if ($this->disable_inverse)
return false;
else
return true;
}
}
if ($this->use_disable_filter && $this->disable_inverse)
return true;
return false;
}
/*** Where all the conditions for showall are checked here ***/
function is_showall() {
if ($this->meta == 'showall')
return true;
elseif ($this->home_control && $this->home_showall && is_home())
return true;
if ($this->use_showall_filter) {
if ($this->showall_cat &&
(($this->showall_cat_always == '1' && in_category(array($this->showall_cat))) ||
is_category(array($this->showall_cat)) ) )
if ($this->showall_inverse)
return false;
else
return true;
elseif ($this->showall_tag &&
(($this->showall_tag_always == '1' && has_tag(array($this->showall_tag))) ||
is_tag(array($this->showall_tag)) ) )
if ($this->showall_inverse)
return false;
else
return true;
}
if ($this->use_showall_filter && $this->showall_inverse)
return true;
return false;
}
/*** The real business happens here. Every post is run through this method ***/
function process($content) {
global $post, $pages, $page, $multipage;
$matches = null;
$matches2 = null;
$auto_close = array();
$this->debug_message = '';
$this->meta = get_post_meta($post->ID, 'teaser', true);
/*** Checks for when we don't want to teaser stuff ***/
if ($this->is_disabled())
return $content;
$this->debug('Starting teaser (got through the checks), version number is ' . $this->version);
$showall = $this->is_showall();
$more = false;
/*** Deal with and (this is very hackish) ***/
$plain_content = $pages[$page-1];
if (strstr($plain_content, '')) {
$this->debug('A "more" tag has been detected... it will be replaced with the teaser text.');
$matches[0] = preg_replace('!.+?!', '', $content);
$matches[0] = preg_replace('!
!', '', $matches[0]);
// Get rid of any closing tags at the end of the content as there have
// been some nesting validation issues (due to WP clashing) and they will be
// automatically closed in order anyway (see below)
$matches[0] = preg_replace('!(([a-zA-Z1-9]+)>\s*)+\s*$!', '', $matches[0]);
$i = 0;
$content = $plain_content;
$more = true;
}
if ($multipage) {
$this->debug('A "nextpage" tag has been detected.');
if ($more)
$content_temp = $matches[0];
else
$content_temp = $content;
$content = '';
foreach ($pages as $item)
$content .= $item;
}
if (($more || $multipage) && ($this->doing_reading_time || $this->doing_counts)) {
remove_filter('the_content', array(&$this, 'process'), 20);
$content = apply_filters('the_content', $content);
add_filter('the_content', array(&$this, 'process'), 20);
$content = str_replace(']]>', ']]>', $content);
}
/*** Reading time ***/
if ($this->doing_reading_time) {
$this->debug('Start of reading time calculation');
$average = $this->word_count($content) / 250 * 60;
$min = (int) ($average / 60);
$sec = round(fmod($average, 60));
if ($sec < 10)
$sec = '0' . $sec;
elseif ($sec == 60) { // Fix seconds round to 60 issue
$min = $min + 1;
$sec = 0;
}
if ($min == 0)
$this->reading_time = ($min * 60 + $sec) . ' ' . $this->word_secs;
else
$this->reading_time = $min . $this->time_separator . $sec . ' ' . $this->word_mins;
$this->debug("End of reading time calculation. Result = {$this->reading_time}, sec = $sec, min = $min");
}
/*** Word/image count ***/
if ($this->doing_counts) {
$this->debug('Start of word/image count calculation');
$word_count = $this->word_count($content);
if (!$this->zero_counts && $word_count == 0)
$word_count = '';
else
$word_count .= ($word_count == 1) ? __(' word', 'post-teaser') : __(' words', 'post-teaser');
$temp = preg_replace('/]*class=\'wp-smiley\'[^>]*>/i', '', $content);
$image_count = preg_match_all('/]*>/i', $temp, $matches2);
if (!$this->zero_counts && $image_count == 0)
$image_count = "";
else
$image_count .= ($image_count == 1) ? __(' image', 'post-teaser') : __(' images', 'post-teaser');
$this->word_image_count = $word_count;
if ($word_count && $image_count)
$this->word_image_count .= $this->count_separator;
$this->word_image_count .= $image_count;
$this->debug("End of word/image count calculation. Result = {$this->word_image_count}, words = $word_count, images = $image_count");
}
if ($multipage)
$content = $content_temp;
/***
This is how it works:
* Split posts into "blocks"
* Find the first block which would produce a cumulative word count greater than the target
* Decrement this value if the block before it is closer to the target
* Decide whether to make a "teaser" of the post based on whether that chosen block is the last one.
Exceptions:
* If it has a tag, it's teasered regardless
* If the post has a "teaser" custom field with a value of "showall", it will be returned in full regardless (and is over-ridden)
* If the post is teasered:
* Put all (X)HTML starting tags into an array
* From this array, get the actual element names, and exclude self-closing tags (like ). What's left goes into $auto_close
* Unset each element as a closing tag is found for it
* The elements left over must have been chopped in half -- give them a closing tag
***/
$i = 0;
$block_count = 0;
if (!$more && !$showall) {
preg_match_all("!.*?<({$this->blocks})[^>]*>.+?\\1>!si", $content, $matches);
$matches = $matches[0];
$block_count = count($matches);
$this->debug("Number of blocks: $block_count");
$current_word_count = 0;
$block_word_count = array();
for ($i = 0; $current_word_count < $this->target && $i < $block_count; /* (increment is conditional, see below) */) {
$block_word_count[$i] = $this->word_count($matches[$i]);
$current_word_count += $block_word_count[$i];
if ($current_word_count < $this->target)
$i++;
}
$this->debug("Finished looping through on block #$i (starts at zero). Cumulative word count was $current_word_count. Target was {$this->target}.");
if ($current_word_count >= $this->target && $i > 0) { // No need if it will definitely not be teasered
$this_block_distance = $current_word_count - $this->target;
$last_block_distance = $this->target - ($current_word_count - $block_word_count[$i]);
$this->debug("Current block is $this_block_distance words from target, previous block is $last_block_distance words from target.");
if ($this_block_distance > $last_block_distance) {
$i--;
$this->debug("Decremented to $i");
}
}
}
if (($i + 1 < $block_count || $more) && !$showall) {
$this->debug('Post will be teasered');
$fullpost = false;
for ($j = 0; $j <= $i; $j++) {
preg_match_all('!<(?:[a-zA-Z1-9]+)[^>]*>!i', $matches[$j], $matches2);
$matches2 = $matches2[0];
foreach ($matches2 as $id => $element) {
if (preg_match('!^<([a-zA-Z1-9]+)[^>]*/>$!i', $element)) {
unset($matches2[$id]);
continue;
}
$element = preg_replace('!^<([a-zA-Z1-9]+)[^>]*>$!i', "$1", $element);
$auto_close[] = $element;
}
}
$content = '';
for ($j = 0; $j <= $i; $j++) {
$temp = $matches[$j];
foreach ($auto_close as $id => $element) {
$pos = strpos(" " . $temp, "$element>"); // Space at front because 0 == false
if ($pos) {
$temp = substr_replace($temp, '', $pos, strlen("$element>")); // Makes sure a closing tag is not counted more than once
unset($auto_close[$id]);
}
}
if (!$this->debug)
$content .= $matches[$j];
else
$content .= "\n\n" . $matches[$j] . "\n\n";
}
$auto_close = array_reverse($auto_close);
foreach ($auto_close as $element)
$content .= "$element>";
if ($this->more_link) {
$lastp = strrpos($content, "");
if ($lastp) {
$more_template =& $this->more_template;
$teaser_str = " " . $this->placeholders($more_template) . '';
$content = substr_replace($content, $teaser_str, $lastp, 0);
}
}
$template =& $this->teaser_template;
} else {
$this->debug('Post is being returned in full');
$fullpost = true;
$template =& $this->full_template;
}
if ($this->block_stats && !($this->omit_fullpost && $fullpost)) {
if ($this->block_stats_before)
$content = "\n\n
" . $this->placeholders($template) . '
' . $content;
else
$content .= "\n\n
" . $this->placeholders($template) . '
';
}
$this->debug('The End.');
if ($this->debug)
$content .= "\n\n\n\n";
return $content;
}
/*** A fix for invalid code from wpautop() (http://trac.wordpress.org/ticket/1099). ***/
/* Disabled because the bug above have been fixed. Will consider removal at a later date
function autopfix($pee) {
$pee = preg_replace('!(<(?:div|address|form)[^>]*>)([^<]+)!', "$1
$2
", $pee);
$pee = preg_replace('!
([^<]+)\s*?((?:div|address|form)[^>]*>)!', "
$1
$2", $pee);
return $pee;
}
*/
function replace_excerpt($excerpt) {
return the_content();
}
}
$post_teaser = new post_teaser();
//add_filter('the_content', array(&$post_teaser, 'autopfix')); // Disabled because the associated bug have been fixed
add_filter('the_content', array(&$post_teaser, 'process'), 20);
add_filter('the_excerpt', array(&$post_teaser, 'replace_excerpt'), 20); // Because Post Teaser does the same thing, better
add_action('admin_menu', array(&$post_teaser, 'init_option_page'));
?>