3a6cc5c8a53c17a55eba7ce06561337212803305
[atutor.git] / mods / wiki / plugins / lib / feed.php
1 <?php
2 /*
3    Outputs RSS/Atom feeds, multiple versions supported (RSS 0.91, 1.0, 2.0,
4    3.0 and Atom 0.3); what clients get depends on what they asked for:
5     - Accept: text/x-rss; revision=2.0
6     - Accept: application/atom+xml
7 */
8
9 //define("EWIKI_DESC", "ThisWiki - site slogan...");  # site description
10 //define("EWIKI_COPY", "PrimarilyPublicDomain");      # site copyright
11 //define("EWIKI_CATEGORY", "Technique");              # site subject
12 //define("EWIKI_LOGO_URL", "http://.../logo.png");
13
14
15 #---------------------------------------------------------------------------
16 # outputs a feed from the given array of page $data hashes
17 #
18 function ewiki_feed($pages, $type="AUTO")
19 {
20    global $ewiki_config;
21    ob_end_clean();
22
23    #-- general feed infos
24    $feed_info = array(
25       "title" => EWIKI_NAME,
26       "lang" => EWIKI_DEFAULT_LANG,
27       "desc" => defined("EWIKI_DESC")?EWIKI_DESC : EWIKI_NAME . " - an open hypertext site",
28       "copyright" => defined("EWIKI_COPY")?EWIKI_COPY : "copyrighted",
29       "category" => defined("EWIKI_CATEGORY")?EWIKI_CATEGORY : "None",
30 //    "logo" => defined("EWIKI_LOGO_URL")?EWIKI_LOGO_URL : "http://erfurtwiki.sf.net/squirrel.jpeg",
31       "url" => ewiki_script_url(),
32       "rc_url" => ewiki_script_url("", "UpdatedPages"),
33       "ewiki" => "ewiki/".EWIKI_VERSION,
34       "charset" => EWIKI_CHARSET,
35    );
36    
37    #-- fix/prepare feed entries
38    $lm = UNIX_MILLENNIUM;
39    foreach ($pages as $i=>$data) {
40       if (!is_array($data)) {
41          $data = ewiki_db::GET($data);
42          $pages[$i] = $data;
43       }
44       // ...
45       if ($data["id"]) {
46          if (empty($data["title"])) { $pages[$i]["title"] = ewiki_split_title($data["id"], -1, 0); }
47          if (empty($data["url"])) { $pages[$i]["url"] = ewiki_script_url("", $data["id"]); }
48          if (empty($data["uri"])) { $pages[$i]["uri"] = "x-wiki:".EWIKI_NAME.":".ewiki_xmlentities(urlencode($data["id"])); }
49          if (empty($data["guid"])) { $pages[$i]["guid"] = ewiki_script_url("", $data["id"], "version=$data[version]"); }
50       }
51       $pages[$i]["content"] = strtr(ewiki_xmlentities(substr($data["content"], 0, 300)), "\r\n\t\f", "    ");
52       $pages[$i]["pdate"] = gmstrftime("%a, %d %b %G %T %Z", $data["lastmodified"]);
53       $pages[$i]["idate"] = gmstrftime("%G%m%dT%TZ", $data["lastmodified"]);
54       $pages[$i]["icdate"] = gmstrftime("%G%m%dT%TZ", $data["created"]);
55       if ($lm < $data["lastmodified"]) {
56          $lm = $data["lastmodified"];
57       }
58       $data["content"] = "";
59    }
60    $info["modified"] = $lm;
61
62    #-- respect some common parameters
63    if (($limit = $_REQUEST["limit"])
64    or ($limit = $_REQUEST["items"])
65    or ($limit = $ewiki_config["list_limit_rss"])
66    or ($limit = $ewiki_config["list_limit"])) {
67       $pages = array_slice($pages, 0, $limit);
68    }
69    
70    #-- encode everything into UTF-8?
71    // no
72
73    #-- engage compression
74    if ($_SERVER["HTTP_ACCEPT_ENCODING"]) {
75       ob_start("ob_gzhandler");
76       ob_implicit_flush(0);
77    }
78
79    #-- what to return
80    if (!is_string($type) || (strtoupper($type)=="AUTO") || ($type=="*")) {
81       $type = ewiki_feed_type();
82    }
83    header("Vary: accept,negotiate");
84    header("TCN: choice");
85    switch ($type) {
86       case "RSS0":
87          ewiki_feed_rss0($feed_info, $pages);
88       case "RSS2":
89          ewiki_feed_rss2($feed_info, $pages);
90       case "RSS3":
91          ewiki_feed_rss3($feed_info, $pages);
92       case "RSS1":
93          ewiki_feed_rss1($feed_info, $pages);
94       case "ATOM":
95          ewiki_feed_atom($feed_info, $pages);
96       case "DUMB":
97       default:
98          header("Status: 406 Not Acceptable");
99          header("Content-Type: text/plain");
100          die("You are using a pretty dumb feed reader, it didn't\n".
101              "send any appropriate Accept: header. Go away.");
102    }
103    die();
104 }
105
106
107
108 #---------------------------------------------------------------------------
109 # returns Netscape RSS 0.91 (other versions are neglectable)
110 #
111 function ewiki_feed_rss0($info, $pages)
112 {
113 #   header('Content-Type: text/x-rss');
114    header('Content-Type: application/rss+xml; revision="0.91"');
115
116    $pages = array_slice($pages, 0, 15);
117    $name = $info["title"];
118
119 echo<<<EOT
120 <?xml version="1.0" encoding="$info[charset]"?>
121 <!DOCTYPE rss PUBLIC "-//Netscape Communications//DTD RSS 0.91//EN" "http://my.netscape.com/publish/formats/rss-0.91.dtd">
122 <rss version="0.91">
123  <channel>
124     <title>$info[title]</title>
125     <language>$info[lang]</language>
126     <description>$info[desc]</description>
127     <link>$info[url]</link>  \n
128 EOT;
129
130    #-- items
131    foreach ($pages as $data) {
132 echo<<<EOT
133    <item>
134      <title>$data[title]</title>
135      <link>$data[url]</link>
136      <description>$data[content]</description>
137      <pubDate>$data[pdate]</pubDate>
138    </item>\n
139 EOT;
140    } //<?
141
142 echo " </channel>\n</rss>\n";
143    die();
144 }
145
146
147
148 #---------------------------------------------------------------------------
149 # writes RSS 2.0
150 #
151 function ewiki_feed_rss2($info, $pages)
152 {
153    header('Content-Type: application/rss+xml; revision="2.0"');
154
155    $name = $info["title"];
156
157 echo<<<EOT
158 <?xml version="1.0" encoding="$info[charset]"?>
159 <rss version="2.0">
160  <channel>
161    <title>$info[title]</title>
162    <link>$info[url]</link>  
163    <language>$info[lang]</language>
164    <description>$info[desc]</description>
165    <generator>$info[ewiki]</generator>
166    <webMaster>$_SERVER[SERVER_ADMIN]</webMaster>\n
167 EOT;
168
169    #-- items
170    foreach ($pages as $data) {
171 echo<<<EOT
172    <item>
173      <title>$data[title]</title>
174      <link>$data[url]</link>
175      <description>$data[content]</description>
176      <pubDate>$data[pdate]</pubDate>
177      <guid>$data[guid]</guid>
178    </item>\n
179 EOT;
180    } //<?
181
182 echo " </channel>\n</rss>\n";
183    die();
184 }
185
186
187
188 #---------------------------------------------------------------------------
189 # outputs RSS 3.0 (text/plain, much like 822)
190 #
191 function ewiki_feed_rss3($info, $pages)
192 {
193    header('Content-Type: text/plain; charset="'.$info["charset"].'"');
194
195    $name = $info["title"];
196
197 echo<<<EOT
198 title: $info[title]
199 link: $info[url]
200 language: $info[lang]
201 description: $info[desc]
202 generator: $info[ewiki]
203 webMaster: $_SERVER[SERVER_ADMIN]
204 rights: $info[copyright]
205 \n
206 EOT;
207
208    #-- items
209    foreach ($pages as $data) {
210 echo<<<EOT
211 title: $data[title]
212 link: $data[url]
213 guid: $data[guid]
214 uri: $data[uri]
215 description: $data[content]
216 created: $data[icdate]
217 last-modified: $data[idate]
218 \n
219 EOT;
220    }
221
222    echo "\n";
223    die();
224 }
225
226
227
228 #---------------------------------------------------------------------------
229 # returns RDF/RSS1.0 (no real RSS, obsoleted by Atom)
230 #
231 function ewiki_feed_rss1($info, $pages)
232 {
233    header('Content-Type: application/rss+xml; revision="1.0"');
234    
235    $name = $info["title"];
236    $urnpfix = "x-wiki";  // uniform resource name prefix ("urn:x-wiki" was ok)
237
238    #-- parts
239    $_logo = $info["logo"] ? "<image rdf:resource=\"$urnpfix:$name:logo#1\" />\n" : "";
240
241 echo<<<EOT
242 <?xml version="1.0" encoding="$info[charset]"?>
243 <rdf:RDF
244 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
245 xmlns:dc="http://purl.org/dc/elements/1.1/"
246 xmlns:RSS="http://purl.org/rss/1.0/"
247 xmlns:wiki="http://purl.org/rss/1.0/modules/wiki/">
248   <RSS:channel rdf:about="$info[url]">
249     <RSS:title>$name</RSS:title>
250     <dc:title>$name</dc:title>
251     <RSS:link>$info[rc_url]</RSS:link>
252     <RSS:description>UpdatedPages on $name</RSS:description>
253     <wiki:interwiki>$name</wiki:interwiki>
254     $_logo
255     <RSS:items>
256       <rdf:Seq>\n
257 EOT;
258    #-- output Seq ids
259    foreach ($pages as $i=>$data) {
260       $pages[$i]["rdf_id"] = "$urnpfix:$name:".ewiki_xmlentities(urlencode($data["id"]))."#$data[version]_$data[lastmodified]";
261       echo '        <rdf:li rdf:resource="'.$pages[$i]["rdf_id"].'"/>' . "\n";
262    } //<?
263 echo<<<EOT
264       </rdf:Seq>
265     </RSS:items>
266   </RSS:channel>
267 <!-- RDF associated data for references from above -->\n
268 EOT;
269    #-- logo
270    if ($_logo) {
271 echo<<<EOT
272   <RSS:image rdf:about="$urnpfix:$name:logo#1">
273     <RSS:title>$name</RSS:title>
274     <RSS:link>$url</RSS:link>
275     <RSS:url>$info[logo]</RSS:url>
276   </RSS:image>\n
277 EOT;
278    } //<?
279
280    #-- write items
281    foreach ($pages as $i=>$data) {
282      preg_match('/^([^\s]+).+\(.*([\d.]+)\)/', $data["author"], $uu);
283      $author_host = $uu[2];
284      $author_name = $uu[1];
285      $url_diff = ewiki_script_url("diff", $data["id"]);
286      $url_info = ewiki_script_url("info", $data["id"]);
287      $stat = ($data["version"]==1) ? "created" : "updated";
288 echo<<<EOT
289   <RSS:item rdf:about="$data[rdf_id]">
290     <RSS:title>$data[title]</RSS:title>
291     <dc:title>$data[title]</dc:title>
292     <RSS:description>$data[content]</RSS:description>
293     <RSS:link>$data[url]</RSS:link>
294     <wiki:diff>$url_diff</wiki:diff>
295     <wiki:history>$url_info</wiki:history>
296     <wiki:version>$data[version]</wiki:version>
297     <wiki:status>$stat</wiki:status>
298     <dc:date>$data[idate]</dc:date>
299     <dc:contributor>
300       <rdf:Description wiki:host="$author_host">
301         <rdf:value>$author_name</rdf:value>
302       </rdf:Description>
303     </dc:contributor>
304   </RSS:item>\n
305 EOT;
306    } //<?
307
308    echo "</rdf:RDF>\n";
309    die();
310 }
311
312
313
314 #---------------------------------------------------------------------------
315 # returns ATOM 0.3 feed
316 #
317 function ewiki_feed_atom($info, $pages)
318 {
319    header('Content-Type: application/atom+xml');
320    $name = $info["title"];
321    $ilm = gmstrftime("%G%m%dT%TZ", $info["modified"]);
322    
323 echo<<<EOT
324 <?xml version="1.0" encoding="$info[charset]"?>
325 <feed version="0.3" xmlns="http://purl.org/atom/ns#">
326   <title>$name</title>
327   <link rel="alternate" type="text/html" href="$info[url]"/>
328   <modified>$ilm</modified>
329   <author>*</author>
330   <generator>$info[ewiki]</generator>\n
331 EOT;
332
333    #-- write items
334    foreach ($pages as $i=>$data) {
335       $etag = ewiki_etag($data);
336       echo<<<EOT
337   <entry>
338     <title>$data[title]</title>
339     <link rel="alternate" type="text/html" href="$data[url]"/>
340     <id>$etag</id>
341     <issued>$data[icdate]</issued>
342     <created>$data[icdate]</created>
343     <modified>$data[idate]</modified>
344     <content>$data[content]</content>
345   </entry>\n
346 EOT;
347    } //<?
348
349    echo "</feed>\n";
350    die();
351 }
352
353
354
355 #---------------------------------------------------------------------------
356 # checks HTTP Accept: header for guessing what's desired
357 # (btw, we just ignore dumb and HTTP incompliant clients)
358 #
359 function ewiki_feed_type()
360 {
361    $regex_RSS = '#^(text|application)/(x[-.])*rss(\+xml)?$|^\*/\*$#';
362    $regex_RDF = '#^(text|application)/(x[-.])*(rss[-.+])?rdf([-.]rss)?(\+xml)?$#';
363    $regex_ATOM= '#^(text|application)/(x[-.])*atom(\+xml)*$#';
364    $what = "DUMB";
365    $types = ewiki_sort_accept($_SERVER["HTTP_ACCEPT"]);
366    foreach ($types as $type=>$attr) {
367       #-- RSS
368       if (preg_match($regex_RSS, $type) || ($type == "text/xml")) {
369          $ver = isset($attr["version"]) ? (int) $attr["version"] : (int) $attr["revision"];
370          if ($ver < 4) {
371             $WHAT = "RSS$ver";    // one of "RSS0", "RSS1", "RSS2", "RSS3"
372             if ($ver != 1) {
373                break;
374             }
375          }
376       }
377       #-- ATOM
378       elseif (preg_match($regex_ATOM, $type)) {
379          $WHAT = "ATOM";
380          break;
381       }
382       #-- exceptions
383       elseif (preg_match($regex_RDF, $type))
384          { $WHAT = "RSS1"; break; }
385       elseif ($type == "text/plain")
386          { $WHAT = "RSS3"; break; }
387       elseif ($type == "*/*")
388          { $WHAT = "RSS2"; }
389    }
390    return($WHAT);
391 }
392
393
394
395 #---------------------------------------------------------------------------
396 # evaluates and sorts Accept: header (and alikes)
397 #
398 function ewiki_sort_accept($str) {
399    $r = array();
400    $attr = array();
401    $def = 0.99;
402    foreach (explode(",", $str) as $type) {
403       $type = strtok(trim($type), ";");
404       $q = ($def *= 0.99);
405       if ($params = trim(strtok("\000"))) {
406          foreach (explode(";", $params) as $p) {
407             $pname = trim(strtok($p, "="));
408             $val = trim(strtok("\000"));
409             if ($pname == "q") {
410                ($q = $val * 1.0) or ($q = $def);
411             }
412             $attr[$type][$pname] = $val;
413          }
414       }
415       $r[$type] = $q;
416    }
417    arsort($r);
418    foreach ($r as $t=>$uu) {
419       $r[$t] = $attr[$t];
420    }
421    return($r);   
422 }
423
424
425 #-- PHP backwards compatibility
426 if (!function_exists("xmlentities")) {
427   function xmlentities($str) {
428      return strtr($str, array("&"=>"&amp;", "<"=>"&lt;",
429           ">"=>"&gt;", "\""=>"&quot;", "'"=>"&apos;"));
430   } 
431 }
432
433 #-- does not reescape numeric XML entities
434 function ewiki_xmlentities($str) {
435    $xe = array("&"=>"&amp;", "<"=>"&lt;", ">"=>"&gt;", "\""=>"&quot;", "'"=>"&apos;");
436    return preg_replace("/(&(?>!#x|#\d)|[<>\"'])/e", '$xe["$1"]', $str);
437 }
438
439
440 ?>