<?php
/*
Plugin Name: jRSS
Plugin URI: http://jakobj.dk/blog/archives/35?lan=english
Description: Easy and stable displaying of feeds from other sites (Widget capable). Requires PHP5 and support the following types of feeds Atom, Feedburner, RSS0.91, RSS0.92, RSS2.0 and RDF/RSS1.0.
Author: Jakob Jensen
Version: 2.1
Author URI: http://jakobj.dk/blog/

    Copyright 2006  Jakob Jensen  (email : jakobj@jakobj.dk)

    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 this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


== Installation ==
1. Upload this file to your /wp-content/plugins/widget/ folder.
2. Create, if it doesn't already exist, the folder /wp-content/plugins/cache/.
3. Chmod the newly-created folder to 777 (full access).
4. Look at Usage Examples to find out how to use it or if your theme supports it, use the menu item 'Presentation > Sidebar Widgets'. :)

== Usage Examples ==
<?php 
$rss = new jRSS();
$rss->display('http://jakobj.dk/blog/feed/rss/');
$rss->display('http://jakobj.dk/blog/feed/rss/', 'items:3, customheading:Creator of jRSS, showdate:false, showdesc:false, updateRatio:60, type:RSS');
$rss->display('http://jakobj.dk/blog/feed/rss/', 'charset:UTF-8, cssclass:jrss, items:3, customheading:Creator of jRSS, showdate:true, dateformat:Y/m/j, shortdesc:100, updateRatio:30, type:RSS');
?>

== Notes ==
 - RSS0.91 will never display a date, because a RSS0.91 feed does not contain a date.
 
*/ 

// fatal check for PHP5
if (substr(phpversion(), 01) != 5)
{
    
trigger_error("<p><b>(jRSS) Fatal Error:</b><br />Plugin needs PHP5</p>"E_USER_ERROR);
    
//Failsafe. In case errors have been disabled.
    
echo "<p><b>(jRSS) Fatal Error:</b><br />Plugin needs PHP5</p>";
    exit();
    
//End failsafe.
}

