OSDN Git Service

BugTrack/2420 AutoTicketLink - Improve regex and JSON encode
[pukiwiki/pukiwiki.git] / plugin / showrss.inc.php
1 <?php
2 // PukiWiki - Yet another WikiWikiWeb clone
3 // showrss.inc.php
4 // Copyright:
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
9 //
10 // Show RSS (of remote site) plugin
11 // NOTE:
12 //    * This plugin needs 'PHP xml extension'
13 //    * Cache data will be stored as CACHE_DIR/*.tmp
14
15 define('PLUGIN_SHOWRSS_USAGE', '#showrss(URI-to-RSS[,default|menubar|recent[,Cache-lifetime[,Show-timestamp]]])');
16
17 // Show related extensions are found or not
18 function plugin_showrss_action()
19 {
20         if (PKWK_SAFE_MODE) die_message('PKWK_SAFE_MODE prohibit this');
21
22         $body = '';
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";
28         }
29         return array('msg' => 'showrss_info', 'body' => convert_html($body));
30 }
31
32 function plugin_showrss_convert()
33 {
34         static $_xml;
35
36         if (! isset($_xml)) $_xml = extension_loaded('xml');
37         if (! $_xml) return '#showrss: xml extension is not found<br />' . "\n";
38
39         $num = func_num_args();
40         if ($num == 0) return PLUGIN_SHOWRSS_USAGE . '<br />' . "\n";
41
42         $argv = func_get_args();
43         $timestamp = FALSE;
44         $cachehour = 0;
45         $template = $uri = '';
46         switch ($num) {
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]);
51         }
52
53         $class = ($template == '' || $template == 'default') ? 'ShowRSS_html' : 'ShowRSS_html_' . $template;
54         if (! class_exists($class)) $class = 'ShowRSS_html';
55
56         if (! is_numeric($cachehour))
57                 return '#showrss: Cache-lifetime seems not numeric: ' . htmlsc($cachehour) . '<br />' . "\n";
58         if (! is_url($uri))
59                 return '#showrss: Seems not URI: ' . htmlsc($uri) . '<br />' . "\n";
60
61         // Remove old caches in 5% rate
62         if (mt_rand(1, 20) === 1) {
63                 plugin_showrss_cache_expire(24);
64         }
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";
70         }
71         $time_display = '';
72         if ($timestamp > 0) {
73                 $time_display = '<p style="font-size:10px; font-weight:bold">Last-Modified:' .
74                         get_date('Y/m/d H:i:s', $time) .  '</p>';
75         }
76
77         $obj = new $class($rss);
78         return $obj->toString($time_display);
79 }
80
81 // Create HTML from RSS array()
82 class ShowRSS_html
83 {
84         var $items = array();
85         var $class = '';
86
87         function ShowRSS_html($rss)
88         {
89                 $this->__construct($rss);
90         }
91         function __construct($rss)
92         {
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);
102                         }
103                 }
104         }
105
106         function format_link($link)
107         {
108                 return $link . '<br />' . "\n";
109         }
110
111         function format_list($date, $str)
112         {
113                 return $str;
114         }
115
116         function format_body($str)
117         {
118                 return $str;
119         }
120
121         function toString($timestamp)
122         {
123                 $retval = '';
124                 foreach ($this->items as $date=>$items)
125                         $retval .= $this->format_list($date, join('', $items));
126                 $retval = $this->format_body($retval);
127                 return <<<EOD
128 <div{$this->class}>
129 $retval$timestamp
130 </div>
131 EOD;
132         }
133 }
134
135 class ShowRSS_html_menubar extends ShowRSS_html
136 {
137         var $class = ' class="small"';
138
139         function format_link($link) {
140                 return '<li>' . $link . '</li>' . "\n";
141         }
142
143         function format_body($str) {
144                 return '<ul class="recent_list">' . "\n" . $str . '</ul>' . "\n";
145         }
146 }
147
148 class ShowRSS_html_recent extends ShowRSS_html
149 {
150         var $class = ' class="small"';
151
152         function format_link($link) {
153                 return '<li>' . $link . '</li>' . "\n";
154         }
155
156         function format_list($date, $str) {
157                 return '<strong>' . $date . '</strong>' . "\n" .
158                         '<ul class="recent_list">' . "\n" . $str . '</ul>' . "\n";
159         }
160 }
161
162 // Get and save RSS
163 function plugin_showrss_get_rss($target, $cachehour)
164 {
165         $buf  = '';
166         $time = NULL;
167         if ($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;
175                 }
176         }
177
178         if (is_null($time)) {
179                 // Newly get RSS
180                 $data = pkwk_http_request($target);
181                 if ($data['rc'] !== 200) {
182                         return array(FALSE, 0);
183                 }
184                 $buf = $data['data'];
185                 $time = UTIME;
186
187                 // Save RSS into cache
188                 if ($cachehour) {
189                         $fp = fopen($filename, 'w');
190                         fwrite($fp, $buf);
191                         fclose($fp);
192                 }
193         }
194         // Parse
195         $obj = new ShowRSS_XML();
196         $obj->modified_date = (is_null($time) ? UTIME : $time);
197         return array($obj->parse($buf),$time);
198 }
199
200 // Remove cache if expired limit exeed
201 function plugin_showrss_cache_expire($cachehour)
202 {
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);
210         }
211         $dh->close();
212 }
213
214 /**
215  * Remove single file cache if expired limit exeed
216  * @param $filename
217  * @param $cachehour
218  */
219 function plugin_showrss_cache_expire_file($filename, $cachehour)
220 {
221         $expire = $cachehour * 60 * 60; // Hour
222         $last = time() - filemtime($filename);
223         if ($last > $expire) {
224                 unlink($filename);
225         }
226 }
227
228 // Get RSS and array() them
229 class ShowRSS_XML
230 {
231         var $items;
232         var $item;
233         var $is_item;
234         var $tag;
235         var $encoding;
236         var $modified_date;
237
238         function parse($buf)
239         {
240                 $this->items   = array();
241                 $this->item    = array();
242                 $this->is_item = FALSE;
243                 $this->tag     = '';
244                 $utf8 = 'UTF-8';
245                 $this->encoding = $utf8;
246                 // Detect encoding
247                 $matches = array();
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);
255                         }
256                 }
257                 // Parsing
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) . '...'));
266                 }
267                 xml_parser_free($xml_parser);
268                 return $this->items;
269         }
270
271         function escape($str)
272         {
273                 // Unescape already-escaped chars (&lt;, &gt;, &amp;, ...) in RSS body before htmlsc()
274                 $str = strtr($str, array_flip(get_html_translation_table(ENT_COMPAT)));
275                 // Escape
276                 $str = htmlsc($str, ENT_COMPAT, $this->encoding);
277                 // Encoding conversion
278                 $str = mb_convert_encoding($str, SOURCE_ENCODING, $this->encoding);
279                 return trim($str);
280         }
281
282         // Tag start
283         function start_element($parser, $name, $attrs)
284         {
285                 if ($this->is_item !== FALSE) {
286                         $this->tag = $name;
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'];
290                         }
291                 } else if ($name === 'ITEM') {
292                         $this->is_item = 'ITEM';
293                 } else if ($name === 'ENTRY') {
294                         $this->is_item = 'ENTRY';
295                 }
296         }
297
298         // Tag end
299         function end_element($parser, $name)
300         {
301                 if ($this->is_item === FALSE || $name !== $this->is_item) return;
302
303                 $item = array_map(array(& $this, 'escape'), $this->item);
304                 $this->item = array();
305
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);
312                 } else {
313                         $time_from_desc = FALSE;
314                         if (isset($item['DESCRIPTION']) &&
315                                 (($description = trim($item['DESCRIPTION'])) != '')) {
316                                 $time_from_desc = strtotime($description);
317                         }
318                         if ($time_from_desc !== FALSE && $time_from_desc !== -1) {
319                                 $time = $time_from_desc - LOCALZONE;
320                         } else {
321                                 $time = time() - LOCALZONE;
322                         }
323                 }
324                 $item['_TIMESTAMP'] = $time;
325                 $date = get_date('Y-m-d', $item['_TIMESTAMP']);
326
327                 $this->items[$date][] = $item;
328                 $this->is_item        = FALSE;
329         }
330
331         function character_data($parser, $data)
332         {
333                 if ($this->is_item === FALSE) return;
334                 if (! isset($this->item[$this->tag])) $this->item[$this->tag] = '';
335                 $this->item[$this->tag] .= $data;
336         }
337 }
338
339 function plugin_showrss_get_timestamp($str, $default_date)
340 {
341         $str = trim($str);
342         if ($str == '') return UTIME;
343
344         $matches = array();
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);
352                 }
353                 return $time;
354         } else {
355                 $time = strtotime($str);
356                 return ($time === FALSE || $time === -1) ? $default_date : $time - LOCALZONE;
357         }
358 }