// (Original)hiro_do3ob@yahoo.co.jp
// License: GPL, same as PukiWiki
//
// Show RSS (of remote site) plugin
// NOTE:
// * This plugin needs 'PHP xml extension'
// * Cache data will be stored as CACHE_DIR/*.tmp
define('PLUGIN_SHOWRSS_USAGE', '#showrss(URI-to-RSS[,default|menubar|recent[,Cache-lifetime[,Show-timestamp]]])');
// Show related extensions are found or not
function plugin_showrss_action()
{
if (PKWK_SAFE_MODE) die_message('PKWK_SAFE_MODE prohibit this');
$body = '';
foreach(array('xml', 'mbstring') as $extension){
$$extension = extension_loaded($extension) ?
'&color(green){Found};' :
'&color(red){Not found};';
$body .= '| ' . $extension . ' extension | ' . $$extension . ' |' . "\n";
}
return array('msg' => 'showrss_info', 'body' => convert_html($body));
}
function plugin_showrss_convert()
{
static $_xml;
if (! isset($_xml)) $_xml = extension_loaded('xml');
if (! $_xml) return '#showrss: xml extension is not found
' . "\n";
$num = func_num_args();
if ($num == 0) return PLUGIN_SHOWRSS_USAGE . '
' . "\n";
$argv = func_get_args();
$timestamp = FALSE;
$cachehour = 0;
$template = $uri = '';
switch ($num) {
case 4: $timestamp = (trim($argv[3]) == '1'); /*FALLTHROUGH*/
case 3: $cachehour = trim($argv[2]); /*FALLTHROUGH*/
case 2: $template = strtolower(trim($argv[1]));/*FALLTHROUGH*/
case 1: $uri = trim($argv[0]);
}
$class = ($template == '' || $template == 'default') ? 'ShowRSS_html' : 'ShowRSS_html_' . $template;
if (! class_exists($class)) $class = 'ShowRSS_html';
if (! is_numeric($cachehour))
return '#showrss: Cache-lifetime seems not numeric: ' . htmlsc($cachehour) . '
' . "\n";
if (! is_url($uri))
return '#showrss: Seems not URI: ' . htmlsc($uri) . '
' . "\n";
// Remove old caches in 5% rate
if (mt_rand(1, 20) === 1) {
plugin_showrss_cache_expire(24);
}
list($rss, $time) = plugin_showrss_get_rss($uri, $cachehour);
if ($rss === FALSE) return '#showrss: Failed fetching RSS from the server
' . "\n";
if (! is_array($rss)) {
// Show XML error message
return '#showrss: Error - ' . htmlsc($rss) . '
' . "\n";
}
$time_display = '';
if ($timestamp > 0) {
$time_display = '
Last-Modified:' .
get_date('Y/m/d H:i:s', $time) . '
';
}
$obj = new $class($rss);
return $obj->toString($time_display);
}
// Create HTML from RSS array()
class ShowRSS_html
{
var $items = array();
var $class = '';
function ShowRSS_html($rss)
{
$this->__construct($rss);
}
function __construct($rss)
{
foreach ($rss as $date=>$items) {
foreach ($items as $item) {
$link = $item['LINK'];
$title = $item['TITLE'];
$date = get_date_atom($item['_TIMESTAMP'] + LOCALZONE);
$link = '' . $title . '';
$this->items[$date][] = $this->format_link($link);
}
}
}
function format_link($link)
{
return $link . '
' . "\n";
}
function format_list($date, $str)
{
return $str;
}
function format_body($str)
{
return $str;
}
function toString($timestamp)
{
$retval = '';
foreach ($this->items as $date=>$items)
$retval .= $this->format_list($date, join('', $items));
$retval = $this->format_body($retval);
return <<class}>
$retval$timestamp
EOD;
}
}
class ShowRSS_html_menubar extends ShowRSS_html
{
var $class = ' class="small"';
function format_link($link) {
return '' . $link . '' . "\n";
}
function format_body($str) {
return '' . "\n";
}
}
class ShowRSS_html_recent extends ShowRSS_html
{
var $class = ' class="small"';
function format_link($link) {
return '' . $link . '' . "\n";
}
function format_list($date, $str) {
return '' . $date . '' . "\n" .
'' . "\n";
}
}
// Get and save RSS
function plugin_showrss_get_rss($target, $cachehour)
{
$buf = '';
$time = NULL;
if ($cachehour) {
$filename = CACHE_DIR . encode($target) . '.tmp';
// Remove expired cache
plugin_showrss_cache_expire_file($filename, $cachehour);
// Get the cache not expired
if (is_readable($filename)) {
$buf = join('', file($filename));
$time = filemtime($filename) - LOCALZONE;
}
}
if (is_null($time)) {
// Newly get RSS
$data = pkwk_http_request($target);
if ($data['rc'] !== 200) {
return array(FALSE, 0);
}
$buf = $data['data'];
$time = UTIME;
// Save RSS into cache
if ($cachehour) {
$fp = fopen($filename, 'w');
fwrite($fp, $buf);
fclose($fp);
}
}
// Parse
$obj = new ShowRSS_XML();
$obj->modified_date = (is_null($time) ? UTIME : $time);
return array($obj->parse($buf),$time);
}
// Remove cache if expired limit exeed
function plugin_showrss_cache_expire($cachehour)
{
$expire = $cachehour * 60 * 60; // Hour
$dh = dir(CACHE_DIR);
while (($file = $dh->read()) !== FALSE) {
if (substr($file, -4) != '.tmp') continue;
$file = CACHE_DIR . $file;
$last = time() - filemtime($file);
if ($last > $expire) unlink($file);
}
$dh->close();
}
/**
* Remove single file cache if expired limit exeed
* @param $filename
* @param $cachehour
*/
function plugin_showrss_cache_expire_file($filename, $cachehour)
{
$expire = $cachehour * 60 * 60; // Hour
$last = time() - filemtime($filename);
if ($last > $expire) {
unlink($filename);
}
}
// Get RSS and array() them
class ShowRSS_XML
{
var $items;
var $item;
var $is_item;
var $tag;
var $encoding;
var $modified_date;
function parse($buf)
{
$this->items = array();
$this->item = array();
$this->is_item = FALSE;
$this->tag = '';
$utf8 = 'UTF-8';
$this->encoding = $utf8;
// Detect encoding
$matches = array();
if(preg_match('/<\?xml [^>]*\bencoding="([a-z0-9-_]+)"/i', $buf, $matches)) {
$encoding = $matches[1];
if (strtoupper($encoding) !== $utf8) {
// xml_parse() fails on non UTF-8 encoding attr in XML decLaration
$buf = preg_replace('/<\?xml ([^>]*)\bencoding="[a-z0-9-_]+"/i', 'items;
}
function escape($str)
{
// Unescape already-escaped chars (<, >, &, ...) in RSS body before htmlsc()
$str = strtr($str, array_flip(get_html_translation_table(ENT_COMPAT)));
// Escape
$str = htmlsc($str, ENT_COMPAT, $this->encoding);
// Encoding conversion
$str = mb_convert_encoding($str, SOURCE_ENCODING, $this->encoding);
return trim($str);
}
// Tag start
function start_element($parser, $name, $attrs)
{
if ($this->is_item !== FALSE) {
$this->tag = $name;
if ($this->is_item === 'ENTRY' && $name === 'LINK' && isset($attrs['HREF'])) {
if (! isset($this->item[$name])) $this->item[$name] = '';
$this->item[$name] .= $attrs['HREF'];
}
} else if ($name === 'ITEM') {
$this->is_item = 'ITEM';
} else if ($name === 'ENTRY') {
$this->is_item = 'ENTRY';
}
}
// Tag end
function end_element($parser, $name)
{
if ($this->is_item === FALSE || $name !== $this->is_item) return;
$item = array_map(array(& $this, 'escape'), $this->item);
$this->item = array();
if (isset($item['DC:DATE'])) {
$time = plugin_showrss_get_timestamp($item['DC:DATE'], $this->modified_date);
} else if (isset($item['PUBDATE'])) {
$time = plugin_showrss_get_timestamp($item['PUBDATE'], $this->modified_date);
} else if (isset($item['UPDATED'])) {
$time = plugin_showrss_get_timestamp($item['UPDATED'], $this->time);
} else {
$time_from_desc = FALSE;
if (isset($item['DESCRIPTION']) &&
(($description = trim($item['DESCRIPTION'])) != '')) {
$time_from_desc = strtotime($description);
}
if ($time_from_desc !== FALSE && $time_from_desc !== -1) {
$time = $time_from_desc - LOCALZONE;
} else {
$time = time() - LOCALZONE;
}
}
$item['_TIMESTAMP'] = $time;
$date = get_date('Y-m-d', $item['_TIMESTAMP']);
$this->items[$date][] = $item;
$this->is_item = FALSE;
}
function character_data($parser, $data)
{
if ($this->is_item === FALSE) return;
if (! isset($this->item[$this->tag])) $this->item[$this->tag] = '';
$this->item[$this->tag] .= $data;
}
}
function plugin_showrss_get_timestamp($str, $default_date)
{
$str = trim($str);
if ($str == '') return UTIME;
$matches = array();
if (preg_match('/(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2})(([+-])(\d{2}):(\d{2}))?/', $str, $matches)) {
$time = strtotime($matches[1] . ' ' . $matches[2]);
if ($time === FALSE || $time === -1) {
$time = $default_date;
} else if (isset($matches[3])) {
$diff = ($matches[5] * 60 + $matches[6]) * 60;
$time += ($matches[4] == '-' ? $diff : -$diff);
}
return $time;
} else {
$time = strtotime($str);
return ($time === FALSE || $time === -1) ? $default_date : $time - LOCALZONE;
}
}