class 
jRSS
{
    private 
$config;
    private 
$feedLocation;
    private 
$writableCache;
    private 
$error;
    public function 
__construct()
    {
        
$this->error false;
        if (!
defined('ABSPATH'))
        {
            
$this->err_(1);
            return 
false;
        }
        
$this->writableCache false;
        if (!
is_dir(ABSPATH .'/wp-content/plugins/cache/'))
        {
            if (!@
mkdir(ABSPATH .'/wp-content/plugins/cache/'0777))
            {
                
$this->err_(2);
                return 
false;
            }
        }
        if (
is_dir(ABSPATH .'/wp-content/plugins/cache/'))
        {
            if (@
fclose(@fopen(ABSPATH .'/wp-content/plugins/cache/writeTest.jRSS'"w+")))
            {
                @
chmod(ABSPATH .'/wp-content/plugins/cache/writeTest.jRSS'0600);
                
$this->writableCache true;
            }
            else
            {
                
$this->err_(0);
                return 
false;
            }
        }
        if (!@
ini_get('allow_url_fopen') && !@function_exists('curl_init'))
        {
            
$this->err_(7);
            return 
false;
        }
        return 
true;
    }
    private function 
setCache()
    {
        if (!
$this->writableCache)
        {
            
$this->err_(0);
            return 
false;
        }
        if (!
$feedStr = @file_get_contents($this->feedLocation))
        {
            if (
$ch = @curl_init())
            {
                @
curl_setopt($chCURLOPT_RETURNTRANSFERtrue);
                @
curl_setopt($chCURLOPT_USERAGENT'jRSS wordpress plugin');
                @
curl_setopt($chCURLOPT_URL$this->feedLocation);
                
$feedStr = @curl_exec($ch);
                @
curl_close($ch);
            }
        }
        if (!
$feedStr)
        {
            
$this->err_(5);
            return 
false;
        }
        if (!
$feedObj = @simplexml_load_string($feedStr))
        {
            
$this->err_(5);
            return 
false;
        }
        if ((isset(
$this->config['type']) && strtolower($this->config['type']) == 'auto') || !isset($this->config['type']))
        {
            
$snippet substr($feedStr0250);
            
$longsnippet substr($feedStr0500);
            if (
stripos($snippet'<rss') !== false)
            {
                
$this->config['type'] = 'rss';
            }
            else if (
stripos($snippet'<rdf') !== false)
            {
                
$this->config['type'] = 'rdf';
            }
            else if (
stripos($longsnippet'xmlns:feedburner') !== false)
            {
                
$this->config['type'] = 'feedburner';
            }
            else if (
stripos($snippet'<feed') !== false)
            {
                
$this->config['type'] = 'atom';
            }
        }
        
$items = array();
        
$feed = array();
        switch (
strtolower($this->config['type']))
        {
            case 
'rss':
                foreach(
$feedObj->channel->item as $item)
                {
                    
array_push($items, array('title' => (string) $item->title'link' => (string) $item->link'description' => (string) $item->description));
                }
                
$feed['title'] = (string) $feedObj->channel->title;
                
$feed['link'] = (string) $feedObj->channel->link;
                
$feed['description'] = (string) $feedObj->channel->description;
                
$date = (string) $feedObj->channel->lastBuildDate;
                
$feed['date'] = (int) (empty($date)) ? strtotime($date);
                break;
            case 
'rdf':
                foreach(
$feedObj->item as $item)
                {
                    
array_push($items, array('title' => (string) $item->title'link' => (string) $item->link'description' => (string) $item->description));
                }
                
$feed['title'] = (string) $feedObj->channel->title;
                
$feed['link'] = (string) $feedObj->channel->link;
                
$feed['description'] = (string) $feedObj->channel->description;
                
// RDF/RSS1.0 has a <dc:date> which simplexml_load_file has a problem with, hence the hack below.
                
$offsetx strpos($feedStr'<dc:date>');
                
$offsety strpos($feedStr'</dc:date>'$offsetx);
                
$length $offsety $offsetx;
                
$date = (string) substr($feedStr$offsetx$length);
                
$y substr($date,  94);
                
$m substr($date142);
                
$d substr($date172);
                
$h substr($date202);
                
$i substr($date232);
                
$s substr($date262);
                
$date mktime($h$i$s$m$d$y);
                
// End hack.
                
$feed['date'] = (int) (empty($date)) ? date($date);
                break;
            case 
'atom':
                foreach(
$feedObj->entry as $item)
                {
                    
array_push($items, array('title' => (string) $item->title'link' => (string) $item->link['href'], 'description' => (string) $item->summary));
                }
                
$feed['title'] = (string) $feedObj->title;
                
$feed['link'] = (string) $feedObj->link['href'];
                
$feed['description'] = (string) $feedObj->tagline;
                
$date = (string) $feedObj->modified;
                
$feed['date'] = (int) (empty($date)) ? strtotime($date);
                break;
            case 
'feedburner':
                foreach(
$feedObj->entry as $item)
                {
                    
array_push($items, array('title' => (string) $item->title'link' => (string) $item->link['href'], 'description' => (string) $item->summary));
                }
                
$feed['title'] = (string) $feedObj->title;
                
$feed['link'] = (string) $feedObj->link['href'];
                
$feed['description'] = (string) $feedObj->tagline;
                
$date = (string) $feedObj->updated;
                
$feed['date'] = (int) (empty($date)) ? strtotime($date);
                break;
            default:
                foreach(
$feedObj->channel->item as $item)
                {
                    
array_push($items, array('title' => (string) $item->title'link' => (string) $item->link'description' => (string) $item->description));
                }
                
$feed['title'] = (string) $feedObj->channel->title;
                
$feed['link'] = (string) $feedObj->channel->link;
                
$feed['description'] = (string) $feedObj->channel->description;
                
$feed['date'] = (int) $feedObj->channel->lastBuildDate;
                break;
        }
        
$feed['items'] = $items;
        
$handle = @fopen(ABSPATH .'/wp-content/plugins/cache/'md5($this->feedLocation $this->config['type']) .'.jRSS'"w");
        if (@
fwrite($handleserialize($feed)))
        {
            @
chmod(ABSPATH .'/wp-content/plugins/cache/'md5($this->feedLocation $this->config['type']) .'.jRSS'0600);
            @
fclose($handle);
            return 
true;
        }
        else
        {
            
$this->err_(0);
            return 
false;
        }
    }
    private function 
getCached()
    {
        if (!isset(
$this->config['updateratio']) || !$this->config['updateratio'])
        {
            
$this->config['updateratio'] = 15;
        }
        if ((
file_exists(ABSPATH .'/wp-content/plugins/cache/'md5($this->feedLocation $this->config['type']) .'.jRSS') && filemtime(ABSPATH .'/wp-content/plugins/cache/'md5($this->feedLocation $this->config['type']) .'.jRSS') < time()-($this->config['updateratio']*60)) || !file_exists(ABSPATH .'/wp-content/plugins/cache/'md5($this->feedLocation $this->config['type']) .'.jRSS'))
        {
            
$this->setCache();
        }
        if (!
$feed = @unserialize(@file_get_contents(ABSPATH .'/wp-content/plugins/cache/'md5($this->feedLocation $this->config['type']) .'.jRSS')))
        {
            
$this->err_(5);
            return 
false;
        }
        return 
$feed;
    }
    private function 
setCharset($text)
    {
        if (!isset(
$this->config['charset']))
        {
            return 
strip_tags($text);
        }
        switch (
strtolower($this->config['charset']))
        {
            case 
'utf-8':
                break;
            case 
'iso-8859-1':
                
$text utf8_decode($text);
                break;
        }
        return 
strip_tags($text);
    }
    private function 
wrapper()
    {
        if (!
$feed $this->getCached())
        {
            
$this->err_(6);
            return 
false;
        }
        if (isset(
$this->config['debug']) && $this->config['debug']) {
            
// "hidden feature"
            
echo '<pre>';
            echo 
"Configuration:\n";
            
var_dump($this->config);
            echo 
"\nLoaded "$this->feedLocation .":\n";
            
var_dump($feed);
            echo 
'</pre>';
            return 
true;
            
// end hidden feature
        
}
        
printf("\n"null);
        
printf('<div class="%1$s">'$this->config['cssclass']);
        if (isset(
$this->config['customheading']) && $this->config['customheading'])
        {
            
printf('<h3 class="%1$s">%2$s</h3>'$this->config['cssclass'], $this->config['customheading']);
        }
        else
        {
            
printf('<h3 class="%1$s">%2$s</h3>'$this->config['cssclass'], $this->setCharset($feed['title']));
        }
        if (isset(
$this->config['showdate']) && $this->config['showdate'] && $feed['date'])
        {
            
$dateformat = (isset($this->config['dateformat'])) ? $this->config['dateformat'] : 'Y-m-j';
            
printf('<div><small>%1$s</small></div>'date($dateformat$feed['date']));
        }
        
printf('<ol class="%1$s">'$this->config['cssclass']);
        
$amount = (isset($this->config['items'])) ? $this->config['items'] : 5;
        
$items = @array_slice($feed['items'], 0$amount);
        foreach(
$items as $item)
        {
            if (isset(
$this->config['showdesc']) && $this->config['showdesc'])
            {
                
$desc = (isset($this->config['shortdesc']) && $this->config['shortdesc']) ? substr($item['description'], 0$this->config['shortdesc']) .'...' $item['description'];
                
printf('<li><a href="%1$s">%2$s</a><br />%3$s</li>'$this->setCharset($item['link']), $this->setCharset($item['title']), $this->setCharset($desc));
            }
            else
            {
                
printf('<li><a href="%1$s">%2$s</a></li>'$this->setCharset($item['link']), $this->setCharset($item['title']));
            }
        }
        
printf('</ol>'null);
        
printf('</div>'null);
        
printf("\n"null);
        return 
true;
    }
    public function 
widget($feedLocation$configString '')
    {
        if (
$this->error || !$this->parseData($feedLocation$configString)) return false;
        if (!
$feed $this->getCached())
        {
            
$this->err_(6);
            return 
false;
        }
        
$output = array();
        if (isset(
$this->config['customheading']) && $this->config['customheading'])
        {
            
$output['title'] = $this->config['customheading'];
        }
        else
        {
            
$output['title'] = $this->setCharset($feed['title']);
        }
        
$output['jrss'] = '<div class="jRSS">';
        if (isset(
$this->config['showdate']) && $this->config['showdate'] && $feed['date'])
        {
            
$dateformat = (isset($this->config['dateformat'])) ? $this->config['dateformat'] : 'Y-m-j';
            
$output['jrss'] .= '<small>'date($dateformat$feed['date']) .'</small>';
        }
        
$output['jrss'] .= '<ol>';
        
$amount = (isset($this->config['items'])) ? $this->config['items'] : 5;
        
$items = @array_slice($feed['items'], 0$amount);
        foreach(
$items as $item)
        {
            if (isset(
$this->config['showdesc']) && $this->config['showdesc'])
            {
                
$desc = (isset($this->config['shortdesc']) && $this->config['shortdesc']) ? substr($item['description'], 0$this->config['shortdesc']) .'...' $item['description'];
                
$output['jrss'] .= sprintf('<li><a href="%1$s">%2$s</a><br />%3$s</li>'$this->setCharset($item['link']), $this->setCharset($item['title']), $this->setCharset($desc));
            }
            else
            {
                
$output['jrss'] .= sprintf('<li><a href="%1$s">%2$s</a></li>'$this->setCharset($item['link']), $this->setCharset($item['title']));
            }
        }
        
$output['jrss'] .= '</ol><div>';
        return 
$output;
    }
    public function 
display($feedLocation$configString '')
    {
        if (
$this->error || !$this->parseData($feedLocation$configString)) return false;
        return 
$this->wrapper();
    }
    private function 
parseData($feedLocation$configString)
    {
        if (empty(
$feedLocation))
        {
            
$this->err_(4);
            return 
false;
        }
        
$config = array();
        
$bits explode(','$configString);
        foreach(
$bits as $bit)
        {
            
$set explode(':'$bit);
            switch (
strtolower(trim($set[0])))
            {
                case 
'items':
                    
$config['items'] = (int) trim($set[1]);
                    break;
                case 
'customheading':
                    
$config['customheading'] = (string) trim($set[1]);
                    break;
                case 
'cssclass':
                    
$config['cssclass'] = (string) trim($set[1]);
                    break;
                case 
'charset':
                    
$config['charset'] = (string) trim($set[1]);
                    break;
                case 
'type':
                    
$config['type'] = (string) trim($set[1]);
                    break;
                case 
'dateformat':
                    
$config['dateformat'] = (string) trim($set[1]);
                    break;
                case 
'showdate':
                    
$config['showdate'] = (strtolower(trim($set[1]))!='false') ? true false;
                    break;
                case 
'showdesc':
                    
$config['showdesc'] = (strtolower(trim($set[1]))!='false') ? true false;
                    break;
                case 
'shortdesc':
                    
$config['shortdesc'] = (int) trim($set[1]);
                    break;
                case 
'updateratio':
                    
$config['updateratio'] = (int) trim($set[1]);
                    break;
                    
// "hidden feature"
                
case 'debug':
                    
$config['debug'] = (strtolower(trim($set[1]))!='false') ? true false;
                    break;
                    
// end hidden feature
            
}
        }
        
$this->config $config;
        
$this->feedLocation $feedLocation;
        return 
true;
    }
    private function 
err_($type)
    {
        echo 
"<p><b>(jRSS) Fatal Error:</b><br />";
        switch(
$type)
        {
            case 
0:
                echo 
"Cache is not writeable!";
                break;
            case 
1:
                echo 
"Plugin was not called inside WordPress!";
                break;
            case 
2:
                echo 
"Cache folder does not exist!";
                break;
                
//outdated, since feed now defaults to rss.
            
case 3
                echo 
"Type of feed is unknown!";
                break;
                
//end outdated
            
case 4:
                echo 
"No feed location given!";
                break;
            case 
5:
                echo 
"Feed location unreadable!";
                break;
            case 
6:
                echo 
"No feed was displayed!";
                break;
            case 
7:
                echo 
"PHP configuration does not allow opening urls and cUrl is not available!";
                break;
        }
        echo 
"</p>";
        
$this->error true;
        return 
true;
    }
}

