2 // PukiWiki - Yet another WikiWikiWeb clone
5 // 2002-2017 PukiWiki Development Team
6 // 2002 PANDA <panda@arino.jp>
7 // (Original)hiro_do3ob@yahoo.co.jp
8 // License: GPL, same as PukiWiki
10 // Show RSS (of remote site) plugin
12 // * This plugin needs 'PHP xml extension'
13 // * Cache data will be stored as CACHE_DIR/*.tmp
15 define('PLUGIN_SHOWRSS_USAGE', '#showrss(URI-to-RSS[,default|menubar|recent[,Cache-lifetime[,Show-timestamp]]])');
17 // Show related extensions are found or not
18 function plugin_showrss_action()
20 if (PKWK_SAFE_MODE) die_message('PKWK_SAFE_MODE prohibit this');
23 foreach(array('xml', 'mbstring') as $extension){
24 $$extension = extension_loaded($extension) ?
25 '&color(green){Found};' :
26 '&color(red){Not found};';
27 $body .= '| ' . $extension . ' extension | ' . $$extension . ' |' . "\n";
29 return array('msg' => 'showrss_info', 'body' => convert_html($body));
32 function plugin_showrss_convert()
36 if (! isset($_xml)) $_xml = extension_loaded('xml');
37 if (! $_xml) return '#showrss: xml extension is not found<br />' . "\n";
39 $num = func_num_args();
40 if ($num == 0) return PLUGIN_SHOWRSS_USAGE . '<br />' . "\n";
42 $argv = func_get_args();
45 $template = $uri = '';
47 case 4: $timestamp = (trim($argv[3]) == '1'); /*FALLTHROUGH*/
48 case 3: $cachehour = trim($argv[2]); /*FALLTHROUGH*/
49 case 2: $template = strtolower(trim($argv[1]));/*FALLTHROUGH*/
50 case 1: $uri = trim($argv[0]);
53 $class = ($template == '' || $template == 'default') ? 'ShowRSS_html' : 'ShowRSS_html_' . $template;
54 if (! class_exists($class)) $class = 'ShowRSS_html';
56 if (! is_numeric($cachehour))
57 return '#showrss: Cache-lifetime seems not numeric: ' . htmlsc($cachehour) . '<br />' . "\n";
59 return '#showrss: Seems not URI: ' . htmlsc($uri) . '<br />' . "\n";
61 // Remove old caches in 5% rate
62 if (mt_rand(1, 20) === 1) {
63 plugin_showrss_cache_expire(24);
65 list($rss, $time) = plugin_showrss_get_rss($uri, $cachehour);
66 if ($rss === FALSE) return '#showrss: Failed fetching RSS from the server<br />' . "\n";
67 if (! is_array($rss)) {
68 // Show XML error message
69 return '#showrss: Error - ' . htmlsc($rss) . '<br />' . "\n";
73 $time_display = '<p style="font-size:10px; font-weight:bold">Last-Modified:' .
74 get_date('Y/m/d H:i:s', $time) . '</p>';
77 $obj = new $class($rss);
78 return $obj->toString($time_display);
81 // Create HTML from RSS array()
87 function ShowRSS_html($rss)
89 $this->__construct($rss);
91 function __construct($rss)
93 foreach ($rss as $date=>$items) {
94 foreach ($items as $item) {
95 $link = $item['LINK'];
96 $title = $item['TITLE'];
97 $date = get_date_atom($item['_TIMESTAMP'] + LOCALZONE);
98 $link = '<a href="' . $link . '" data-mtime="' .
99 $date . '" class="' . get_link_passage_class() .
100 '" rel="nofollow">' . $title . '</a>';
101 $this->items[$date][] = $this->format_link($link);
106 function format_link($link)
108 return $link . '<br />' . "\n";
111 function format_list($date, $str)
116 function format_body($str)
121 function toString($timestamp)
124 foreach ($this->items as $date=>$items)
125 $retval .= $this->format_list($date, join('', $items));
126 $retval = $this->format_body($retval);
135 class ShowRSS_html_menubar extends ShowRSS_html
137 var $class = ' class="small"';
139 function format_link($link) {
140 return '<li>' . $link . '</li>' . "\n";
143 function format_body($str) {
144 return '<ul class="recent_list">' . "\n" . $str . '</ul>' . "\n";
148 class ShowRSS_html_recent extends ShowRSS_html
150 var $class = ' class="small"';
152 function format_link($link) {
153 return '<li>' . $link . '</li>' . "\n";
156 function format_list($date, $str) {
157 return '<strong>' . $date . '</strong>' . "\n" .
158 '<ul class="recent_list">' . "\n" . $str . '</ul>' . "\n";
163 function plugin_showrss_get_rss($target, $cachehour)
168 $filename = CACHE_DIR . encode($target) . '.tmp';
169 // Remove expired cache
170 plugin_showrss_cache_expire_file($filename, $cachehour);
171 // Get the cache not expired
172 if (is_readable($filename)) {
173 $buf = join('', file($filename));
174 $time = filemtime($filename) - LOCALZONE;
178 if (is_null($time)) {
180 $data = pkwk_http_request($target);
181 if ($data['rc'] !== 200) {
182 return array(FALSE, 0);
184 $buf = $data['data'];
187 // Save RSS into cache
189 $fp = fopen($filename, 'w');
195 $obj = new ShowRSS_XML();
196 $obj->modified_date = (is_null($time) ? UTIME : $time);
197 return array($obj->parse($buf),$time);
200 // Remove cache if expired limit exeed
201 function plugin_showrss_cache_expire($cachehour)
203 $expire = $cachehour * 60 * 60; // Hour
204 $dh = dir(CACHE_DIR);
205 while (($file = $dh->read()) !== FALSE) {
206 if (substr($file, -4) != '.tmp') continue;
207 $file = CACHE_DIR . $file;
208 $last = time() - filemtime($file);
209 if ($last > $expire) unlink($file);
215 * Remove single file cache if expired limit exeed
219 function plugin_showrss_cache_expire_file($filename, $cachehour)
221 $expire = $cachehour * 60 * 60; // Hour
222 $last = time() - filemtime($filename);
223 if ($last > $expire) {
228 // Get RSS and array() them
240 $this->items = array();
241 $this->item = array();
242 $this->is_item = FALSE;
245 $this->encoding = $utf8;
248 if(preg_match('/<\?xml [^>]*\bencoding="([a-z0-9-_]+)"/i', $buf, $matches)) {
249 $encoding = $matches[1];
250 if (strtoupper($encoding) !== $utf8) {
251 // xml_parse() fails on non UTF-8 encoding attr in XML decLaration
252 $buf = preg_replace('/<\?xml ([^>]*)\bencoding="[a-z0-9-_]+"/i', '<?xml $1', $buf);
253 // xml_parse() requires UTF-8 compatible encoding
254 $buf = mb_convert_encoding($buf, $utf8, $encoding);
258 $xml_parser = xml_parser_create($utf8);
259 xml_set_element_handler($xml_parser, array(& $this, 'start_element'), array(& $this, 'end_element'));
260 xml_set_character_data_handler($xml_parser, array(& $this, 'character_data'));
261 if (! xml_parse($xml_parser, $buf, 1)) {
262 return sprintf('XML error: %s at line %d in %s',
263 xml_error_string(xml_get_error_code($xml_parser)),
264 xml_get_current_line_number($xml_parser),
265 (strlen($buf) < 500 ? $buf : substr($buf, 0, 500) . '...'));
267 xml_parser_free($xml_parser);
271 function escape($str)
273 // Unescape already-escaped chars (<, >, &, ...) in RSS body before htmlsc()
274 $str = strtr($str, array_flip(get_html_translation_table(ENT_COMPAT)));
276 $str = htmlsc($str, ENT_COMPAT, $this->encoding);
277 // Encoding conversion
278 $str = mb_convert_encoding($str, SOURCE_ENCODING, $this->encoding);
283 function start_element($parser, $name, $attrs)
285 if ($this->is_item !== FALSE) {
287 if ($this->is_item === 'ENTRY' && $name === 'LINK' && isset($attrs['HREF'])) {
288 if (! isset($this->item[$name])) $this->item[$name] = '';
289 $this->item[$name] .= $attrs['HREF'];
291 } else if ($name === 'ITEM') {
292 $this->is_item = 'ITEM';
293 } else if ($name === 'ENTRY') {
294 $this->is_item = 'ENTRY';
299 function end_element($parser, $name)
301 if ($this->is_item === FALSE || $name !== $this->is_item) return;
303 $item = array_map(array(& $this, 'escape'), $this->item);
304 $this->item = array();
306 if (isset($item['DC:DATE'])) {
307 $time = plugin_showrss_get_timestamp($item['DC:DATE'], $this->modified_date);
308 } else if (isset($item['PUBDATE'])) {
309 $time = plugin_showrss_get_timestamp($item['PUBDATE'], $this->modified_date);
310 } else if (isset($item['UPDATED'])) {
311 $time = plugin_showrss_get_timestamp($item['UPDATED'], $this->time);
313 $time_from_desc = FALSE;
314 if (isset($item['DESCRIPTION']) &&
315 (($description = trim($item['DESCRIPTION'])) != '')) {
316 $time_from_desc = strtotime($description);
318 if ($time_from_desc !== FALSE && $time_from_desc !== -1) {
319 $time = $time_from_desc - LOCALZONE;
321 $time = time() - LOCALZONE;
324 $item['_TIMESTAMP'] = $time;
325 $date = get_date('Y-m-d', $item['_TIMESTAMP']);
327 $this->items[$date][] = $item;
328 $this->is_item = FALSE;
331 function character_data($parser, $data)
333 if ($this->is_item === FALSE) return;
334 if (! isset($this->item[$this->tag])) $this->item[$this->tag] = '';
335 $this->item[$this->tag] .= $data;
339 function plugin_showrss_get_timestamp($str, $default_date)
342 if ($str == '') return UTIME;
345 if (preg_match('/(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2})(([+-])(\d{2}):(\d{2}))?/', $str, $matches)) {
346 $time = strtotime($matches[1] . ' ' . $matches[2]);
347 if ($time === FALSE || $time === -1) {
348 $time = $default_date;
349 } else if (isset($matches[3])) {
350 $diff = ($matches[5] * 60 + $matches[6]) * 60;
351 $time += ($matches[4] == '-' ? $diff : -$diff);
355 $time = strtotime($str);
356 return ($time === FALSE || $time === -1) ? $default_date : $time - LOCALZONE;