OSDN Git Service

BugTrack/2176 showrss: Fix character encoding issues etc.
[pukiwiki/pukiwiki.git] / plugin / showrss.inc.php
index 455fae5..9968214 100644 (file)
@@ -1,67 +1,81 @@
 <?php
 // PukiWiki - Yet another WikiWikiWeb clone
-// $Id: showrss.inc.php,v 1.18 2006/05/02 01:28:01 henoheno Exp $
-//  Id:showrss.inc.php,v 1.40 2003/03/18 11:52:58 hiro Exp
-// Copyright (C):
-//     2002-2005 PukiWiki Developers Team
+// showrss.inc.php
+// Copyright:
+//     2002-2017 PukiWiki Development Team
 //     2002      PANDA <panda@arino.jp>
 //     (Original)hiro_do3ob@yahoo.co.jp
 // License: GPL, same as PukiWiki
 //
 // Show RSS (of remote site) plugin
-// NOTE: This plugin needs 'PHP xml extension'
+// NOTE:
+//    * This plugin needs 'PHP xml extension'
+//    * Cache data will be stored as CACHE_DIR/*.tmp
 
-// Show this plugin is enable or not for this PukiWiki
+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()
 {
-       $xml_extension      = extension_loaded('xml');
-       $mbstring_extension = extension_loaded('mbstring');
-
-       $xml_msg      = $xml_extension      ? 'xml extension is loaded'      : 'COLOR(RED){xml extension is not loaded}';
-       $mbstring_msg = $mbstring_extension ? 'mbstring extension is loaded' : 'COLOR(RED){mbstring extension is not loaded}';
-       $showrss_info  =
-               '| xml parser | ' . $xml_msg      . ' |' . "\n" .
-               '| multibyte  | ' . $mbstring_msg . ' |' . "\n";
-
-       return array('msg' => 'showrss_info', 'body' => convert_html($showrss_info));
+       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()
 {
-       if (func_num_args() == 0) {
-               return '<p>showrss: no parameter(s).</p>' . "\n";
-       } else if (! extension_loaded('xml')) {
-               return '<p>showrss: xml extension is not loaded</p>' . "\n";
+       static $_xml;
+
+       if (! isset($_xml)) $_xml = extension_loaded('xml');
+       if (! $_xml) return '#showrss: xml extension is not found<br />' . "\n";
+
+       $num = func_num_args();
+       if ($num == 0) return PLUGIN_SHOWRSS_USAGE . '<br />' . "\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]);
        }
 
-       $array  = func_get_args();
-       $rssurl = $tmplname = $usecache = $usetimestamp = '';
-
-       switch (func_num_args()) {
-       case 4: $usetimestamp = trim($array[3]);
-       case 3: $usecache     = $array[2];
-       case 2: $tmplname     = strtolower(trim($array[1]));
-       case 1: $rssurl       = trim($array[0]);
+       $class = ($template == '' || $template == 'default') ? 'ShowRSS_html' : 'ShowRSS_html_' . $template;
+       if (! is_numeric($cachehour))
+               return '#showrss: Cache-lifetime seems not numeric: ' . htmlsc($cachehour) . '<br />' . "\n";
+       if (! class_exists($class))
+               return '#showrss: Template not found: ' . htmlsc($template) . '<br />' . "\n";
+       if (! is_url($uri))
+               return '#showrss: Seems not URI: ' . htmlsc($uri) . '<br />' . "\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<br />' . "\n";
+       if (! is_array($rss)) {
+               // Show XML error message
+               return '#showrss: Error - ' . htmlsc($rss) . '<br />' . "\n";
+       }
+       $time_display = '';
+       if ($timestamp > 0) {
+               $time_display = '<p style="font-size:10px; font-weight:bold">Last-Modified:' .
+                       get_date('Y/m/d H:i:s', $time) .  '</p>';
        }
-
-       if (! is_url($rssurl))
-               return '<p>showrss: syntax error. ' . htmlspecialchars($rssurl) . '</p>' . "\n";
-
-       $class = 'ShowRSS_html_' . $tmplname;
-       if (! class_exists($class)) $class = 'ShowRSS_html';
-
-       list($rss, $time) = plugin_showrss_get_rss($rssurl, $usecache);
-       if ($rss === FALSE)
-               return '<p>showrss: cannot get rss from server.</p>' . "\n";
 
        $obj = new $class($rss);
-
-       $timestamp = '';
-       if ($usetimestamp > 0) {
-               $time = get_date('Y/m/d H:i:s',$time);
-               $timestamp = '<p style="font-size:10px; font-weight:bold">Last-Modified:' . $time . '</p>';
-       }
-       return $obj->toString($timestamp);
+       return $obj->toString($time_display);
 }
 
 // Create HTML from RSS array()
@@ -72,6 +86,10 @@ class ShowRSS_html
 
        function ShowRSS_html($rss)
        {
+               $this->__construct($rss);
+       }
+       function __construct($rss)
+       {
                foreach ($rss as $date=>$items) {
                        foreach ($items as $item) {
                                $link  = $item['LINK'];
@@ -140,49 +158,48 @@ class ShowRSS_html_recent extends ShowRSS_html
        }
 }
 
-// Get RSS
-function plugin_showrss_get_rss($target, $usecache)
+// Get and save RSS
+function plugin_showrss_get_rss($target, $cachehour)
 {
-       $buf = '';
+       $buf  = '';
        $time = NULL;
-       if ($usecache) {
+       if ($cachehour) {
+               $filename = CACHE_DIR . encode($target) . '.tmp';
                // Remove expired cache
-               plugin_showrss_cache_expire($usecache);
-
+               plugin_showrss_cache_expire_file($filename, $cachehour);
                // Get the cache not expired
-               $filename = CACHE_DIR . encode($target) . '.tmp';
                if (is_readable($filename)) {
                        $buf  = join('', file($filename));
                        $time = filemtime($filename) - LOCALZONE;
                }
        }
 
-       if ($time === NULL) {
+       if (is_null($time)) {
                // Newly get RSS
-               $data = http_request($target);
-               if ($data['rc'] !== 200)
+               $data = pkwk_http_request($target);
+               if ($data['rc'] !== 200) {
                        return array(FALSE, 0);
-
+               }
                $buf = $data['data'];
                $time = UTIME;
 
                // Save RSS into cache
-               if ($usecache) {
+               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($usecache)
+function plugin_showrss_cache_expire($cachehour)
 {
-       $expire = $usecache * 60 * 60; // Hour
+       $expire = $cachehour * 60 * 60; // Hour
        $dh = dir(CACHE_DIR);
        while (($file = $dh->read()) !== FALSE) {
                if (substr($file, -4) != '.tmp') continue;
@@ -193,6 +210,20 @@ function plugin_showrss_cache_expire($usecache)
        $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
 {
@@ -201,6 +232,7 @@ class ShowRSS_XML
        var $is_item;
        var $tag;
        var $encoding;
+       var $modified_date;
 
        function parse($buf)
        {
@@ -208,33 +240,39 @@ class ShowRSS_XML
                $this->item    = array();
                $this->is_item = FALSE;
                $this->tag     = '';
-
+               $utf8 = 'UTF-8';
+               $this->encoding = $utf8;
                // Detect encoding
-               $this->encoding = mb_detect_encoding($buf);
-               if (! in_array(strtolower($this->encoding), array('us-ascii', 'iso-8859-1', 'utf-8'))) {
-                       $buf = mb_convert_encoding($buf, 'utf-8', $this->encoding);
-                       $this->encoding = 'utf-8';
+               $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', '<?xml $1', $buf);
+                               // xml_parse() requires UTF-8 compatible encoding
+                               $buf = mb_convert_encoding($buf, $utf8, $encoding);
+                       }
                }
-               $xml_parser = xml_parser_create($this->encoding);
+               // Parsing
+               $xml_parser = xml_parser_create($utf8);
                xml_set_element_handler($xml_parser, array(& $this, 'start_element'), array(& $this, 'end_element'));
                xml_set_character_data_handler($xml_parser, array(& $this, 'character_data'));
-
                if (! xml_parse($xml_parser, $buf, 1)) {
-                       return(sprintf('XML error: %s at line %d in %s',
+                       return sprintf('XML error: %s at line %d in %s',
                                xml_error_string(xml_get_error_code($xml_parser)),
-                               xml_get_current_line_number($xml_parser), $buf));
+                               xml_get_current_line_number($xml_parser),
+                               (strlen($buf) < 500 ? $buf : substr($buf, 0, 500) . '...'));
                }
                xml_parser_free($xml_parser);
-
                return $this->items;
        }
 
        function escape($str)
        {
-               // Unescape already-escaped chars (&lt;, &gt;, &amp;, ...) in RSS body before htmlspecialchars()
+               // Unescape already-escaped chars (&lt;, &gt;, &amp;, ...) in RSS body before htmlsc()
                $str = strtr($str, array_flip(get_html_translation_table(ENT_COMPAT)));
                // Escape
-               $str = htmlspecialchars($str);
+               $str = htmlsc($str);
                // Encoding conversion
                $str = mb_convert_encoding($str, SOURCE_ENCODING, $this->encoding);
                return trim($str);
@@ -259,18 +297,21 @@ class ShowRSS_XML
                $this->item = array();
 
                if (isset($item['DC:DATE'])) {
-                       $time = plugin_showrss_get_timestamp($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']);
-                       
-               } else if (isset($item['DESCRIPTION']) &&
-                       ($description = trim($item['DESCRIPTION'])) != '' &&
-                       ($time = strtotime($description)) != -1) {
-                               $time -= LOCALZONE;
-
+                       $time = plugin_showrss_get_timestamp($item['PUBDATE'], $this->modified_date);
                } else {
-                       $time = time() - LOCALZONE;
+                       $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']);
@@ -287,22 +328,23 @@ class ShowRSS_XML
        }
 }
 
-function plugin_showrss_get_timestamp($str)
+function plugin_showrss_get_timestamp($str, $default_date)
 {
-       if (($str = trim($str)) == '')
-               return UTIME;
+       $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)) {
+       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 == -1) ? UTIME : $time - LOCALZONE;
-       }
-       $str  = $matches[1];
-       $time = strtotime($matches[1] . ' ' . $matches[2]);
-       if (! empty($matches[3])) {
-               $diff = ($matches[5] * 60 + $matches[6]) * 60;
-               $time += ($matches[4] == '-' ? $diff : -$diff);
+               return ($time === FALSE || $time === -1) ? $default_date : $time - LOCALZONE;
        }
-       return $time;
 }
-?>