function 
widget_jrss_init()
{
    if ( !
function_exists('register_sidebar_widget') || !function_exists('register_widget_control') )
    {
        return;
    }
    function 
widget_jrss($args)
    {
        
extract($args);
        
$options get_option('widget_jrss');
        
$url = empty($options['url']) ? '' $options['url'];
        
$conf = empty($options['conf']) ? '' $options['conf'];
        
$jrss = new jRSS();
        
$result $jrss->widget($url$conf);
        echo 
$before_widget;
        echo 
$before_title $result['title'] . $after_title;
        echo 
$result['jrss'];
        echo 
$after_widget;
    }
    function 
widget_jrss_control() {
        
$options get_option('widget_jrss');
        if ( 
$_POST['jrss-submit'] )
        {
            
$newoptions['url'] = strip_tags(stripslashes($_POST['jrss-url']));
            
$newoptions['conf'] = strip_tags(stripslashes($_POST['jrss-conf']));
        }
        if ( 
$options != $newoptions )
        {
            
$options $newoptions;
            
update_option('widget_jrss'$options);
        }
        
$url htmlspecialchars($options['url'], ENT_QUOTES);
        
$conf htmlspecialchars($options['conf'], ENT_QUOTES);
?>
        <div>
        <label for="jrss-url" style="line-height:35px;display:block;">Widget url: <input type="text" id="jrss-url" name="jrss-url" value="<?php echo&nb