Rev 172 Rev 185
1 <?php 1 <?php
2 # vim:et:ts=4:sts=4:sw=4:fdm=marker: 2 # vim:et:ts=4:sts=4:sw=4:fdm=marker:
3 # {{{ Info 3 # {{{ Info
4 /*************************************************************************** 4 /***************************************************************************
5   5  
6 FeedCreator class v1.6 6 FeedCreator class v1.6
7 originally (c) Kai Blankenhorn 7 originally (c) Kai Blankenhorn
8 www.bitfolge.de 8 www.bitfolge.de
9 kaib@bitfolge.de 9 kaib@bitfolge.de
10 v1.3 work by Scott Reynen (scott@randomchaos.com) and Kai Blankenhorn 10 v1.3 work by Scott Reynen (scott@randomchaos.com) and Kai Blankenhorn
11 v1.5 OPML support by Dirk Clemens 11 v1.5 OPML support by Dirk Clemens
12   12  
13 This program is free software; you can redistribute it and/or 13 This program is free software; you can redistribute it and/or
14 modify it under the terms of the GNU General Public License 14 modify it under the terms of the GNU General Public License
15 as published by the Free Software Foundation; either version 2 15 as published by the Free Software Foundation; either version 2
16 of the License, or (at your option) any later version. 16 of the License, or (at your option) any later version.
17   17  
18 This program is distributed in the hope that it will be useful, 18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of 19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details: <http://www.gnu.org/licenses/gpl.txt> 21 GNU General Public License for more details: <http://www.gnu.org/licenses/gpl.txt>
22   22  
23 **************************************************************************** 23 ****************************************************************************
24   24  
25   25  
26 Changelog: 26 Changelog:
27   27  
28 Modifications for WebSVN: 28 Modifications for WebSVN:
29 The main description link wasn't put through htmlspecialcharacters 29 The main description link wasn't put through htmlspecialcharacters
30 Output encoding now defined by $config 30 Output encoding now defined by $config
31 Remove hardcoded time zone 31 Remove hardcoded time zone
32   32  
33 v1.6 05-10-04 33 v1.6 05-10-04
34 added stylesheet to RSS 1.0 feeds 34 added stylesheet to RSS 1.0 feeds
35 fixed generator comment (thanks Kevin L. Papendick and Tanguy Pruvot) 35 fixed generator comment (thanks Kevin L. Papendick and Tanguy Pruvot)
36 fixed RFC822 date bug (thanks Tanguy Pruvot) 36 fixed RFC822 date bug (thanks Tanguy Pruvot)
37 added TimeZone customization for RFC8601 (thanks Tanguy Pruvot) 37 added TimeZone customization for RFC8601 (thanks Tanguy Pruvot)
38 fixed Content-type could be empty (thanks Tanguy Pruvot) 38 fixed Content-type could be empty (thanks Tanguy Pruvot)
39 fixed author/creator in RSS1.0 (thanks Tanguy Pruvot) 39 fixed author/creator in RSS1.0 (thanks Tanguy Pruvot)
40   40  
41   41  
42 v1.6 beta 02-28-04 42 v1.6 beta 02-28-04
43 added Atom 0.3 support (not all features, though) 43 added Atom 0.3 support (not all features, though)
44 improved OPML 1.0 support (hopefully - added more elements) 44 improved OPML 1.0 support (hopefully - added more elements)
45 added support for arbitrary additional elements (use with caution) 45 added support for arbitrary additional elements (use with caution)
46 code beautification :-) 46 code beautification :-)
47 considered beta due to some internal changes 47 considered beta due to some internal changes
48   48  
49 v1.5.1 01-27-04 49 v1.5.1 01-27-04
50 fixed some RSS 1.0 glitches (thanks to Stéphane Vanpoperynghe) 50 fixed some RSS 1.0 glitches (thanks to Stéphane Vanpoperynghe)
51 fixed some inconsistencies between documentation and code (thanks to Timothy Martin) 51 fixed some inconsistencies between documentation and code (thanks to Timothy Martin)
52   52  
53 v1.5 01-06-04 53 v1.5 01-06-04
54 added support for OPML 1.0 54 added support for OPML 1.0
55 added more documentation 55 added more documentation
56   56  
57 v1.4 11-11-03 57 v1.4 11-11-03
58 optional feed saving and caching 58 optional feed saving and caching
59 improved documentation 59 improved documentation
60 minor improvements 60 minor improvements
61   61  
62 v1.3 10-02-03 62 v1.3 10-02-03
63 renamed to FeedCreator, as it not only creates RSS anymore 63 renamed to FeedCreator, as it not only creates RSS anymore
64 added support for mbox 64 added support for mbox
65 tentative support for echo/necho/atom/pie/??? 65 tentative support for echo/necho/atom/pie/???
66 66
67 v1.2 07-20-03 67 v1.2 07-20-03
68 intelligent auto-truncating of RSS 0.91 attributes 68 intelligent auto-truncating of RSS 0.91 attributes
69 don't create some attributes when they're not set 69 don't create some attributes when they're not set
70 documentation improved 70 documentation improved
71 fixed a real and a possible bug with date conversions 71 fixed a real and a possible bug with date conversions
72 code cleanup 72 code cleanup
73   73  
74 v1.1 06-29-03 74 v1.1 06-29-03
75 added images to feeds 75 added images to feeds
76 now includes most RSS 0.91 attributes 76 now includes most RSS 0.91 attributes
77 added RSS 2.0 feeds 77 added RSS 2.0 feeds
78   78  
79 v1.0 06-24-03 79 v1.0 06-24-03
80 initial release 80 initial release
81   81  
82   82  
83   83  
84 ***************************************************************************/ 84 ***************************************************************************/
85   85  
86 /*** GENERAL USAGE ********************************************************* 86 /*** GENERAL USAGE *********************************************************
87   87  
88 include("feedcreator.class.php"); 88 include("feedcreator.class.php");
89   89  
90 $rss = new UniversalFeedCreator(); 90 $rss = new UniversalFeedCreator();
91 $rss->useCached(); // use cached version if age<1 hour 91 $rss->useCached(); // use cached version if age<1 hour
92 $rss->title = "PHP news"; 92 $rss->title = "PHP news";
93 $rss->description = "daily news from the PHP scripting world"; 93 $rss->description = "daily news from the PHP scripting world";
94 $rss->link = "http://www.dailyphp.net/news"; 94 $rss->link = "http://www.dailyphp.net/news";
95 $rss->syndicationURL = "http://www.dailyphp.net/".$_SERVER["PHP_SELF"]; 95 $rss->syndicationURL = "http://www.dailyphp.net/".$_SERVER["PHP_SELF"];
96   96  
97 $image = new FeedImage(); 97 $image = new FeedImage();
98 $image->title = "dailyphp.net logo"; 98 $image->title = "dailyphp.net logo";
99 $image->url = "http://www.dailyphp.net/images/logo.gif"; 99 $image->url = "http://www.dailyphp.net/images/logo.gif";
100 $image->link = "http://www.dailyphp.net"; 100 $image->link = "http://www.dailyphp.net";
101 $image->description = "Feed provided by dailyphp.net. Click to visit."; 101 $image->description = "Feed provided by dailyphp.net. Click to visit.";
102 $rss->image = $image; 102 $rss->image = $image;
103   103  
104 // get your news items from somewhere, e.g. your database: 104 // get your news items from somewhere, e.g. your database:
105 mysql_select_db($dbHost, $dbUser, $dbPass); 105 mysql_select_db($dbHost, $dbUser, $dbPass);
106 $res = mysql_query("SELECT * FROM news ORDER BY newsdate DESC"); 106 $res = mysql_query("SELECT * FROM news ORDER BY newsdate DESC");
107 while ($data = mysql_fetch_object($res)) { 107 while ($data = mysql_fetch_object($res)) {
108 $item = new FeedItem(); 108 $item = new FeedItem();
109 $item->title = $data->title; 109 $item->title = $data->title;
110 $item->link = $data->url; 110 $item->link = $data->url;
111 $item->description = $data->short; 111 $item->description = $data->short;
112 $item->date = $data->newsdate; 112 $item->date = $data->newsdate;
113 $item->source = "http://www.dailyphp.net"; 113 $item->source = "http://www.dailyphp.net";
114 $item->author = "John Doe"; 114 $item->author = "John Doe";
115 115
116 $rss->addItem($item); 116 $rss->addItem($item);
117 } 117 }
118   118  
119 // valid format strings are: RSS0.91, RSS1.0, RSS2.0, PIE0.1 (deprecated), 119 // valid format strings are: RSS0.91, RSS1.0, RSS2.0, PIE0.1 (deprecated),
120 // MBOX, OPML, ATOM0.3 120 // MBOX, OPML, ATOM0.3
121 echo $rss->saveFeed("RSS1.0", "news/feed.xml"); 121 echo $rss->saveFeed("RSS1.0", "news/feed.xml");
122   122  
123 # }}} 123 # }}}
124   124  
125 *************************************************************************** 125 ***************************************************************************
126 * A little setup * 126 * A little setup *
127 **************************************************************************/ 127 **************************************************************************/
128   128  
129 /** 129 /**
130 * Version string. 130 * Version string.
131 **/ 131 **/
132 define("FEEDCREATOR_VERSION", "FeedCreator 1.6"); 132 define("FEEDCREATOR_VERSION", "FeedCreator 1.6");
133   133  
134   134  
135   135  
136 /** 136 /**
137 * A FeedItem is a part of a FeedCreator feed. 137 * A FeedItem is a part of a FeedCreator feed.
138 * 138 *
139 * @author Kai Blankenhorn <kaib@bitfolge.de> 139 * @author Kai Blankenhorn <kaib@bitfolge.de>
140 * @since 1.3 140 * @since 1.3
141 */ 141 */
142 class FeedItem { 142 class FeedItem {
143 # {{{ Properties 143 # {{{ Properties
144   144  
145 /** 145 /**
146 * Mandatory attributes of an item. 146 * Mandatory attributes of an item.
147 */ 147 */
148 var $title, $description, $link; 148 var $title, $description, $link;
149 149
150 /** 150 /**
151 * Optional attributes of an item. 151 * Optional attributes of an item.
152 */ 152 */
153 var $author, $authorEmail, $image, $category, $comments, $guid, $source, $creator; 153 var $author, $authorEmail, $image, $category, $comments, $guid, $source, $creator;
154 154
155 /** 155 /**
156 * Publishing date of an item. May be in one of the following formats: 156 * Publishing date of an item. May be in one of the following formats:
157 * 157 *
158 * RFC 822: 158 * RFC 822:
159 * "Mon, 20 Jan 03 18:05:41 +0400" 159 * "Mon, 20 Jan 03 18:05:41 +0400"
160 * "20 Jan 03 18:05:41 +0000" 160 * "20 Jan 03 18:05:41 +0000"
161 * 161 *
162 * ISO 8601: 162 * ISO 8601:
163 * "2003-01-20T18:05:41+04:00" 163 * "2003-01-20T18:05:41+04:00"
164 * 164 *
165 * Unix: 165 * Unix:
166 * 1043082341 166 * 1043082341
167 */ 167 */
168 var $date; 168 var $date;
169 169
170 /** 170 /**
171 * Any additional elements to include as an assiciated array. All $key => $value pairs 171 * Any additional elements to include as an assiciated array. All $key => $value pairs
172 * will be included unencoded in the feed item in the form 172 * will be included unencoded in the feed item in the form
173 * <$key>$value</$key> 173 * <$key>$value</$key>
174 * Again: No encoding will be used! This means you can invalidate or enhance the feed 174 * Again: No encoding will be used! This means you can invalidate or enhance the feed
175 * if $value contains markup. This may be abused to embed tags not implemented by 175 * if $value contains markup. This may be abused to embed tags not implemented by
176 * the FeedCreator class used. 176 * the FeedCreator class used.
177 */ 177 */
178 var $additionalElements = Array(); 178 var $additionalElements = Array();
179   179  
180 // on hold 180 // on hold
181 // var $source; 181 // var $source;
182   182  
183 # }}} 183 # }}}
184 } 184 }
185   185  
186   186  
187   187  
188 /** 188 /**
189 * An FeedImage may be added to a FeedCreator feed. 189 * An FeedImage may be added to a FeedCreator feed.
190 * @author Kai Blankenhorn <kaib@bitfolge.de> 190 * @author Kai Blankenhorn <kaib@bitfolge.de>
191 * @since 1.3 191 * @since 1.3
192 */ 192 */
193 class FeedImage { 193 class FeedImage {
194 # {{{ Properties 194 # {{{ Properties
195   195  
196 /** 196 /**
197 * Mandatory attributes of an image. 197 * Mandatory attributes of an image.
198 */ 198 */
199 var $title, $url, $link; 199 var $title, $url, $link;
200 200
201 /** 201 /**
202 * Optional attributes of an image. 202 * Optional attributes of an image.
203 */ 203 */
204 var $width, $height, $description; 204 var $width, $height, $description;
205   205  
206 # }}} 206 # }}}
207 } 207 }
208   208  
209   209  
210 /** 210 /**
211 * UniversalFeedCreator lets you choose during runtime which 211 * UniversalFeedCreator lets you choose during runtime which
212 * format to build. 212 * format to build.
213 * For general usage of a feed class, see the FeedCreator class 213 * For general usage of a feed class, see the FeedCreator class
214 * below or the example above. 214 * below or the example above.
215 * 215 *
216 * @since 1.3 216 * @since 1.3
217 * @author Kai Blankenhorn <kaib@bitfolge.de> 217 * @author Kai Blankenhorn <kaib@bitfolge.de>
218 */ 218 */
219 class UniversalFeedCreator extends FeedCreator { 219 class UniversalFeedCreator extends FeedCreator {
220 var $_feed; 220 var $_feed;
221 221
222 # {{{ _setFormat 222 # {{{ _setFormat
223 function _setFormat($format) { 223 function _setFormat($format) {
224 switch (strtoupper($format)) { 224 switch (strtoupper($format)) {
225 225
226 case "2.0": 226 case "2.0":
227 // fall through 227 // fall through
228 case "RSS2.0": 228 case "RSS2.0":
229 $this->_feed = new RSSCreator20(); 229 $this->_feed = new RSSCreator20();
230 break; 230 break;
231 231
232 case "1.0": 232 case "1.0":
233 // fall through 233 // fall through
234 case "RSS1.0": 234 case "RSS1.0":
235 $this->_feed = new RSSCreator10(); 235 $this->_feed = new RSSCreator10();
236 break; 236 break;
237 237
238 case "0.91": 238 case "0.91":
239 // fall through 239 // fall through
240 case "RSS0.91": 240 case "RSS0.91":
241 $this->_feed = new RSSCreator091(); 241 $this->_feed = new RSSCreator091();
242 break; 242 break;
243 243
244 case "PIE0.1": 244 case "PIE0.1":
245 $this->_feed = new PIECreator01(); 245 $this->_feed = new PIECreator01();
246 break; 246 break;
247 247
248 case "MBOX": 248 case "MBOX":
249 $this->_feed = new MBOXCreator(); 249 $this->_feed = new MBOXCreator();
250 break; 250 break;
251 251
252 case "OPML": 252 case "OPML":
253 $this->_feed = new OPMLCreator(); 253 $this->_feed = new OPMLCreator();
254 break; 254 break;
255 255
256 case "ATOM0.3": 256 case "ATOM0.3":
257 $this->_feed = new AtomCreator03(); 257 $this->_feed = new AtomCreator03();
258 break; 258 break;
259 259
260 default: 260 default:
261 $this->_feed = new RSSCreator091(); 261 $this->_feed = new RSSCreator091();
262 break; 262 break;
263 } 263 }
264 264
265 $vars = get_object_vars($this); 265 $vars = get_object_vars($this);
266 foreach ($vars as $key => $value) { 266 foreach ($vars as $key => $value) {
267 if ($key!="feed") { 267 if ($key!="feed") {
268 $this->_feed->{$key} = $this->{$key}; 268 $this->_feed->{$key} = $this->{$key};
269 } 269 }
270 } 270 }
271 } 271 }
272 # }}} 272 # }}}
273 273
274 # {{{ createFeed 274 # {{{ createFeed
275 /** 275 /**
276 * Creates a syndication feed based on the items previously added. 276 * Creates a syndication feed based on the items previously added.
277 * 277 *
278 * @see FeedCreator::addItem() 278 * @see FeedCreator::addItem()
279 * @param string format format the feed should comply to. Valid values are: 279 * @param string format format the feed should comply to. Valid values are:
280 * "PIE0.1", "mbox", "RSS0.91", "RSS1.0", "RSS2.0", "OPML". 280 * "PIE0.1", "mbox", "RSS0.91", "RSS1.0", "RSS2.0", "OPML".
281 * @return string the contents of the feed. 281 * @return string the contents of the feed.
282 */ 282 */
283 function createFeed($format = "RSS0.91") { 283 function createFeed($format = "RSS0.91") {
284 $this->_setFormat($format); 284 $this->_setFormat($format);
285 return $this->_feed->createFeed(); 285 return $this->_feed->createFeed();
286 } 286 }
287 # }}} 287 # }}}
288   288  
289 # {{{ saveFeed 289 # {{{ saveFeed
290 /** 290 /**
291 * Saves this feed as a file on the local disk. After the file is saved, an HTTP redirect 291 * Saves this feed as a file on the local disk. After the file is saved, an HTTP redirect
292 * header may be sent to redirect the use to the newly created file. 292 * header may be sent to redirect the use to the newly created file.
293 * @since 1.4 293 * @since 1.4
294 * 294 *
295 * @param string format format the feed should comply to. Valid values are: 295 * @param string format format the feed should comply to. Valid values are:
296 * "PIE0.1" (deprecated), "mbox", "RSS0.91", "RSS1.0", "RSS2.0", "OPML", "ATOM0.3". 296 * "PIE0.1" (deprecated), "mbox", "RSS0.91", "RSS1.0", "RSS2.0", "OPML", "ATOM0.3".
297 * @param string filename optional the filename where a recent version of the feed is saved. If not specified, the filename is $_SERVER["PHP_SELF"] with the extension changed to .xml (see _generateFilename()). 297 * @param string filename optional the filename where a recent version of the feed is saved. If not specified, the filename is $_SERVER["PHP_SELF"] with the extension changed to .xml (see _generateFilename()).
298 * @param boolean displayContents optional send the content of the file or not. If true, the file will be sent in the body of the response. 298 * @param boolean displayContents optional send the content of the file or not. If true, the file will be sent in the body of the response.
299 */ 299 */
300 function saveFeed($format="RSS0.91", $filename="", $displayContents=true) { 300 function saveFeed($format="RSS0.91", $filename="", $displayContents=true) {
301 $this->_setFormat($format); 301 $this->_setFormat($format);
302 $this->_feed->saveFeed($filename, $displayContents); 302 $this->_feed->saveFeed($filename, $displayContents);
303 } 303 }
304 # }}} 304 # }}}
305   305  
306 } 306 }
307   307  
308   308  
309 /** 309 /**
310 * FeedCreator is the abstract base implementation for concrete 310 * FeedCreator is the abstract base implementation for concrete
311 * implementations that implement a specific format of syndication. 311 * implementations that implement a specific format of syndication.
312 * 312 *
313 * @abstract 313 * @abstract
314 * @author Kai Blankenhorn <kaib@bitfolge.de> 314 * @author Kai Blankenhorn <kaib@bitfolge.de>
315 * @since 1.4 315 * @since 1.4
316 */ 316 */
317 class FeedCreator { 317 class FeedCreator {
318 # {{{ Properties 318 # {{{ Properties
319   319  
320 /** 320 /**
321 * Mandatory attributes of a feed. 321 * Mandatory attributes of a feed.
322 */ 322 */
323 var $title, $description, $link; 323 var $title, $description, $link;
324   324  
325   325  
326 /** 326 /**
327 * Optional attributes of a feed. 327 * Optional attributes of a feed.
328 */ 328 */
329 var $syndicationURL, $image, $language, $copyright, $pubDate, $lastBuildDate, $editor, $editorEmail, $webmaster, $category, $docs, $ttl, $rating, $skipHours, $skipDays; 329 var $syndicationURL, $image, $language, $copyright, $pubDate, $lastBuildDate, $editor, $editorEmail, $webmaster, $category, $docs, $ttl, $rating, $skipHours, $skipDays;
330   330  
331   331  
332 /** 332 /**
333 * @access private 333 * @access private
334 */ 334 */
335 var $items = Array(); 335 var $items = Array();
336   336  
337   337  
338 /** 338 /**
339 * This feed's MIME content type. 339 * This feed's MIME content type.
340 * @since 1.4 340 * @since 1.4
341 * @access private 341 * @access private
342 */ 342 */
343 var $contentType = "text/xml"; 343 var $contentType = "text/xml";
344   344  
345   345  
346 /** 346 /**
347 * Any additional elements to include as an assiciated array. All $key => $value pairs 347 * Any additional elements to include as an assiciated array. All $key => $value pairs
348 * will be included unencoded in the feed in the form 348 * will be included unencoded in the feed in the form
349 * <$key>$value</$key> 349 * <$key>$value</$key>
350 * Again: No encoding will be used! This means you can invalidate or enhance the feed 350 * Again: No encoding will be used! This means you can invalidate or enhance the feed
351 * if $value contains markup. This may be abused to embed tags not implemented by 351 * if $value contains markup. This may be abused to embed tags not implemented by
352 * the FeedCreator class used. 352 * the FeedCreator class used.
353 */ 353 */
354 var $additionalElements = Array(); 354 var $additionalElements = Array();
355   355  
356 # }}} 356 # }}}
357 357
358 # {{{ addItem 358 # {{{ addItem
359 /** 359 /**
360 * Adds an FeedItem to the feed. 360 * Adds an FeedItem to the feed.
361 * 361 *
362 * @param object FeedItem $item The FeedItem to add to the feed. 362 * @param object FeedItem $item The FeedItem to add to the feed.
363 * @access public 363 * @access public
364 */ 364 */
365 function addItem($item) { 365 function addItem($item) {
366 $this->items[] = $item; 366 $this->items[] = $item;
367 } 367 }
368 # }}} 368 # }}}
369   369  
370 # {{{ iTrunc 370 # {{{ iTrunc
371 /** 371 /**
372 * Truncates a string to a certain length at the most sensible point. 372 * Truncates a string to a certain length at the most sensible point.
373 * First, if there's a '.' character near the end of the string, the string is truncated after this character. 373 * First, if there's a '.' character near the end of the string, the string is truncated after this character.
374 * If there is no '.', the string is truncated after the last ' ' character. 374 * If there is no '.', the string is truncated after the last ' ' character.
375 * If the string is truncated, " ..." is appended. 375 * If the string is truncated, " ..." is appended.
376 * If the string is already shorter than $length, it is returned unchanged. 376 * If the string is already shorter than $length, it is returned unchanged.
377 * 377 *
378 * @static 378 * @static
379 * @param string string A string to be truncated. 379 * @param string string A string to be truncated.
380 * @param int length the maximum length the string should be truncated to 380 * @param int length the maximum length the string should be truncated to
381 * @return string the truncated string 381 * @return string the truncated string
382 */ 382 */
383 function iTrunc($string, $length) { 383 function iTrunc($string, $length) {
384 if (strlen($string)<=$length) { 384 if (strlen($string)<=$length) {
385 return $string; 385 return $string;
386 } 386 }
387 387
388 $pos = strrpos($string,"."); 388 $pos = strrpos($string,".");
389 if ($pos>=$length-4) { 389 if ($pos>=$length-4) {
390 $string = substr($string,0,$length-4); 390 $string = substr($string,0,$length-4);
391 $pos = strrpos($string,"."); 391 $pos = strrpos($string,".");
392 } 392 }
393 if ($pos>=$length*0.4) { 393 if ($pos>=$length*0.4) {
394 return substr($string,0,$pos+1)." ..."; 394 return substr($string,0,$pos+1)." ...";
395 } 395 }
396 396
397 $pos = strrpos($string," "); 397 $pos = strrpos($string," ");
398 if ($pos>=$length-4) { 398 if ($pos>=$length-4) {
399 $string = substr($string,0,$length-4); 399 $string = substr($string,0,$length-4);
400 $pos = strrpos($string," "); 400 $pos = strrpos($string," ");
401 } 401 }
402 if ($pos>=$length*0.4) { 402 if ($pos>=$length*0.4) {
403 return substr($string,0,$pos)." ..."; 403 return substr($string,0,$pos)." ...";
404 } 404 }
405 405
406 return substr($string,0,$length-4)." ..."; 406 return substr($string,0,$length-4)." ...";
407 407
408 } 408 }
409 # }}} 409 # }}}
410   410  
411 # {{{ _createGeneratorComment 411 # {{{ _createGeneratorComment
412 /** 412 /**
413 * Creates a comment indicating the generator of this feed. 413 * Creates a comment indicating the generator of this feed.
414 * The format of this comment seems to be recognized by 414 * The format of this comment seems to be recognized by
415 * Syndic8.com. 415 * Syndic8.com.
416 */ 416 */
417 function _createGeneratorComment() { 417 function _createGeneratorComment() {
418 return "<!-- generator=\"".FEEDCREATOR_VERSION."\" -->\n"; 418 return "<!-- generator=\"".FEEDCREATOR_VERSION."\" -->\n";
419 } 419 }
420 # }}} 420 # }}}
421   421  
422 # {{{ _createAdditionalElements 422 # {{{ _createAdditionalElements
423 /** 423 /**
424 * Creates a string containing all additional elements specified in 424 * Creates a string containing all additional elements specified in
425 * $additionalElements. 425 * $additionalElements.
426 * @param elements array an associative array containing key => value pairs 426 * @param elements array an associative array containing key => value pairs
427 * @param indentString string a string that will be inserted before every generated line 427 * @param indentString string a string that will be inserted before every generated line
428 * @return string the XML tags corresponding to $additionalElements 428 * @return string the XML tags corresponding to $additionalElements
429 */ 429 */
430 function _createAdditionalElements($elements, $indentString="") { 430 function _createAdditionalElements($elements, $indentString="") {
431 $ae = ""; 431 $ae = "";
432 if (is_array($elements)) { 432 if (is_array($elements)) {
433 foreach($elements AS $key => $value) { 433 foreach($elements AS $key => $value) {
434 $ae.= $indentString."<$key>$value</$key>\n"; 434 $ae.= $indentString."<$key>$value</$key>\n";
435 } 435 }
436 } 436 }
437 return $ae; 437 return $ae;
438 } 438 }
439 # }}} 439 # }}}
440   440  
441 # {{{ createFeed 441 # {{{ createFeed
442 /** 442 /**
443 * Builds the feed's text. 443 * Builds the feed's text.
444 * @abstract 444 * @abstract
445 * @return string the feed's complete text 445 * @return string the feed's complete text
446 */ 446 */
447 function createFeed() { 447 function createFeed() {
448 } 448 }
449 # }}} 449 # }}}
450   450  
451 # {{{ _generateFilename 451 # {{{ _generateFilename
452 /** 452 /**
453 * Generate a filename for the feed cache file. The result will be $_SERVER["PHP_SELF"] with the extension changed to .xml. 453 * Generate a filename for the feed cache file. The result will be $_SERVER["PHP_SELF"] with the extension changed to .xml.
454 * For example: 454 * For example:
455 * 455 *
456 * echo $_SERVER["PHP_SELF"]."\n"; 456 * echo $_SERVER["PHP_SELF"]."\n";
457 * echo FeedCreator::_generateFilename(); 457 * echo FeedCreator::_generateFilename();
458 * 458 *
459 * would produce: 459 * would produce:
460 * 460 *
461 * /rss/latestnews.php 461 * /rss/latestnews.php
462 * latestnews.xml 462 * latestnews.xml
463 * 463 *
464 * @return string the feed cache filename 464 * @return string the feed cache filename
465 * @since 1.4 465 * @since 1.4
466 * @access private 466 * @access private
467 */ 467 */
468 function _generateFilename() { 468 function _generateFilename() {
469 $fileInfo = pathinfo($_SERVER["PHP_SELF"]); 469 $fileInfo = pathinfo($_SERVER["PHP_SELF"]);
470 return substr($fileInfo["basename"],0,-(strlen($fileInfo["extension"])+1)).".xml"; 470 return substr($fileInfo["basename"],0,-(strlen($fileInfo["extension"])+1)).".xml";
471 } 471 }
472 # }}} 472 # }}}
473   473  
474 # {{{ _redirect 474 # {{{ _redirect
475 /** 475 /**
476 * @since 1.4 476 * @since 1.4
477 * @access private 477 * @access private
478 */ 478 */
479 function _redirect($filename) { 479 function _redirect($filename) {
480 // attention, heavily-commented-out-area 480 // attention, heavily-commented-out-area
481 481
482 // maybe use this in addition to file time checking 482 // maybe use this in addition to file time checking
483 //Header("Expires: ".date("r",time()+$this->_timeout)); 483 //Header("Expires: ".date("r",time()+$this->_timeout));
484 484
485 /* no caching at all, doesn't seem to work as good: 485 /* no caching at all, doesn't seem to work as good:
486 Header("Cache-Control: no-cache"); 486 Header("Cache-Control: no-cache");
487 Header("Pragma: no-cache"); 487 Header("Pragma: no-cache");
488 */ 488 */
489 489
490 // HTTP redirect, some feed readers' simple HTTP implementations don't follow it 490 // HTTP redirect, some feed readers' simple HTTP implementations don't follow it
491 //Header("Location: ".$filename); 491 //Header("Location: ".$filename);
492   492  
493 Header("Content-Type: ".$this->contentType."; filename=".basename($filename)); 493 Header("Content-Type: ".$this->contentType."; filename=".basename($filename));
494 Header("Content-Disposition: inline; filename=".basename($filename)); 494 Header("Content-Disposition: inline; filename=".basename($filename));
495 readfile($filename, "r"); 495 readfile($filename, "r");
496 die(); 496 die();
497 } 497 }
498 # }}} 498 # }}}
499   499  
500 # {{{ useCached 500 # {{{ useCached
501 /** 501 /**
502 * Turns on caching and checks if there is a recent version of this feed in the cache. 502 * Turns on caching and checks if there is a recent version of this feed in the cache.
503 * If there is, an HTTP redirect header is sent. 503 * If there is, an HTTP redirect header is sent.
504 * To effectively use caching, you should create the FeedCreator object and call this method 504 * To effectively use caching, you should create the FeedCreator object and call this method
505 * before anything else, especially before you do the time consuming task to build the feed 505 * before anything else, especially before you do the time consuming task to build the feed
506 * (web fetching, for example). 506 * (web fetching, for example).
507 * @since 1.4 507 * @since 1.4
508 * @param filename string optional the filename where a recent version of the feed is saved. If not specified, the filename is $_SERVER["PHP_SELF"] with the extension changed to .xml (see _generateFilename()). 508 * @param filename string optional the filename where a recent version of the feed is saved. If not specified, the filename is $_SERVER["PHP_SELF"] with the extension changed to .xml (see _generateFilename()).
509 * @param timeout int optional the timeout in seconds before a cached version is refreshed (defaults to 3600 = 1 hour) 509 * @param timeout int optional the timeout in seconds before a cached version is refreshed (defaults to 3600 = 1 hour)
510 */ 510 */
511 function useCached($filename="", $timeout=3600) { 511 function useCached($filename="", $timeout=3600) {
512 $this->_timeout = $timeout; 512 $this->_timeout = $timeout;
513 if ($filename=="") { 513 if ($filename=="") {
514 $filename = $this->_generateFilename(); 514 $filename = $this->_generateFilename();
515 } 515 }
516 if (file_exists($filename) AND (time()-filemtime($filename) < $timeout)) { 516 if (file_exists($filename) AND (time()-filemtime($filename) < $timeout)) {
517 $this->_redirect($filename); 517 $this->_redirect($filename);
518 } 518 }
519 } 519 }
520 # }}} 520 # }}}
521   521  
522 # {{{ saveFeed 522 # {{{ saveFeed
523 /** 523 /**
524 * Saves this feed as a file on the local disk. After the file is saved, a redirect 524 * Saves this feed as a file on the local disk. After the file is saved, a redirect
525 * header may be sent to redirect the user to the newly created file. 525 * header may be sent to redirect the user to the newly created file.
526 * @since 1.4 526 * @since 1.4
527 * 527 *
528 * @param filename string optional the filename where a recent version of the feed is saved. If not specified, the filename is $_SERVER["PHP_SELF"] with the extension changed to .xml (see _generateFilename()). 528 * @param filename string optional the filename where a recent version of the feed is saved. If not specified, the filename is $_SERVER["PHP_SELF"] with the extension changed to .xml (see _generateFilename()).
529 * @param redirect boolean optional send an HTTP redirect header or not. If true, the user will be automatically redirected to the created file. 529 * @param redirect boolean optional send an HTTP redirect header or not. If true, the user will be automatically redirected to the created file.
530 */ 530 */
531 function saveFeed($filename="", $displayContents=true) { 531 function saveFeed($filename="", $displayContents=true) {
532 if ($filename=="") { 532 if ($filename=="") {
533 $filename = $this->_generateFilename(); 533 $filename = $this->_generateFilename();
534 } 534 }
535 $feedFile = fopen($filename, "w+"); 535 $feedFile = fopen($filename, "w+");
536 if ($feedFile) { 536 if ($feedFile) {
537 fputs($feedFile,$this->createFeed()); 537 fputs($feedFile,$this->createFeed());
538 fclose($feedFile); 538 fclose($feedFile);
539 if ($displayContents) { 539 if ($displayContents) {
540 $this->_redirect($filename); 540 $this->_redirect($filename);
541 } 541 }
542 } else { 542 } else {
543 echo "<br /><b>Error creating feed file, please check write permissions.</b><br />"; 543 echo "<br /><b>Error creating feed file, please check write permissions.</b><br />";
544 } 544 }
545 } 545 }
546 # }}} 546 # }}}
547 } 547 }
548   548  
549   549  
550 /** 550 /**
551 * FeedDate is an internal class that stores a date for a feed or feed item. 551 * FeedDate is an internal class that stores a date for a feed or feed item.
552 * Usually, you won't need to use this. 552 * Usually, you won't need to use this.
553 */ 553 */
554 class FeedDate { 554 class FeedDate {
555 var $unix; 555 var $unix;
556   556  
557 # {{{ __construct 557 # {{{ __construct
558 /** 558 /**
559 * Creates a new instance of FeedDate representing a given date. 559 * Creates a new instance of FeedDate representing a given date.
560 * Accepts RFC 822, ISO 8601 date formats as well as unix time stamps. 560 * Accepts RFC 822, ISO 8601 date formats as well as unix time stamps.
561 * @param mixed $dateString optional the date this FeedDate will represent. If not specified, the current date and time is used. 561 * @param mixed $dateString optional the date this FeedDate will represent. If not specified, the current date and time is used.
562 */ 562 */
563 function FeedDate($dateString="") { 563 function FeedDate($dateString="") {
564 if ($dateString=="") $dateString = date("r"); 564 if ($dateString=="") $dateString = date("r");
565 565
566 if (is_integer($dateString)) { 566 if (is_integer($dateString)) {
567 $this->unix = $dateString; 567 $this->unix = $dateString;
568 return; 568 return;
569 } 569 }
570 if (preg_match("~(?:(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun),\\s+)?(\\d{1,2})\\s+([a-zA-Z]{3})\\s+(\\d{4})\\s+(\\d{2}):(\\d{2}):(\\d{2})\\s+(.*)~",$dateString,$matches)) { 570 if (preg_match("~(?:(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun),\\s+)?(\\d{1,2})\\s+([a-zA-Z]{3})\\s+(\\d{4})\\s+(\\d{2}):(\\d{2}):(\\d{2})\\s+(.*)~",$dateString,$matches)) {
571 $months = Array("Jan"=>1,"Feb"=>2,"Mar"=>3,"Apr"=>4,"May"=>5,"Jun"=>6,"Jul"=>7,"Aug"=>8,"Sep"=>9,"Oct"=>10,"Nov"=>11,"Dec"=>12); 571 $months = Array("Jan"=>1,"Feb"=>2,"Mar"=>3,"Apr"=>4,"May"=>5,"Jun"=>6,"Jul"=>7,"Aug"=>8,"Sep"=>9,"Oct"=>10,"Nov"=>11,"Dec"=>12);
572 $this->unix = mktime($matches[4],$matches[5],$matches[6],$months[$matches[2]],$matches[1],$matches[3]); 572 $this->unix = mktime($matches[4],$matches[5],$matches[6],$months[$matches[2]],$matches[1],$matches[3]);
573 if (substr($matches[7],0,1)=='+' OR substr($matches[7],0,1)=='-') { 573 if (substr($matches[7],0,1)=='+' OR substr($matches[7],0,1)=='-') {
574 $tzOffset = (substr($matches[7],0,3) * 60 + substr($matches[7],-2)) * 60; 574 $tzOffset = (substr($matches[7],0,3) * 60 + substr($matches[7],-2)) * 60;
575 } else { 575 } else {
576 if (strlen($matches[7])==1) { 576 if (strlen($matches[7])==1) {
577 $oneHour = 3600; 577 $oneHour = 3600;
578 $ord = ord($matches[7]); 578 $ord = ord($matches[7]);
579 if ($ord < ord("M")) { 579 if ($ord < ord("M")) {
580 $tzOffset = (ord("A") - $ord - 1) * $oneHour; 580 $tzOffset = (ord("A") - $ord - 1) * $oneHour;
581 } elseif ($ord >= ord("M") AND $matches[7]!="Z") { 581 } elseif ($ord >= ord("M") AND $matches[7]!="Z") {
582 $tzOffset = ($ord - ord("M")) * $oneHour; 582 $tzOffset = ($ord - ord("M")) * $oneHour;
583 } elseif ($matches[7]=="Z") { 583 } elseif ($matches[7]=="Z") {
584 $tzOffset = 0; 584 $tzOffset = 0;
585 } 585 }
586 } 586 }
587 switch ($matches[7]) { 587 switch ($matches[7]) {
588 case "UT": 588 case "UT":
589 case "GMT": $tzOffset = 0; 589 case "GMT": $tzOffset = 0;
590 } 590 }
591 } 591 }
592 $this->unix += $tzOffset; 592 $this->unix += $tzOffset;
593 return; 593 return;
594 } 594 }
595 if (preg_match("~(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(.*)~",$dateString,$matches)) { 595 if (preg_match("~(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(.*)~",$dateString,$matches)) {
596 $this->unix = mktime($matches[4],$matches[5],$matches[6],$matches[2],$matches[3],$matches[1]); 596 $this->unix = mktime($matches[4],$matches[5],$matches[6],$matches[2],$matches[3],$matches[1]);
597 if (substr($matches[7],0,1)=='+' OR substr($matches[7],0,1)=='-') { 597 if (substr($matches[7],0,1)=='+' OR substr($matches[7],0,1)=='-') {
598 $tzOffset = (substr($matches[7],0,3) * 60 + substr($matches[7],-2)) * 60; 598 $tzOffset = (substr($matches[7],0,3) * 60 + substr($matches[7],-2)) * 60;
599 } else { 599 } else {
600 if ($matches[7]=="Z") { 600 if ($matches[7]=="Z") {
601 $tzOffset = 0; 601 $tzOffset = 0;
602 } 602 }
603 } 603 }
604 $this->unix += $tzOffset; 604 $this->unix += $tzOffset;
605 return; 605 return;
606 } 606 }
607 $this->unix = 0; 607 $this->unix = 0;
608 } 608 }
609 # }}} 609 # }}}
610   610  
611 # {{{ rfc822 611 # {{{ rfc822
612 /** 612 /**
613 * Gets the date stored in this FeedDate as an RFC 822 date. 613 * Gets the date stored in this FeedDate as an RFC 822 date.
614 * 614 *
615 * @return a date in RFC 822 format 615 * @return a date in RFC 822 format
616 */ 616 */
617 function rfc822() { 617 function rfc822() {
618 return gmdate("r",$this->unix); 618 return gmdate("r",$this->unix);
619 } 619 }
620 # }}} 620 # }}}
621   621  
622 # {{{ iso8601 622 # {{{ iso8601
623 /** 623 /**
624 * Gets the date stored in this FeedDate as an ISO 8601 date. 624 * Gets the date stored in this FeedDate as an ISO 8601 date.
625 * 625 *
626 * @return a date in ISO 8601 format 626 * @return a date in ISO 8601 format
627 */ 627 */
628 function iso8601() { 628 function iso8601() {
629 $date = gmdate("Y-m-d\TH:i:sO",$this->unix); 629 $date = gmdate("Y-m-d\TH:i:sO",$this->unix);
630 $date = substr($date,0,22) . ':' . substr($date,-2); 630 $date = substr($date,0,22) . ':' . substr($date,-2);
631 return $date; 631 return $date;
632 } 632 }
633 # }}} 633 # }}}
634   634  
635 # {{{ unix 635 # {{{ unix
636 /** 636 /**
637 * Gets the date stored in this FeedDate as unix time stamp. 637 * Gets the date stored in this FeedDate as unix time stamp.
638 * 638 *
639 * @return a date as a unix time stamp 639 * @return a date as a unix time stamp
640 */ 640 */
641 function unix() { 641 function unix() {
642 return $this->unix; 642 return $this->unix;
643 } 643 }
644 # }}} 644 # }}}
645 } 645 }
646   646  
647   647  
648 /** 648 /**
649 * RSSCreator10 is a FeedCreator that implements RDF Site Summary (RSS) 1.0. 649 * RSSCreator10 is a FeedCreator that implements RDF Site Summary (RSS) 1.0.
650 * 650 *
651 * @see http://www.purl.org/rss/1.0/ 651 * @see http://www.purl.org/rss/1.0/
652 * @since 1.3 652 * @since 1.3
653 * @author Kai Blankenhorn <kaib@bitfolge.de> 653 * @author Kai Blankenhorn <kaib@bitfolge.de>
654 */ 654 */
655 class RSSCreator10 extends FeedCreator { 655 class RSSCreator10 extends FeedCreator {
656   656  
657 # {{{ createFeed 657 # {{{ createFeed
658 /** 658 /**
659 * Builds the RSS feed's text. The feed will be compliant to RDF Site Summary (RSS) 1.0. 659 * Builds the RSS feed's text. The feed will be compliant to RDF Site Summary (RSS) 1.0.
660 * The feed will contain all items previously added in the same order. 660 * The feed will contain all items previously added in the same order.
661 * @return string the feed's complete text 661 * @return string the feed's complete text
662 */ 662 */
663 function createFeed() { 663 function createFeed() {
664 global $config; 664 global $config;
665 $feed = "<?xml version=\"1.0\" encoding=\"".$config->outputEnc."\"?>\n"; 665 $feed = "<?xml version=\"1.0\" encoding=\"".$config->outputEnc."\"?>\n";
666 $feed.= "<?xml-stylesheet href=\"http://www.w3.org/2000/08/w3c-synd/style.css\" type=\"text/css\"?>\n"; 666 $feed.= "<?xml-stylesheet href=\"http://www.w3.org/2000/08/w3c-synd/style.css\" type=\"text/css\"?>\n";
667 $feed.= $this->_createGeneratorComment(); 667 $feed.= $this->_createGeneratorComment();
668 $feed.= "<rdf:RDF\n"; 668 $feed.= "<rdf:RDF\n";
669 $feed.= " xmlns=\"http://purl.org/rss/1.0/\"\n"; 669 $feed.= " xmlns=\"http://purl.org/rss/1.0/\"\n";
670 $feed.= " xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n"; 670 $feed.= " xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n";
671 $feed.= " xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n"; 671 $feed.= " xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n";
672 $feed.= " <channel rdf:about=\"".htmlspecialchars($this->syndicationURL)."\">\n"; 672 $feed.= " <channel rdf:about=\"".htmlspecialchars($this->syndicationURL)."\">\n";
673 $feed.= " <title>".htmlspecialchars($this->title)."</title>\n"; 673 $feed.= " <title>".htmlspecialchars($this->title)."</title>\n";
674 $feed.= " <description>".htmlspecialchars($this->description)."</description>\n"; 674 $feed.= " <description>".htmlspecialchars($this->description)."</description>\n";
675 $feed.= " <link>".htmlspecialchars($this->link)."</link>\n"; 675 $feed.= " <link>".htmlspecialchars($this->link)."</link>\n";
676 if ($this->image!=null) { 676 if ($this->image!=null) {
677 $feed.= " <image rdf:resource=\"".$this->image->url."\" />\n"; 677 $feed.= " <image rdf:resource=\"".$this->image->url."\" />\n";
678 } 678 }
679 $now = new FeedDate(); 679 $now = new FeedDate();
680 $feed.= " <dc:date>".htmlspecialchars($now->iso8601())."</dc:date>\n"; 680 $feed.= " <dc:date>".htmlspecialchars($now->iso8601())."</dc:date>\n";
681 $feed.= " <items>\n"; 681 $feed.= " <items>\n";
682 $feed.= " <rdf:Seq>\n"; 682 $feed.= " <rdf:Seq>\n";
683 for ($i=0;$i<count($this->items);$i++) { 683 for ($i=0;$i<count($this->items);$i++) {
684 $feed.= " <rdf:li rdf:resource=\"".htmlspecialchars($this->items[$i]->link)."\"/>\n"; 684 $feed.= " <rdf:li rdf:resource=\"".htmlspecialchars($this->items[$i]->link)."\"/>\n";
685 } 685 }
686 $feed.= " </rdf:Seq>\n"; 686 $feed.= " </rdf:Seq>\n";
687 $feed.= " </items>\n"; 687 $feed.= " </items>\n";
688 $feed.= " </channel>\n"; 688 $feed.= " </channel>\n";
689 if ($this->image!=null) { 689 if ($this->image!=null) {
690 $feed.= " <image rdf:about=\"".$this->image->url."\">\n"; 690 $feed.= " <image rdf:about=\"".$this->image->url."\">\n";
691 $feed.= " <title>".$this->image->title."</title>\n"; 691 $feed.= " <title>".$this->image->title."</title>\n";
692 $feed.= " <link>".$this->image->link."</link>\n"; 692 $feed.= " <link>".$this->image->link."</link>\n";
693 $feed.= " <url>".$this->image->url."</url>\n"; 693 $feed.= " <url>".$this->image->url."</url>\n";
694 $feed.= " </image>\n"; 694 $feed.= " </image>\n";
695 } 695 }
696 //$feed.= $this->_createAdditionalElements($this->items[$i]->additionalElements, " "); 696 //$feed.= $this->_createAdditionalElements($this->items[$i]->additionalElements, " ");
697 697
698 for ($i=0;$i<count($this->items);$i++) { 698 for ($i=0;$i<count($this->items);$i++) {
699 $feed.= " <item rdf:about=\"".htmlspecialchars($this->items[$i]->link)."\">\n"; 699 $feed.= " <item rdf:about=\"".htmlspecialchars($this->items[$i]->link)."\">\n";
700 //$feed.= " <dc:type>Posting</dc:type>\n"; 700 //$feed.= " <dc:type>Posting</dc:type>\n";
701 $feed.= " <dc:format>text/html</dc:format>\n"; 701 $feed.= " <dc:format>text/html</dc:format>\n";
702 if ($this->items[$i]->date!=null) { 702 if ($this->items[$i]->date!=null) {
703 $itemDate = new FeedDate($this->items[$i]->date); 703 $itemDate = new FeedDate($this->items[$i]->date);
704 $feed.= " <dc:date>".htmlspecialchars($itemDate->iso8601())."</dc:date>\n"; 704 $feed.= " <dc:date>".htmlspecialchars($itemDate->iso8601())."</dc:date>\n";
705 } 705 }
706 if ($this->items[$i]->source!="") { 706 if ($this->items[$i]->source!="") {
707 $feed.= " <dc:source>".htmlspecialchars($this->items[$i]->source)."</dc:source>\n"; 707 $feed.= " <dc:source>".htmlspecialchars($this->items[$i]->source)."</dc:source>\n";
708 } 708 }
709 if ($this->items[$i]->author!="") { 709 if ($this->items[$i]->author!="") {
710 $feed.= " <dc:creator>".htmlspecialchars($this->items[$i]->author)."</dc:creator>\n"; 710 $feed.= " <dc:creator>".htmlspecialchars($this->items[$i]->author)."</dc:creator>\n";
711 } 711 }
712 $feed.= " <title>".htmlspecialchars(strip_tags(strtr($this->items[$i]->title,"\n\r"," ")))."</title>\n"; 712 $feed.= " <title>".htmlspecialchars(strip_tags(strtr($this->items[$i]->title,"\n\r"," ")))."</title>\n";
713 $feed.= " <link>".htmlspecialchars($this->items[$i]->link)."</link>\n"; 713 $feed.= " <link>".htmlspecialchars($this->items[$i]->link)."</link>\n";
714 $feed.= " <description>".htmlspecialchars($this->items[$i]->description)."</description>\n"; 714 $feed.= " <description>".htmlspecialchars($this->items[$i]->description)."</description>\n";
715 $feed.= $this->_createAdditionalElements($this->items[$i]->additionalElements, " "); 715 $feed.= $this->_createAdditionalElements($this->items[$i]->additionalElements, " ");
716 $feed.= " </item>\n"; 716 $feed.= " </item>\n";
717 } 717 }
718 $feed.= "</rdf:RDF>\n"; 718 $feed.= "</rdf:RDF>\n";
719 return $feed; 719 return $feed;
720 } 720 }
721 # }}} 721 # }}}
722 } 722 }
723   723  
724   724  
725   725  
726 /** 726 /**
727 * RSSCreator091 is a FeedCreator that implements RSS 0.91 Spec, revision 3. 727 * RSSCreator091 is a FeedCreator that implements RSS 0.91 Spec, revision 3.
728 * 728 *
729 * @see http://my.netscape.com/publish/formats/rss-spec-0.91.html 729 * @see http://my.netscape.com/publish/formats/rss-spec-0.91.html
730 * @since 1.3 730 * @since 1.3
731 * @author Kai Blankenhorn <kaib@bitfolge.de> 731 * @author Kai Blankenhorn <kaib@bitfolge.de>
732 */ 732 */
733 class RSSCreator091 extends FeedCreator { 733 class RSSCreator091 extends FeedCreator {
734   734  
735 /** 735 /**
736 * Stores this RSS feed's version number. 736 * Stores this RSS feed's version number.
737 * @access private 737 * @access private
738 */ 738 */
739 var $RSSVersion; 739 var $RSSVersion;
740   740  
741 # {{{ __construct 741 # {{{ __construct
742 function RSSCreator091() { 742 function RSSCreator091() {
743 $this->_setRSSVersion("0.91"); 743 $this->_setRSSVersion("0.91");
744 $this->contentType = "application/rss+xml"; 744 $this->contentType = "application/rss+xml";
745 } 745 }
746 # }}} 746 # }}}
747   747  
748 # {{{ _setRSSVersion 748 # {{{ _setRSSVersion
749 /** 749 /**
750 * Sets this RSS feed's version number. 750 * Sets this RSS feed's version number.
751 * @access private 751 * @access private
752 */ 752 */
753 function _setRSSVersion($version) { 753 function _setRSSVersion($version) {
754 $this->RSSVersion = $version; 754 $this->RSSVersion = $version;
755 } 755 }
756 # }}} 756 # }}}
757   757  
758 # {{{ createFeed 758 # {{{ createFeed
759 /** 759 /**
760 * Builds the RSS feed's text. The feed will be compliant to RDF Site Summary (RSS) 1.0. 760 * Builds the RSS feed's text. The feed will be compliant to RDF Site Summary (RSS) 1.0.
761 * The feed will contain all items previously added in the same order. 761 * The feed will contain all items previously added in the same order.
762 * @return string the feed's complete text 762 * @return string the feed's complete text
763 */ 763 */
764 function createFeed() { 764 function createFeed() {
765 global $config; 765 global $config;
766 $feed = "<?xml version=\"1.0\" encoding=\"".$config->outputEnc."\"?>\n"; 766 $feed = "<?xml version=\"1.0\" encoding=\"".$config->outputEnc."\"?>\n";
767 $feed.= $this->_createGeneratorComment(); 767 $feed.= $this->_createGeneratorComment();
768 $feed.= "<rss version=\"".$this->RSSVersion."\">\n"; 768 $feed.= "<rss version=\"".$this->RSSVersion."\">\n";
769 $feed.= " <channel>\n"; 769 $feed.= " <channel>\n";
770 $feed.= " <title>".FeedCreator::iTrunc(htmlspecialchars($this->title),100)."</title>\n"; 770 $feed.= " <title>".FeedCreator::iTrunc(htmlspecialchars($this->title),100)."</title>\n";
771 $feed.= " <description>".FeedCreator::iTrunc(htmlspecialchars($this->description),500)."</description>\n"; 771 $feed.= " <description>".FeedCreator::iTrunc(htmlspecialchars($this->description),500)."</description>\n";
772 $feed.= " <link>".htmlspecialchars($this->link)."</link>\n"; 772 $feed.= " <link>".htmlspecialchars($this->link)."</link>\n";
773 $now = new FeedDate(); 773 $now = new FeedDate();
774 $feed.= " <lastBuildDate>".htmlspecialchars($now->rfc822())."</lastBuildDate>\n"; 774 $feed.= " <lastBuildDate>".htmlspecialchars($now->rfc822())."</lastBuildDate>\n";
775 $feed.= " <generator>".FEEDCREATOR_VERSION."</generator>\n"; 775 $feed.= " <generator>".FEEDCREATOR_VERSION."</generator>\n";
776   776  
777 if ($this->image!=null) { 777 if ($this->image!=null) {
778 $feed.= " <image>\n"; 778 $feed.= " <image>\n";
779 $feed.= " <url>".$this->image->url."</url>\n"; 779 $feed.= " <url>".$this->image->url."</url>\n";
780 $feed.= " <title>".FeedCreator::iTrunc(htmlspecialchars($this->image->title),100)."</title>\n"; 780 $feed.= " <title>".FeedCreator::iTrunc(htmlspecialchars($this->image->title),100)."</title>\n";
781 $feed.= " <link>".$this->image->link."</link>\n"; 781 $feed.= " <link>".$this->image->link."</link>\n";
782 if ($this->image->width!="") { 782 if ($this->image->width!="") {
783 $feed.= " <width>".$this->image->width."</width>\n"; 783 $feed.= " <width>".$this->image->width."</width>\n";
784 } 784 }
785 if ($this->image->height!="") { 785 if ($this->image->height!="") {
786 $feed.= " <height>".$this->image->height."</height>\n"; 786 $feed.= " <height>".$this->image->height."</height>\n";
787 } 787 }
788 if ($this->image->description!="") { 788 if ($this->image->description!="") {
789 $feed.= " <description>".htmlspecialchars($this->image->description)."</description>\n"; 789 $feed.= " <description>".htmlspecialchars($this->image->description)."</description>\n";
790 } 790 }
791 $feed.= " </image>\n"; 791 $feed.= " </image>\n";
792 } 792 }
793 if ($this->language!="") { 793 if ($this->language!="") {
794 $feed.= " <language>".$this->language."</language>\n"; 794 $feed.= " <language>".$this->language."</language>\n";
795 } 795 }
796 if ($this->copyright!="") { 796 if ($this->copyright!="") {
797 $feed.= " <copyright>".FeedCreator::iTrunc(htmlspecialchars($this->copyright),100)."</copyright>\n"; 797 $feed.= " <copyright>".FeedCreator::iTrunc(htmlspecialchars($this->copyright),100)."</copyright>\n";
798 } 798 }
799 if ($this->editor!="") { 799 if ($this->editor!="") {
800 $feed.= " <managingEditor>".FeedCreator::iTrunc(htmlspecialchars($this->editor),100)."</managingEditor>\n"; 800 $feed.= " <managingEditor>".FeedCreator::iTrunc(htmlspecialchars($this->editor),100)."</managingEditor>\n";
801 } 801 }
802 if ($this->webmaster!="") { 802 if ($this->webmaster!="") {
803 $feed.= " <webMaster>".FeedCreator::iTrunc(htmlspecialchars($this->webmaster),100)."</webMaster>\n"; 803 $feed.= " <webMaster>".FeedCreator::iTrunc(htmlspecialchars($this->webmaster),100)."</webMaster>\n";
804 } 804 }
805 if ($this->pubDate!="") { 805 if ($this->pubDate!="") {
806 $pubDate = new FeedDate($this->pubDate); 806 $pubDate = new FeedDate($this->pubDate);
807 $feed.= " <pubDate>".htmlspecialchars($pubDate->rfc822())."</pubDate>\n"; 807 $feed.= " <pubDate>".htmlspecialchars($pubDate->rfc822())."</pubDate>\n";
808 } 808 }
809 if ($this->category!="") { 809 if ($this->category!="") {
810 $feed.= " <category>".htmlspecialchars($this->category)."</category>\n"; 810 $feed.= " <category>".htmlspecialchars($this->category)."</category>\n";
811 } 811 }
812 if ($this->docs!="") { 812 if ($this->docs!="") {
813 $feed.= " <docs>".FeedCreator::iTrunc(htmlspecialchars($this->docs),500)."</docs>\n"; 813 $feed.= " <docs>".FeedCreator::iTrunc(htmlspecialchars($this->docs),500)."</docs>\n";
814 } 814 }
815 if ($this->ttl!="") { 815 if ($this->ttl!="") {
816 $feed.= " <ttl>".htmlspecialchars($this->ttl)."</ttl>\n"; 816 $feed.= " <ttl>".htmlspecialchars($this->ttl)."</ttl>\n";
817 } 817 }
818 if ($this->rating!="") { 818 if ($this->rating!="") {
819 $feed.= " <rating>".FeedCreator::iTrunc(htmlspecialchars($this->rating),500)."</rating>\n"; 819 $feed.= " <rating>".FeedCreator::iTrunc(htmlspecialchars($this->rating),500)."</rating>\n";
820 } 820 }
821 if ($this->skipHours!="") { 821 if ($this->skipHours!="") {
822 $feed.= " <skipHours>".htmlspecialchars($this->skipHours)."</skipHours>\n"; 822 $feed.= " <skipHours>".htmlspecialchars($this->skipHours)."</skipHours>\n";
823 } 823 }
824 if ($this->skipDays!="") { 824 if ($this->skipDays!="") {
825 $feed.= " <skipDays>".htmlspecialchars($this->skipDays)."</skipDays>\n"; 825 $feed.= " <skipDays>".htmlspecialchars($this->skipDays)."</skipDays>\n";
826 } 826 }
827 $feed.= $this->_createAdditionalElements($this->additionalElements, " "); 827 $feed.= $this->_createAdditionalElements($this->additionalElements, " ");
828   828  
829 for ($i=0;$i<count($this->items);$i++) { 829 for ($i=0;$i<count($this->items);$i++) {
830 $feed.= " <item>\n"; 830 $feed.= " <item>\n";
831 $feed.= " <title>".FeedCreator::iTrunc(htmlspecialchars(strip_tags($this->items[$i]->title)),100)."</title>\n"; 831 $feed.= " <title>".FeedCreator::iTrunc(htmlspecialchars(strip_tags($this->items[$i]->title)),100)."</title>\n";
832 $feed.= " <link>".htmlspecialchars($this->items[$i]->link)."</link>\n"; 832 $feed.= " <link>".htmlspecialchars($this->items[$i]->link)."</link>\n";
833 $feed.= " <description>".htmlspecialchars($this->items[$i]->description)."</description>\n"; 833 $feed.= " <description>".htmlspecialchars($this->items[$i]->description)."</description>\n";
834 if ($this->items[$i]->author!="") { 834 if ($this->items[$i]->author!="") {
835 $feed.= " <author>".htmlspecialchars($this->items[$i]->author)."</author>\n"; 835 $feed.= " <author>".htmlspecialchars($this->items[$i]->author)."</author>\n";
836 } 836 }
837 /* 837 /*
838 // on hold 838 // on hold
839 if ($this->items[$i]->source!="") { 839 if ($this->items[$i]->source!="") {
840 $feed.= " <source>".htmlspecialchars($this->items[$i]->source)."</source>\n"; 840 $feed.= " <source>".htmlspecialchars($this->items[$i]->source)."</source>\n";
841 } 841 }
842 */ 842 */
843 if ($this->items[$i]->category!="") { 843 if ($this->items[$i]->category!="") {
844 $feed.= " <category>".htmlspecialchars($this->items[$i]->category)."</category>\n"; 844 $feed.= " <category>".htmlspecialchars($this->items[$i]->category)."</category>\n";
845 } 845 }
846 if ($this->items[$i]->comments!="") { 846 if ($this->items[$i]->comments!="") {
847 $feed.= " <comments>".$this->items[$i]->comments."</comments>\n"; 847 $feed.= " <comments>".$this->items[$i]->comments."</comments>\n";
848 } 848 }
849 if ($this->items[$i]->date!="") { 849 if ($this->items[$i]->date!="") {
850 $itemDate = new FeedDate($this->items[$i]->date); 850 $itemDate = new FeedDate($this->items[$i]->date);
851 $feed.= " <pubDate>".htmlspecialchars($itemDate->rfc822())."</pubDate>\n"; 851 $feed.= " <pubDate>".htmlspecialchars($itemDate->rfc822())."</pubDate>\n";
852 } 852 }
853 if ($this->items[$i]->guid!="") { 853 if ($this->items[$i]->guid!="") {
854 $feed.= " <guid>".$this->items[$i]->guid."</guid>\n"; 854 $feed.= " <guid>".$this->items[$i]->guid."</guid>\n";
855 } 855 }
856 $feed.= $this->_createAdditionalElements($this->items[$i]->additionalElements, " "); 856 $feed.= $this->_createAdditionalElements($this->items[$i]->additionalElements, " ");
857 $feed.= " </item>\n"; 857 $feed.= " </item>\n";
858 } 858 }
859 $feed.= " </channel>\n"; 859 $feed.= " </channel>\n";
860 $feed.= "</rss>\n"; 860 $feed.= "</rss>\n";
861 return $feed; 861 return $feed;
862 } 862 }
863 # }}} 863 # }}}
864 } 864 }
865   865  
866   866  
867   867  
868 /** 868 /**
869 * RSSCreator20 is a FeedCreator that implements RDF Site Summary (RSS) 2.0. 869 * RSSCreator20 is a FeedCreator that implements RDF Site Summary (RSS) 2.0.
870 * 870 *
871 * @see http://backend.userland.com/rss 871 * @see http://backend.userland.com/rss
872 * @since 1.3 872 * @since 1.3
873 * @author Kai Blankenhorn <kaib@bitfolge.de> 873 * @author Kai Blankenhorn <kaib@bitfolge.de>
874 */ 874 */
875 class RSSCreator20 extends RSSCreator091 { 875 class RSSCreator20 extends RSSCreator091 {
876   876  
877 # {{{ __construct 877 # {{{ __construct
878 function RSSCreator20() { 878 function RSSCreator20() {
879 parent::_setRSSVersion("2.0"); 879 parent::_setRSSVersion("2.0");
880 } 880 }
881 # }}} 881 # }}}
882   882  
883 } 883 }
884   884  
885   885  
886 /** 886 /**
887 * PIECreator01 is a FeedCreator that implements the emerging PIE specification, 887 * PIECreator01 is a FeedCreator that implements the emerging PIE specification,
888 * as in http://intertwingly.net/wiki/pie/Syntax. 888 * as in http://intertwingly.net/wiki/pie/Syntax.
889 * 889 *
890 * @deprecated 890 * @deprecated
891 * @since 1.3 891 * @since 1.3
892 * @author Scott Reynen <scott@randomchaos.com> and Kai Blankenhorn <kaib@bitfolge.de> 892 * @author Scott Reynen <scott@randomchaos.com> and Kai Blankenhorn <kaib@bitfolge.de>
893 */ 893 */
894 class PIECreator01 extends FeedCreator { 894 class PIECreator01 extends FeedCreator {
895   895  
896 # {{{ createFeed 896 # {{{ createFeed
897 function createFeed() { 897 function createFeed() {
898 $feed = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; 898 $feed = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
899 $feed.= "<feed version=\"0.1\" xmlns=\"http://example.com/newformat#\">\n"; 899 $feed.= "<feed version=\"0.1\" xmlns=\"http://example.com/newformat#\">\n";
900 $feed.= " <title>".FeedCreator::iTrunc(htmlspecialchars($this->title),100)."</title>\n"; 900 $feed.= " <title>".FeedCreator::iTrunc(htmlspecialchars($this->title),100)."</title>\n";
901 $feed.= " <subtitle>".FeedCreator::iTrunc(htmlspecialchars($this->description),500)."</subtitle>\n"; 901 $feed.= " <subtitle>".FeedCreator::iTrunc(htmlspecialchars($this->description),500)."</subtitle>\n";
902 $feed.= " <link>".$this->link."</link>\n"; 902 $feed.= " <link>".$this->link."</link>\n";
903 for ($i=0;$i<count($this->items);$i++) { 903 for ($i=0;$i<count($this->items);$i++) {
904 $feed.= " <entry>\n"; 904 $feed.= " <entry>\n";
905 $feed.= " <title>".FeedCreator::iTrunc(htmlspecialchars(strip_tags($this->items[$i]->title)),100)."</title>\n"; 905 $feed.= " <title>".FeedCreator::iTrunc(htmlspecialchars(strip_tags($this->items[$i]->title)),100)."</title>\n";
906 $feed.= " <link>".htmlspecialchars($this->items[$i]->link)."</link>\n"; 906 $feed.= " <link>".htmlspecialchars($this->items[$i]->link)."</link>\n";
907 $itemDate = new FeedDate($this->items[$i]->date); 907 $itemDate = new FeedDate($this->items[$i]->date);
908 $feed.= " <created>".htmlspecialchars($itemDate->iso8601())."</created>\n"; 908 $feed.= " <created>".htmlspecialchars($itemDate->iso8601())."</created>\n";
909 $feed.= " <issued>".htmlspecialchars($itemDate->iso8601())."</issued>\n"; 909 $feed.= " <issued>".htmlspecialchars($itemDate->iso8601())."</issued>\n";
910 $feed.= " <modified>".htmlspecialchars($itemDate->iso8601())."</modified>\n"; 910 $feed.= " <modified>".htmlspecialchars($itemDate->iso8601())."</modified>\n";
911 $feed.= " <id>".$this->items[$i]->guid."</id>\n"; 911 $feed.= " <id>".$this->items[$i]->guid."</id>\n";
912 if ($this->items[$i]->author!="") { 912 if ($this->items[$i]->author!="") {
913 $feed.= " <author>\n"; 913 $feed.= " <author>\n";
914 $feed.= " <name>".htmlspecialchars($this->items[$i]->author)."</name>\n"; 914 $feed.= " <name>".htmlspecialchars($this->items[$i]->author)."</name>\n";
915 if ($this->items[$i]->authorEmail!="") { 915 if ($this->items[$i]->authorEmail!="") {
916 $feed.= " <email>".$this->items[$i]->authorEmail."</email>\n"; 916 $feed.= " <email>".$this->items[$i]->authorEmail."</email>\n";
917 } 917 }
918 $feed.=" </author>\n"; 918 $feed.=" </author>\n";
919 } 919 }
920 $feed.= " <content type=\"text/html\" xml:lang=\"en-us\">\n"; 920 $feed.= " <content type=\"text/html\" xml:lang=\"en-us\">\n";
921 $feed.= " <div xmlns=\"http://www.w3.org/1999/xhtml\">".$this->items[$i]->description."</div>\n"; 921 $feed.= " <div xmlns=\"http://www.w3.org/1999/xhtml\">".$this->items[$i]->description."</div>\n";
922 $feed.= " </content>\n"; 922 $feed.= " </content>\n";
923 $feed.= " </entry>\n"; 923 $feed.= " </entry>\n";
924 } 924 }
925 $feed.= "</feed>\n"; 925 $feed.= "</feed>\n";
926 return $feed; 926 return $feed;
927 } 927 }
928 # }}} 928 # }}}
929 } 929 }
930   930  
931   931  
932 /** 932 /**
933 * AtomCreator03 is a FeedCreator that implements the atom specification, 933 * AtomCreator03 is a FeedCreator that implements the atom specification,
934 * as in http://www.intertwingly.net/wiki/pie/FrontPage. 934 * as in http://www.intertwingly.net/wiki/pie/FrontPage.
935 * Please note that just by using AtomCreator03 you won't automatically 935 * Please note that just by using AtomCreator03 you won't automatically
936 * produce valid atom files. For example, you have to specify either an editor 936 * produce valid atom files. For example, you have to specify either an editor
937 * for the feed or an author for every single feed item. 937 * for the feed or an author for every single feed item.
938 * 938 *
939 * Some elements have not been implemented yet. These are (incomplete list): 939 * Some elements have not been implemented yet. These are (incomplete list):
940 * author URL, item author's email and URL, item contents, alternate links, 940 * author URL, item author's email and URL, item contents, alternate links,
941 * other link content types than text/html. Some of them may be created with 941 * other link content types than text/html. Some of them may be created with
942 * AtomCreator03::additionalElements. 942 * AtomCreator03::additionalElements.
943 * 943 *
944 * @see FeedCreator#additionalElements 944 * @see FeedCreator#additionalElements
945 * @since 1.6 945 * @since 1.6
946 * @author Kai Blankenhorn <kaib@bitfolge.de>, Scott Reynen <scott@randomchaos.com> 946 * @author Kai Blankenhorn <kaib@bitfolge.de>, Scott Reynen <scott@randomchaos.com>
947 */ 947 */
948 class AtomCreator03 extends FeedCreator { 948 class AtomCreator03 extends FeedCreator {
949   949  
950 # {{{ __construct 950 # {{{ __construct
951 function AtomCreator03() { 951 function AtomCreator03() {
952 $this->contentType = "application/atom+xml"; 952 $this->contentType = "application/atom+xml";
953 } 953 }
954 # }}} 954 # }}}
955   955  
956 # {{{ createFeed 956 # {{{ createFeed
957 function createFeed() { 957 function createFeed() {
958 $feed = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; 958 $feed = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
959 $feed.= $this->_createGeneratorComment(); 959 $feed.= $this->_createGeneratorComment();
960 $feed.= "<feed version=\"0.3\" xmlns=\"http://purl.org/atom/ns#\""; 960 $feed.= "<feed version=\"0.3\" xmlns=\"http://purl.org/atom/ns#\"";
961 if ($this->language!="") { 961 if ($this->language!="") {
962 $feed.= " xml:lang:\"".$this->language."\""; 962 $feed.= " xml:lang:\"".$this->language."\"";
963 } 963 }
964 $feed.= ">\n"; 964 $feed.= ">\n";
965 $feed.= " <title>".htmlspecialchars($this->title)."</title>\n"; 965 $feed.= " <title>".htmlspecialchars($this->title)."</title>\n";
966 $feed.= " <tagline>".htmlspecialchars($this->description)."</tagline>\n"; 966 $feed.= " <tagline>".htmlspecialchars($this->description)."</tagline>\n";
967 $feed.= " <link rel=\"alternate\" type=\"text/html\" href=\"".htmlspecialchars($this->link)."\"/>\n"; 967 $feed.= " <link rel=\"alternate\" type=\"text/html\" href=\"".htmlspecialchars($this->link)."\"/>\n";
968 $feed.= " <id>".$this->link."</id>\n"; 968 $feed.= " <id>".$this->link."</id>\n";
969 $now = new FeedDate(); 969 $now = new FeedDate();
970 $feed.= " <modified>".htmlspecialchars($now->iso8601())."</modified>\n"; 970 $feed.= " <modified>".htmlspecialchars($now->iso8601())."</modified>\n";
971 if ($this->editor!="") { 971 if ($this->editor!="") {
972 $feed.= " <author>\n"; 972 $feed.= " <author>\n";
973 $feed.= " <name>".$this->editor."</name>\n"; 973 $feed.= " <name>".$this->editor."</name>\n";
974 if ($this->editorEmail!="") { 974 if ($this->editorEmail!="") {
975 $feed.= " <email>".$this->editorEmail."</email>\n"; 975 $feed.= " <email>".$this->editorEmail."</email>\n";
976 } 976 }
977 $feed.= " </author>\n"; 977 $feed.= " </author>\n";
978 } 978 }
979 $feed.= " <generator>".FEEDCREATOR_VERSION."</generator>\n"; 979 $feed.= " <generator>".FEEDCREATOR_VERSION."</generator>\n";
980 $feed.= $this->_createAdditionalElements($this->additionalElements, " "); 980 $feed.= $this->_createAdditionalElements($this->additionalElements, " ");
981 for ($i=0;$i<count($this->items);$i++) { 981 for ($i=0;$i<count($this->items);$i++) {
982 $feed.= " <entry>\n"; 982 $feed.= " <entry>\n";
983 $feed.= " <title>".htmlspecialchars(strip_tags($this->items[$i]->title))."</title>\n"; 983 $feed.= " <title>".htmlspecialchars(strip_tags($this->items[$i]->title))."</title>\n";
984 $feed.= " <link rel=\"alternate\" type=\"text/html\" href=\"".htmlspecialchars($this->items[$i]->link)."\"/>\n"; 984 $feed.= " <link rel=\"alternate\" type=\"text/html\" href=\"".htmlspecialchars($this->items[$i]->link)."\"/>\n";
985 if ($this->items[$i]->date=="") { 985 if ($this->items[$i]->date=="") {
986 $this->items[$i]->date = time(); 986 $this->items[$i]->date = time();
987 } 987 }
988 $itemDate = new FeedDate($this->items[$i]->date); 988 $itemDate = new FeedDate($this->items[$i]->date);
989 $feed.= " <created>".htmlspecialchars($itemDate->iso8601())."</created>\n"; 989 $feed.= " <created>".htmlspecialchars($itemDate->iso8601())."</created>\n";
990 $feed.= " <issued>".htmlspecialchars($itemDate->iso8601())."</issued>\n"; 990 $feed.= " <issued>".htmlspecialchars($itemDate->iso8601())."</issued>\n";
991 $feed.= " <modified>".htmlspecialchars($itemDate->iso8601())."</modified>\n"; 991 $feed.= " <modified>".htmlspecialchars($itemDate->iso8601())."</modified>\n";
992 $feed.= " <id>".$this->items[$i]->link."</id>\n"; 992 $feed.= " <id>".$this->items[$i]->link."</id>\n";
993 $feed.= $this->_createAdditionalElements($this->items[$i]->additionalElements, " "); 993 $feed.= $this->_createAdditionalElements($this->items[$i]->additionalElements, " ");
994 if ($this->items[$i]->author!="") { 994 if ($this->items[$i]->author!="") {
995 $feed.= " <author>\n"; 995 $feed.= " <author>\n";
996 $feed.= " <name>".htmlspecialchars($this->items[$i]->author)."</name>\n"; 996 $feed.= " <name>".htmlspecialchars($this->items[$i]->author)."</name>\n";
997 $feed.= " </author>\n"; 997 $feed.= " </author>\n";
998 } 998 }
999 if ($this->items[$i]->description!="") { 999 if ($this->items[$i]->description!="") {
1000 $feed.= " <summary>".htmlspecialchars($this->items[$i]->description)."</summary>\n"; 1000 $feed.= " <summary>".htmlspecialchars($this->items[$i]->description)."</summary>\n";
1001 } 1001 }
1002 $feed.= " </entry>\n"; 1002 $feed.= " </entry>\n";
1003 } 1003 }
1004 $feed.= "</feed>\n"; 1004 $feed.= "</feed>\n";
1005 return $feed; 1005 return $feed;
1006 } 1006 }
1007 # }}} 1007 # }}}
1008 } 1008 }
1009   1009  
1010   1010  
1011 /** 1011 /**
1012 * MBOXCreator is a FeedCreator that implements the mbox format 1012 * MBOXCreator is a FeedCreator that implements the mbox format
1013 * as described in http://www.qmail.org/man/man5/mbox.html 1013 * as described in http://www.qmail.org/man/man5/mbox.html
1014 * 1014 *
1015 * @since 1.3 1015 * @since 1.3
1016 * @author Kai Blankenhorn <kaib@bitfolge.de> 1016 * @author Kai Blankenhorn <kaib@bitfolge.de>
1017 */ 1017 */
1018 class MBOXCreator extends FeedCreator { 1018 class MBOXCreator extends FeedCreator {
1019   1019  
1020 # {{{ __construct 1020 # {{{ __construct
1021 function MBOXCreator() { 1021 function MBOXCreator() {
1022 $this->contentType = "text/plain"; 1022 $this->contentType = "text/plain";
1023 } 1023 }
1024 # }}} 1024 # }}}
1025   1025  
1026 # {{{ qp_enc 1026 # {{{ qp_enc
1027 function qp_enc($input = "", $line_max = 76) { 1027 function qp_enc($input = "", $line_max = 76) {
1028 $hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'); 1028 $hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
1029 $lines = preg_split("/(?:\r\n|\r|\n)/", $input); 1029 $lines = preg_split("/(?:\r\n|\r|\n)/", $input);
1030 $eol = "\r\n"; 1030 $eol = "\r\n";
1031 $escape = "="; 1031 $escape = "=";
1032 $output = ""; 1032 $output = "";
1033 while( list(, $line) = each($lines) ) { 1033 while( list(, $line) = each($lines) ) {
1034 //$line = rtrim($line); // remove trailing white space -> no =20\r\n necessary 1034 //$line = rtrim($line); // remove trailing white space -> no =20\r\n necessary
1035 $linlen = strlen($line); 1035 $linlen = strlen($line);
1036 $newline = ""; 1036 $newline = "";
1037 for($i = 0; $i < $linlen; $i++) { 1037 for($i = 0; $i < $linlen; $i++) {
1038 $c = substr($line, $i, 1); 1038 $c = substr($line, $i, 1);
1039 $dec = ord($c); 1039 $dec = ord($c);
1040 if ( ($dec == 32) && ($i == ($linlen - 1)) ) { // convert space at eol only 1040 if ( ($dec == 32) && ($i == ($linlen - 1)) ) { // convert space at eol only
1041 $c = "=20"; 1041 $c = "=20";
1042 } elseif ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) ) { // always encode "\t", which is *not* required 1042 } elseif ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) ) { // always encode "\t", which is *not* required
1043 $h2 = floor($dec/16); $h1 = floor($dec%16); 1043 $h2 = floor($dec/16); $h1 = floor($dec%16);
1044 $c = $escape.$hex["$h2"].$hex["$h1"]; 1044 $c = $escape.$hex["$h2"].$hex["$h1"];
1045 } 1045 }
1046 if ( (strlen($newline) + strlen($c)) >= $line_max ) { // CRLF is not counted 1046 if ( (strlen($newline) + strlen($c)) >= $line_max ) { // CRLF is not counted
1047 $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay 1047 $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay
1048 $newline = ""; 1048 $newline = "";
1049 } 1049 }
1050 $newline .= $c; 1050 $newline .= $c;
1051 } // end of for 1051 } // end of for
1052 $output .= $newline.$eol; 1052 $output .= $newline.$eol;
1053 } 1053 }
1054 return trim($output); 1054 return trim($output);
1055 } 1055 }
1056 # }}} 1056 # }}}
1057   1057  
1058 # {{{ createFeed 1058 # {{{ createFeed
1059 /** 1059 /**
1060 * Builds the MBOX contents. 1060 * Builds the MBOX contents.
1061 * @return string the feed's complete text 1061 * @return string the feed's complete text
1062 */ 1062 */
1063 function createFeed() { 1063 function createFeed() {
1064 global $config; 1064 global $config;
1065 for ($i=0;$i<count($this->items);$i++) { 1065 for ($i=0;$i<count($this->items);$i++) {
1066 if ($this->items[$i]->author!="") { 1066 if ($this->items[$i]->author!="") {
1067 $from = $this->items[$i]->author; 1067 $from = $this->items[$i]->author;
1068 } else { 1068 } else {
1069 $from = $this->title; 1069 $from = $this->title;
1070 } 1070 }
1071 $itemDate = new FeedDate($this->items[$i]->date); 1071 $itemDate = new FeedDate($this->items[$i]->date);
1072 $feed.= "From ".strtr(MBOXCreator::qp_enc($from)," ","_")." ".date("D M d H:i:s Y",$itemDate->unix())."\n"; 1072 $feed.= "From ".strtr(MBOXCreator::qp_enc($from)," ","_")." ".date("D M d H:i:s Y",$itemDate->unix())."\n";
1073 $feed.= "Content-Type: text/plain;\n"; 1073 $feed.= "Content-Type: text/plain;\n";
1074 $feed.= " charset=\"".$config->outputEnc."\"\n"; 1074 $feed.= " charset=\"".$config->outputEnc."\"\n";
1075 $feed.= "Content-Transfer-Encoding: quoted-printable\n"; 1075 $feed.= "Content-Transfer-Encoding: quoted-printable\n";
1076 $feed.= "Content-Type: text/plain\n"; 1076 $feed.= "Content-Type: text/plain\n";
1077 $feed.= "From: \"".MBOXCreator::qp_enc($from)."\"\n"; 1077 $feed.= "From: \"".MBOXCreator::qp_enc($from)."\"\n";
1078 $feed.= "Date: ".$itemDate->rfc822()."\n"; 1078 $feed.= "Date: ".$itemDate->rfc822()."\n";
1079 $feed.= "Subject: ".MBOXCreator::qp_enc(FeedCreator::iTrunc($this->items[$i]->title,100))."\n"; 1079 $feed.= "Subject: ".MBOXCreator::qp_enc(FeedCreator::iTrunc($this->items[$i]->title,100))."\n";
1080 $feed.= "\n"; 1080 $feed.= "\n";
1081 $body = chunk_split(MBOXCreator::qp_enc($this->items[$i]->description)); 1081 $body = chunk_split(MBOXCreator::qp_enc($this->items[$i]->description));
1082 $feed.= preg_replace("~\nFrom ([^\n]*)(\n?)~","\n>From $1$2\n",$body); 1082 $feed.= preg_replace("~\nFrom ([^\n]*)(\n?)~","\n>From $1$2\n",$body);
1083 $feed.= "\n"; 1083 $feed.= "\n";
1084 $feed.= "\n"; 1084 $feed.= "\n";
1085 } 1085 }
1086 return $feed; 1086 return $feed;
1087 } 1087 }
1088 # }}} 1088 # }}}
1089   1089  
1090 # {{{ _generateFilename 1090 # {{{ _generateFilename
1091 /** 1091 /**
1092 * Generate a filename for the feed cache file. Overridden from FeedCreator to prevent XML data types. 1092 * Generate a filename for the feed cache file. Overridden from FeedCreator to prevent XML data types.
1093 * @return string the feed cache filename 1093 * @return string the feed cache filename
1094 * @since 1.4 1094 * @since 1.4
1095 * @access private 1095 * @access private
1096 */ 1096 */
1097 function _generateFilename() { 1097 function _generateFilename() {
1098 $fileInfo = pathinfo($_SERVER["PHP_SELF"]); 1098 $fileInfo = pathinfo($_SERVER["PHP_SELF"]);
1099 return substr($fileInfo["basename"],0,-(strlen($fileInfo["extension"])+1)).".mbox"; 1099 return substr($fileInfo["basename"],0,-(strlen($fileInfo["extension"])+1)).".mbox";
1100 } 1100 }
1101 # }}} 1101 # }}}
1102 } 1102 }
1103   1103  
1104   1104  
1105 /** 1105 /**
1106 * OPMLCreator is a FeedCreator that implements OPML 1.0. 1106 * OPMLCreator is a FeedCreator that implements OPML 1.0.
1107 * 1107 *
1108 * @see http://opml.scripting.com/spec 1108 * @see http://opml.scripting.com/spec
1109 * @author Dirk Clemens, Kai Blankenhorn 1109 * @author Dirk Clemens, Kai Blankenhorn
1110 * @since 1.5 1110 * @since 1.5
1111 */ 1111 */
1112 class OPMLCreator extends FeedCreator { 1112 class OPMLCreator extends FeedCreator {
1113 1113
1114 # {{{ createFeed 1114 # {{{ createFeed
1115 function createFeed() { 1115 function createFeed() {
1116 $feed = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; 1116 $feed = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
1117 $feed.= $this->_createGeneratorComment(); 1117 $feed.= $this->_createGeneratorComment();
1118 $feed.= "<opml xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"; 1118 $feed.= "<opml xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n";
1119 $feed.= " <head>\n"; 1119 $feed.= " <head>\n";
1120 $feed.= " <title>".htmlspecialchars($this->title)."</title>\n"; 1120 $feed.= " <title>".htmlspecialchars($this->title)."</title>\n";
1121 if ($this->pubDate!="") { 1121 if ($this->pubDate!="") {
1122 $date = new FeedDate($this->pubDate); 1122 $date = new FeedDate($this->pubDate);
1123 $feed.= " <dateCreated>".$date->rfc822()."</dateCreated>\n"; 1123 $feed.= " <dateCreated>".$date->rfc822()."</dateCreated>\n";
1124 } 1124 }
1125 if ($this->lastBuildDate!="") { 1125 if ($this->lastBuildDate!="") {
1126 $date = new FeedDate($this->lastBuildDate); 1126 $date = new FeedDate($this->lastBuildDate);
1127 $feed.= " <dateModified>".$date->rfc822()."</dateModified>\n"; 1127 $feed.= " <dateModified>".$date->rfc822()."</dateModified>\n";
1128 } 1128 }
1129 if ($this->editor!="") { 1129 if ($this->editor!="") {
1130 $feed.= " <ownerName>".$this->editor."</ownerName>\n"; 1130 $feed.= " <ownerName>".$this->editor."</ownerName>\n";
1131 } 1131 }
1132 if ($this->editorEmail!="") { 1132 if ($this->editorEmail!="") {
1133 $feed.= " <ownerEmail>".$this->editorEmail."</ownerEmail>\n"; 1133 $feed.= " <ownerEmail>".$this->editorEmail."</ownerEmail>\n";
1134 } 1134 }
1135 $feed.= " </head>\n"; 1135 $feed.= " </head>\n";
1136 $feed.= " <body>\n"; 1136 $feed.= " <body>\n";
1137 for ($i=0;$i<count($this->items);$i++) { 1137 for ($i=0;$i<count($this->items);$i++) {
1138 $feed.= " <outline type=\"rss\" "; 1138 $feed.= " <outline type=\"rss\" ";
1139 $title = htmlspecialchars(strip_tags(strtr($this->items[$i]->title,"\n\r"," "))); 1139 $title = htmlspecialchars(strip_tags(strtr($this->items[$i]->title,"\n\r"," ")));
1140 $feed.= " title=\"".$title."\""; 1140 $feed.= " title=\"".$title."\"";
1141 $feed.= " text=\"".$title."\""; 1141 $feed.= " text=\"".$title."\"";
1142 //$feed.= " description=\"".htmlspecialchars($this->items[$i]->description)."\""; 1142 //$feed.= " description=\"".htmlspecialchars($this->items[$i]->description)."\"";
1143 $feed.= " url=\"".htmlspecialchars($this->items[$i]->link)."\""; 1143 $feed.= " url=\"".htmlspecialchars($this->items[$i]->link)."\"";
1144 $feed.= "/>\n"; 1144 $feed.= "/>\n";
1145 } 1145 }
1146 $feed.= " </body>\n"; 1146 $feed.= " </body>\n";
1147 $feed.= "</opml>\n"; 1147 $feed.= "</opml>\n";
1148 return $feed; 1148 return $feed;
1149 } 1149 }
1150 # }}} 1150 # }}}
1151 } 1151 }
1152   1152  
1153 ?> 1153 ?>