]> git.wh0rd.org - tt-rss.git/blob - feedcreator.class.php
bugfix: catchupAllFeeds was broken in iframe transition
[tt-rss.git] / feedcreator.class.php
1 <?php
2 /***************************************************************************
3
4 FeedCreator class v1.7.2
5 originally (c) Kai Blankenhorn
6 www.bitfolge.de
7 kaib@bitfolge.de
8 v1.3 work by Scott Reynen (scott@randomchaos.com) and Kai Blankenhorn
9 v1.5 OPML support by Dirk Clemens
10
11 This library is free software; you can redistribute it and/or
12 modify it under the terms of the GNU Lesser General Public
13 License as published by the Free Software Foundation; either
14 version 2.1 of the License, or (at your option) any later version.
15
16 This library is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 Lesser General Public License for more details.
20
21 You should have received a copy of the GNU Lesser General Public
22 License along with this library; if not, write to the Free Software
23 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24
25 ****************************************************************************
26
27
28 Changelog:
29
30 v1.7.2 10-11-04
31 license changed to LGPL
32
33 v1.7.1
34 fixed a syntax bug
35 fixed left over debug code
36
37 v1.7 07-18-04
38 added HTML and JavaScript feeds (configurable via CSS) (thanks to Pascal Van Hecke)
39 added HTML descriptions for all feed formats (thanks to Pascal Van Hecke)
40 added a switch to select an external stylesheet (thanks to Pascal Van Hecke)
41 changed default content-type to application/xml
42 added character encoding setting
43 fixed numerous smaller bugs (thanks to Sören Fuhrmann of golem.de)
44 improved changing ATOM versions handling (thanks to August Trometer)
45 improved the UniversalFeedCreator's useCached method (thanks to Sören Fuhrmann of golem.de)
46 added charset output in HTTP headers (thanks to Sören Fuhrmann of golem.de)
47 added Slashdot namespace to RSS 1.0 (thanks to Sören Fuhrmann of golem.de)
48
49 v1.6 05-10-04
50 added stylesheet to RSS 1.0 feeds
51 fixed generator comment (thanks Kevin L. Papendick and Tanguy Pruvot)
52 fixed RFC822 date bug (thanks Tanguy Pruvot)
53 added TimeZone customization for RFC8601 (thanks Tanguy Pruvot)
54 fixed Content-type could be empty (thanks Tanguy Pruvot)
55 fixed author/creator in RSS1.0 (thanks Tanguy Pruvot)
56
57 v1.6 beta 02-28-04
58 added Atom 0.3 support (not all features, though)
59 improved OPML 1.0 support (hopefully - added more elements)
60 added support for arbitrary additional elements (use with caution)
61 code beautification :-)
62 considered beta due to some internal changes
63
64 v1.5.1 01-27-04
65 fixed some RSS 1.0 glitches (thanks to Stéphane Vanpoperynghe)
66 fixed some inconsistencies between documentation and code (thanks to Timothy Martin)
67
68 v1.5 01-06-04
69 added support for OPML 1.0
70 added more documentation
71
72 v1.4 11-11-03
73 optional feed saving and caching
74 improved documentation
75 minor improvements
76
77 v1.3 10-02-03
78 renamed to FeedCreator, as it not only creates RSS anymore
79 added support for mbox
80 tentative support for echo/necho/atom/pie/???
81
82 v1.2 07-20-03
83 intelligent auto-truncating of RSS 0.91 attributes
84 don't create some attributes when they're not set
85 documentation improved
86 fixed a real and a possible bug with date conversions
87 code cleanup
88
89 v1.1 06-29-03
90 added images to feeds
91 now includes most RSS 0.91 attributes
92 added RSS 2.0 feeds
93
94 v1.0 06-24-03
95 initial release
96
97
98
99 ***************************************************************************/
100
101 /*** GENERAL USAGE *********************************************************
102
103 include("feedcreator.class.php");
104
105 $rss = new UniversalFeedCreator();
106 $rss->useCached(); // use cached version if age<1 hour
107 $rss->title = "PHP news";
108 $rss->description = "daily news from the PHP scripting world";
109
110 //optional
111 $rss->descriptionTruncSize = 500;
112 $rss->descriptionHtmlSyndicated = true;
113
114 $rss->link = "http://www.dailyphp.net/news";
115 $rss->syndicationURL = "http://www.dailyphp.net/".$_SERVER["PHP_SELF"];
116
117 $image = new FeedImage();
118 $image->title = "dailyphp.net logo";
119 $image->url = "http://www.dailyphp.net/images/logo.gif";
120 $image->link = "http://www.dailyphp.net";
121 $image->description = "Feed provided by dailyphp.net. Click to visit.";
122
123 //optional
124 $image->descriptionTruncSize = 500;
125 $image->descriptionHtmlSyndicated = true;
126
127 $rss->image = $image;
128
129 // get your news items from somewhere, e.g. your database:
130 mysql_select_db($dbHost, $dbUser, $dbPass);
131 $res = mysql_query("SELECT * FROM news ORDER BY newsdate DESC");
132 while ($data = mysql_fetch_object($res)) {
133 $item = new FeedItem();
134 $item->title = $data->title;
135 $item->link = $data->url;
136 $item->description = $data->short;
137
138 //optional
139 item->descriptionTruncSize = 500;
140 item->descriptionHtmlSyndicated = true;
141
142 $item->date = $data->newsdate;
143 $item->source = "http://www.dailyphp.net";
144 $item->author = "John Doe";
145
146 $rss->addItem($item);
147 }
148
149 // valid format strings are: RSS0.91, RSS1.0, RSS2.0, PIE0.1 (deprecated),
150 // MBOX, OPML, ATOM, ATOM0.3, HTML, JS
151 echo $rss->saveFeed("RSS1.0", "news/feed.xml");
152
153
154 ***************************************************************************
155 * A little setup *
156 **************************************************************************/
157
158 // your local timezone, set to "" to disable or for GMT
159 define("TIME_ZONE","+01:00");
160
161
162
163
164 /**
165 * Version string.
166 **/
167 define("FEEDCREATOR_VERSION", "FeedCreator 1.7.2");
168
169
170
171 /**
172 * A FeedItem is a part of a FeedCreator feed.
173 *
174 * @author Kai Blankenhorn <kaib@bitfolge.de>
175 * @since 1.3
176 */
177 class FeedItem extends HtmlDescribable {
178 /**
179 * Mandatory attributes of an item.
180 */
181 var $title, $description, $link;
182
183 /**
184 * Optional attributes of an item.
185 */
186 var $author, $authorEmail, $image, $category, $comments, $guid, $source, $creator;
187
188 /**
189 * Publishing date of an item. May be in one of the following formats:
190 *
191 * RFC 822:
192 * "Mon, 20 Jan 03 18:05:41 +0400"
193 * "20 Jan 03 18:05:41 +0000"
194 *
195 * ISO 8601:
196 * "2003-01-20T18:05:41+04:00"
197 *
198 * Unix:
199 * 1043082341
200 */
201 var $date;
202
203 /**
204 * Any additional elements to include as an assiciated array. All $key => $value pairs
205 * will be included unencoded in the feed item in the form
206 * <$key>$value</$key>
207 * Again: No encoding will be used! This means you can invalidate or enhance the feed
208 * if $value contains markup. This may be abused to embed tags not implemented by
209 * the FeedCreator class used.
210 */
211 var $additionalElements = Array();
212
213 // on hold
214 // var $source;
215 }
216
217
218
219 /**
220 * An FeedImage may be added to a FeedCreator feed.
221 * @author Kai Blankenhorn <kaib@bitfolge.de>
222 * @since 1.3
223 */
224 class FeedImage extends HtmlDescribable {
225 /**
226 * Mandatory attributes of an image.
227 */
228 var $title, $url, $link;
229
230 /**
231 * Optional attributes of an image.
232 */
233 var $width, $height, $description;
234 }
235
236
237
238 /**
239 * An HtmlDescribable is an item within a feed that can have a description that may
240 * include HTML markup.
241 */
242 class HtmlDescribable {
243 /**
244 * Indicates whether the description field should be rendered in HTML.
245 */
246 var $descriptionHtmlSyndicated;
247
248 /**
249 * Indicates whether and to how many characters a description should be truncated.
250 */
251 var $descriptionTruncSize;
252
253 /**
254 * Returns a formatted description field, depending on descriptionHtmlSyndicated and
255 * $descriptionTruncSize properties
256 * @return string the formatted description
257 */
258 function getDescription() {
259 $descriptionField = new FeedHtmlField($this->description);
260 $descriptionField->syndicateHtml = $this->descriptionHtmlSyndicated;
261 $descriptionField->truncSize = $this->descriptionTruncSize;
262 return $descriptionField->output();
263 }
264
265 }
266
267
268 /**
269 * An FeedHtmlField describes and generates
270 * a feed, item or image html field (probably a description). Output is
271 * generated based on $truncSize, $syndicateHtml properties.
272 * @author Pascal Van Hecke <feedcreator.class.php@vanhecke.info>
273 * @version 1.6
274 */
275 class FeedHtmlField {
276 /**
277 * Mandatory attributes of a FeedHtmlField.
278 */
279 var $rawFieldContent;
280
281 /**
282 * Optional attributes of a FeedHtmlField.
283 *
284 */
285 var $truncSize, $syndicateHtml;
286
287 /**
288 * Creates a new instance of FeedHtmlField.
289 * @param $string: if given, sets the rawFieldContent property
290 */
291 function FeedHtmlField($parFieldContent) {
292 if ($parFieldContent) {
293 $this->rawFieldContent = $parFieldContent;
294 }
295 }
296
297
298 /**
299 * Creates the right output, depending on $truncSize, $syndicateHtml properties.
300 * @return string the formatted field
301 */
302 function output() {
303 // when field available and syndicated in html we assume
304 // - valid html in $rawFieldContent and we enclose in CDATA tags
305 // - no truncation (truncating risks producing invalid html)
306 if (!$this->rawFieldContent) {
307 $result = "";
308 } elseif ($this->syndicateHtml) {
309 $result = "<![CDATA[".$this->rawFieldContent."]]>";
310 } else {
311 if ($this->truncSize and is_int($this->truncSize)) {
312 $result = FeedCreator::iTrunc(htmlspecialchars($this->rawFieldContent),$this->truncSize);
313 } else {
314 $result = htmlspecialchars($this->rawFieldContent);
315 }
316 }
317 return $result;
318 }
319
320 }
321
322
323
324 /**
325 * UniversalFeedCreator lets you choose during runtime which
326 * format to build.
327 * For general usage of a feed class, see the FeedCreator class
328 * below or the example above.
329 *
330 * @since 1.3
331 * @author Kai Blankenhorn <kaib@bitfolge.de>
332 */
333 class UniversalFeedCreator extends FeedCreator {
334 var $_feed;
335
336 function _setFormat($format) {
337 switch (strtoupper($format)) {
338
339 case "2.0":
340 // fall through
341 case "RSS2.0":
342 $this->_feed = new RSSCreator20();
343 break;
344
345 case "1.0":
346 // fall through
347 case "RSS1.0":
348 $this->_feed = new RSSCreator10();
349 break;
350
351 case "0.91":
352 // fall through
353 case "RSS0.91":
354 $this->_feed = new RSSCreator091();
355 break;
356
357 case "PIE0.1":
358 $this->_feed = new PIECreator01();
359 break;
360
361 case "MBOX":
362 $this->_feed = new MBOXCreator();
363 break;
364
365 case "OPML":
366 $this->_feed = new OPMLCreator();
367 break;
368
369 case "ATOM":
370 // fall through: always the latest ATOM version
371
372 case "ATOM0.3":
373 $this->_feed = new AtomCreator03();
374 break;
375
376 case "HTML":
377 $this->_feed = new HTMLCreator();
378 break;
379
380 case "JS":
381 // fall through
382 case "JAVASCRIPT":
383 $this->_feed = new JSCreator();
384 break;
385
386 default:
387 $this->_feed = new RSSCreator091();
388 break;
389 }
390
391 $vars = get_object_vars($this);
392 foreach ($vars as $key => $value) {
393 // prevent overwriting of properties "contentType", "encoding"; do not copy "_feed" itself
394 if (!in_array($key, array("_feed", "contentType", "encoding"))) {
395 $this->_feed->{$key} = $this->{$key};
396 }
397 }
398 }
399
400 /**
401 * Creates a syndication feed based on the items previously added.
402 *
403 * @see FeedCreator::addItem()
404 * @param string format format the feed should comply to. Valid values are:
405 * "PIE0.1", "mbox", "RSS0.91", "RSS1.0", "RSS2.0", "OPML", "ATOM0.3", "HTML", "JS"
406 * @return string the contents of the feed.
407 */
408 function createFeed($format = "RSS0.91") {
409 $this->_setFormat($format);
410 return $this->_feed->createFeed();
411 }
412
413
414
415 /**
416 * Saves this feed as a file on the local disk. After the file is saved, an HTTP redirect
417 * header may be sent to redirect the use to the newly created file.
418 * @since 1.4
419 *
420 * @param string format format the feed should comply to. Valid values are:
421 * "PIE0.1" (deprecated), "mbox", "RSS0.91", "RSS1.0", "RSS2.0", "OPML", "ATOM", "ATOM0.3", "HTML", "JS"
422 * @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()).
423 * @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.
424 */
425 function saveFeed($format="RSS0.91", $filename="", $displayContents=true) {
426 $this->_setFormat($format);
427 $this->_feed->saveFeed($filename, $displayContents);
428 }
429
430
431 /**
432 * Turns on caching and checks if there is a recent version of this feed in the cache.
433 * If there is, an HTTP redirect header is sent.
434 * To effectively use caching, you should create the FeedCreator object and call this method
435 * before anything else, especially before you do the time consuming task to build the feed
436 * (web fetching, for example).
437 *
438 * @param string format format the feed should comply to. Valid values are:
439 * "PIE0.1" (deprecated), "mbox", "RSS0.91", "RSS1.0", "RSS2.0", "OPML", "ATOM0.3".
440 * @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()).
441 * @param timeout int optional the timeout in seconds before a cached version is refreshed (defaults to 3600 = 1 hour)
442 */
443 function useCached($format="RSS0.91", $filename="", $timeout=3600) {
444 $this->_setFormat($format);
445 $this->_feed->useCached($filename, $timeout);
446 }
447
448 }
449
450
451 /**
452 * FeedCreator is the abstract base implementation for concrete
453 * implementations that implement a specific format of syndication.
454 *
455 * @abstract
456 * @author Kai Blankenhorn <kaib@bitfolge.de>
457 * @since 1.4
458 */
459 class FeedCreator extends HtmlDescribable {
460
461 /**
462 * Mandatory attributes of a feed.
463 */
464 var $title, $description, $link;
465
466
467 /**
468 * Optional attributes of a feed.
469 */
470 var $syndicationURL, $image, $language, $copyright, $pubDate, $lastBuildDate, $editor, $editorEmail, $webmaster, $category, $docs, $ttl, $rating, $skipHours, $skipDays;
471
472 /**
473 * The url of the external xsl stylesheet used to format the naked rss feed.
474 * Ignored in the output when empty.
475 */
476 var $xslStyleSheet = "";
477
478
479 /**
480 * @access private
481 */
482 var $items = Array();
483
484
485 /**
486 * This feed's MIME content type.
487 * @since 1.4
488 * @access private
489 */
490 var $contentType = "application/xml";
491
492
493 /**
494 * This feed's character encoding.
495 * @since 1.6.1
496 **/
497 var $encoding = "utf-8";
498
499
500 /**
501 * Any additional elements to include as an assiciated array. All $key => $value pairs
502 * will be included unencoded in the feed in the form
503 * <$key>$value</$key>
504 * Again: No encoding will be used! This means you can invalidate or enhance the feed
505 * if $value contains markup. This may be abused to embed tags not implemented by
506 * the FeedCreator class used.
507 */
508 var $additionalElements = Array();
509
510
511 /**
512 * Adds an FeedItem to the feed.
513 *
514 * @param object FeedItem $item The FeedItem to add to the feed.
515 * @access public
516 */
517 function addItem($item) {
518 $this->items[] = $item;
519 }
520
521
522 /**
523 * Truncates a string to a certain length at the most sensible point.
524 * First, if there's a '.' character near the end of the string, the string is truncated after this character.
525 * If there is no '.', the string is truncated after the last ' ' character.
526 * If the string is truncated, " ..." is appended.
527 * If the string is already shorter than $length, it is returned unchanged.
528 *
529 * @static
530 * @param string string A string to be truncated.
531 * @param int length the maximum length the string should be truncated to
532 * @return string the truncated string
533 */
534 function iTrunc($string, $length) {
535 if (strlen($string)<=$length) {
536 return $string;
537 }
538
539 $pos = strrpos($string,".");
540 if ($pos>=$length-4) {
541 $string = substr($string,0,$length-4);
542 $pos = strrpos($string,".");
543 }
544 if ($pos>=$length*0.4) {
545 return substr($string,0,$pos+1)." ...";
546 }
547
548 $pos = strrpos($string," ");
549 if ($pos>=$length-4) {
550 $string = substr($string,0,$length-4);
551 $pos = strrpos($string," ");
552 }
553 if ($pos>=$length*0.4) {
554 return substr($string,0,$pos)." ...";
555 }
556
557 return substr($string,0,$length-4)." ...";
558
559 }
560
561
562 /**
563 * Creates a comment indicating the generator of this feed.
564 * The format of this comment seems to be recognized by
565 * Syndic8.com.
566 */
567 function _createGeneratorComment() {
568 return "<!-- generator=\"".FEEDCREATOR_VERSION."\" -->\n";
569 }
570
571
572 /**
573 * Creates a string containing all additional elements specified in
574 * $additionalElements.
575 * @param elements array an associative array containing key => value pairs
576 * @param indentString string a string that will be inserted before every generated line
577 * @return string the XML tags corresponding to $additionalElements
578 */
579 function _createAdditionalElements($elements, $indentString="") {
580 $ae = "";
581 if (is_array($elements)) {
582 foreach($elements AS $key => $value) {
583 $ae.= $indentString."<$key>$value</$key>\n";
584 }
585 }
586 return $ae;
587 }
588
589 function _createStylesheetReferences() {
590 $xml = "";
591 if ($this->cssStyleSheet) $xml .= "<?xml-stylesheet href=\"".$this->cssStyleSheet."\" type=\"text/css\"?>\n";
592 if ($this->xslStyleSheet) $xml .= "<?xml-stylesheet href=\"".$this->xslStyleSheet."\" type=\"text/xsl\"?>\n";
593 return $xml;
594 }
595
596
597 /**
598 * Builds the feed's text.
599 * @abstract
600 * @return string the feed's complete text
601 */
602 function createFeed() {
603 }
604
605 /**
606 * Generate a filename for the feed cache file. The result will be $_SERVER["PHP_SELF"] with the extension changed to .xml.
607 * For example:
608 *
609 * echo $_SERVER["PHP_SELF"]."\n";
610 * echo FeedCreator::_generateFilename();
611 *
612 * would produce:
613 *
614 * /rss/latestnews.php
615 * latestnews.xml
616 *
617 * @return string the feed cache filename
618 * @since 1.4
619 * @access private
620 */
621 function _generateFilename() {
622 $fileInfo = pathinfo($_SERVER["PHP_SELF"]);
623 return substr($fileInfo["basename"],0,-(strlen($fileInfo["extension"])+1)).".xml";
624 }
625
626
627 /**
628 * @since 1.4
629 * @access private
630 */
631 function _redirect($filename) {
632 // attention, heavily-commented-out-area
633
634 // maybe use this in addition to file time checking
635 //Header("Expires: ".date("r",time()+$this->_timeout));
636
637 /* no caching at all, doesn't seem to work as good:
638 Header("Cache-Control: no-cache");
639 Header("Pragma: no-cache");
640 */
641
642 // HTTP redirect, some feed readers' simple HTTP implementations don't follow it
643 //Header("Location: ".$filename);
644
645 Header("Content-Type: ".$this->contentType."; charset=".$this->encoding."; filename=".basename($filename));
646 Header("Content-Disposition: inline; filename=".basename($filename));
647 readfile($filename, "r");
648 die();
649 }
650
651 /**
652 * Turns on caching and checks if there is a recent version of this feed in the cache.
653 * If there is, an HTTP redirect header is sent.
654 * To effectively use caching, you should create the FeedCreator object and call this method
655 * before anything else, especially before you do the time consuming task to build the feed
656 * (web fetching, for example).
657 * @since 1.4
658 * @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()).
659 * @param timeout int optional the timeout in seconds before a cached version is refreshed (defaults to 3600 = 1 hour)
660 */
661 function useCached($filename="", $timeout=3600) {
662 $this->_timeout = $timeout;
663 if ($filename=="") {
664 $filename = $this->_generateFilename();
665 }
666 if (file_exists($filename) AND (time()-filemtime($filename) < $timeout)) {
667 $this->_redirect($filename);
668 }
669 }
670
671
672 /**
673 * Saves this feed as a file on the local disk. After the file is saved, a redirect
674 * header may be sent to redirect the user to the newly created file.
675 * @since 1.4
676 *
677 * @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()).
678 * @param redirect boolean optional send an HTTP redirect header or not. If true, the user will be automatically redirected to the created file.
679 */
680 function saveFeed($filename="", $displayContents=true) {
681 if ($filename=="") {
682 $filename = $this->_generateFilename();
683 }
684 $feedFile = fopen($filename, "w+");
685 if ($feedFile) {
686 fputs($feedFile,$this->createFeed());
687 fclose($feedFile);
688 if ($displayContents) {
689 $this->_redirect($filename);
690 }
691 } else {
692 echo "<br /><b>Error creating feed file, please check write permissions.</b><br />";
693 }
694 }
695
696 }
697
698
699 /**
700 * FeedDate is an internal class that stores a date for a feed or feed item.
701 * Usually, you won't need to use this.
702 */
703 class FeedDate {
704 var $unix;
705
706 /**
707 * Creates a new instance of FeedDate representing a given date.
708 * Accepts RFC 822, ISO 8601 date formats as well as unix time stamps.
709 * @param mixed $dateString optional the date this FeedDate will represent. If not specified, the current date and time is used.
710 */
711 function FeedDate($dateString="") {
712 if ($dateString=="") $dateString = date("r");
713
714 if (is_integer($dateString)) {
715 $this->unix = $dateString;
716 return;
717 }
718 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)) {
719 $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);
720 $this->unix = mktime($matches[4],$matches[5],$matches[6],$months[$matches[2]],$matches[1],$matches[3]);
721 if (substr($matches[7],0,1)=='+' OR substr($matches[7],0,1)=='-') {
722 $tzOffset = (substr($matches[7],0,3) * 60 + substr($matches[7],-2)) * 60;
723 } else {
724 if (strlen($matches[7])==1) {
725 $oneHour = 3600;
726 $ord = ord($matches[7]);
727 if ($ord < ord("M")) {
728 $tzOffset = (ord("A") - $ord - 1) * $oneHour;
729 } elseif ($ord >= ord("M") AND $matches[7]!="Z") {
730 $tzOffset = ($ord - ord("M")) * $oneHour;
731 } elseif ($matches[7]=="Z") {
732 $tzOffset = 0;
733 }
734 }
735 switch ($matches[7]) {
736 case "UT":
737 case "GMT": $tzOffset = 0;
738 }
739 }
740 $this->unix += $tzOffset;
741 return;
742 }
743 if (preg_match("~(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(.*)~",$dateString,$matches)) {
744 $this->unix = mktime($matches[4],$matches[5],$matches[6],$matches[2],$matches[3],$matches[1]);
745 if (substr($matches[7],0,1)=='+' OR substr($matches[7],0,1)=='-') {
746 $tzOffset = (substr($matches[7],0,3) * 60 + substr($matches[7],-2)) * 60;
747 } else {
748 if ($matches[7]=="Z") {
749 $tzOffset = 0;
750 }
751 }
752 $this->unix += $tzOffset;
753 return;
754 }
755 $this->unix = 0;
756 }
757
758 /**
759 * Gets the date stored in this FeedDate as an RFC 822 date.
760 *
761 * @return a date in RFC 822 format
762 */
763 function rfc822() {
764 //return gmdate("r",$this->unix);
765 $date = gmdate("D, d M Y H:i:s", $this->unix);
766 if (TIME_ZONE!="") $date .= " ".str_replace(":","",TIME_ZONE);
767 return $date;
768 }
769
770 /**
771 * Gets the date stored in this FeedDate as an ISO 8601 date.
772 *
773 * @return a date in ISO 8601 format
774 */
775 function iso8601() {
776 $date = gmdate("Y-m-d\TH:i:sO",$this->unix);
777 $date = substr($date,0,22) . ':' . substr($date,-2);
778 if (TIME_ZONE!="") $date = str_replace("+00:00",TIME_ZONE,$date);
779 return $date;
780 }
781
782 /**
783 * Gets the date stored in this FeedDate as unix time stamp.
784 *
785 * @return a date as a unix time stamp
786 */
787 function unix() {
788 return $this->unix;
789 }
790 }
791
792
793 /**
794 * RSSCreator10 is a FeedCreator that implements RDF Site Summary (RSS) 1.0.
795 *
796 * @see http://www.purl.org/rss/1.0/
797 * @since 1.3
798 * @author Kai Blankenhorn <kaib@bitfolge.de>
799 */
800 class RSSCreator10 extends FeedCreator {
801
802 /**
803 * Builds the RSS feed's text. The feed will be compliant to RDF Site Summary (RSS) 1.0.
804 * The feed will contain all items previously added in the same order.
805 * @return string the feed's complete text
806 */
807 function createFeed() {
808 $feed = "<?xml version=\"1.0\" encoding=\"".$this->encoding."\"?>\n";
809 $feed.= $this->_createGeneratorComment();
810 if ($this->cssStyleSheet=="") {
811 $cssStyleSheet = "http://www.w3.org/2000/08/w3c-synd/style.css";
812 }
813 $feed.= $this->_createStylesheetReferences();
814 $feed.= "<rdf:RDF\n";
815 $feed.= " xmlns=\"http://purl.org/rss/1.0/\"\n";
816 $feed.= " xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n";
817 $feed.= " xmlns:slash=\"http://purl.org/rss/1.0/modules/slash/\"\n";
818 $feed.= " xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n";
819 $feed.= " <channel rdf:about=\"".$this->syndicationURL."\">\n";
820 $feed.= " <title>".htmlspecialchars($this->title)."</title>\n";
821 $feed.= " <description>".htmlspecialchars($this->description)."</description>\n";
822 $feed.= " <link>".$this->link."</link>\n";
823 if ($this->image!=null) {
824 $feed.= " <image rdf:resource=\"".$this->image->url."\" />\n";
825 }
826 $now = new FeedDate();
827 $feed.= " <dc:date>".htmlspecialchars($now->iso8601())."</dc:date>\n";
828 $feed.= " <items>\n";
829 $feed.= " <rdf:Seq>\n";
830 for ($i=0;$i<count($this->items);$i++) {
831 $feed.= " <rdf:li rdf:resource=\"".htmlspecialchars($this->items[$i]->link)."\"/>\n";
832 }
833 $feed.= " </rdf:Seq>\n";
834 $feed.= " </items>\n";
835 $feed.= " </channel>\n";
836 if ($this->image!=null) {
837 $feed.= " <image rdf:about=\"".$this->image->url."\">\n";
838 $feed.= " <title>".$this->image->title."</title>\n";
839 $feed.= " <link>".$this->image->link."</link>\n";
840 $feed.= " <url>".$this->image->url."</url>\n";
841 $feed.= " </image>\n";
842 }
843 $feed.= $this->_createAdditionalElements($this->additionalElements, " ");
844
845 for ($i=0;$i<count($this->items);$i++) {
846 $feed.= " <item rdf:about=\"".htmlspecialchars($this->items[$i]->link)."\">\n";
847 //$feed.= " <dc:type>Posting</dc:type>\n";
848 $feed.= " <dc:format>text/html</dc:format>\n";
849 if ($this->items[$i]->date!=null) {
850 $itemDate = new FeedDate($this->items[$i]->date);
851 $feed.= " <dc:date>".htmlspecialchars($itemDate->iso8601())."</dc:date>\n";
852 }
853 if ($this->items[$i]->source!="") {
854 $feed.= " <dc:source>".htmlspecialchars($this->items[$i]->source)."</dc:source>\n";
855 }
856 if ($this->items[$i]->author!="") {
857 $feed.= " <dc:creator>".htmlspecialchars($this->items[$i]->author)."</dc:creator>\n";
858 }
859 $feed.= " <title>".htmlspecialchars(strip_tags(strtr($this->items[$i]->title,"\n\r"," ")))."</title>\n";
860 $feed.= " <link>".htmlspecialchars($this->items[$i]->link)."</link>\n";
861 $feed.= " <description>".htmlspecialchars($this->items[$i]->description)."</description>\n";
862 $feed.= $this->_createAdditionalElements($this->items[$i]->additionalElements, " ");
863 $feed.= " </item>\n";
864 }
865 $feed.= "</rdf:RDF>\n";
866 return $feed;
867 }
868 }
869
870
871
872 /**
873 * RSSCreator091 is a FeedCreator that implements RSS 0.91 Spec, revision 3.
874 *
875 * @see http://my.netscape.com/publish/formats/rss-spec-0.91.html
876 * @since 1.3
877 * @author Kai Blankenhorn <kaib@bitfolge.de>
878 */
879 class RSSCreator091 extends FeedCreator {
880
881 /**
882 * Stores this RSS feed's version number.
883 * @access private
884 */
885 var $RSSVersion;
886
887 function RSSCreator091() {
888 $this->_setRSSVersion("0.91");
889 $this->contentType = "application/rss+xml";
890 }
891
892 /**
893 * Sets this RSS feed's version number.
894 * @access private
895 */
896 function _setRSSVersion($version) {
897 $this->RSSVersion = $version;
898 }
899
900 /**
901 * Builds the RSS feed's text. The feed will be compliant to RDF Site Summary (RSS) 1.0.
902 * The feed will contain all items previously added in the same order.
903 * @return string the feed's complete text
904 */
905 function createFeed() {
906 $feed = "<?xml version=\"1.0\" encoding=\"".$this->encoding."\"?>\n";
907 $feed.= $this->_createGeneratorComment();
908 $feed.= $this->_createStylesheetReferences();
909 $feed.= "<rss version=\"".$this->RSSVersion."\">\n";
910 $feed.= " <channel>\n";
911 $feed.= " <title>".FeedCreator::iTrunc(htmlspecialchars($this->title),100)."</title>\n";
912 $this->descriptionTruncSize = 500;
913 $feed.= " <description>".$this->getDescription()."</description>\n";
914 $feed.= " <link>".$this->link."</link>\n";
915 $now = new FeedDate();
916 $feed.= " <lastBuildDate>".htmlspecialchars($now->rfc822())."</lastBuildDate>\n";
917 $feed.= " <generator>".FEEDCREATOR_VERSION."</generator>\n";
918
919 if ($this->image!=null) {
920 $feed.= " <image>\n";
921 $feed.= " <url>".$this->image->url."</url>\n";
922 $feed.= " <title>".FeedCreator::iTrunc(htmlspecialchars($this->image->title),100)."</title>\n";
923 $feed.= " <link>".$this->image->link."</link>\n";
924 if ($this->image->width!="") {
925 $feed.= " <width>".$this->image->width."</width>\n";
926 }
927 if ($this->image->height!="") {
928 $feed.= " <height>".$this->image->height."</height>\n";
929 }
930 if ($this->image->description!="") {
931 $feed.= " <description>".$this->image->getDescription()."</description>\n";
932 }
933 $feed.= " </image>\n";
934 }
935 if ($this->language!="") {
936 $feed.= " <language>".$this->language."</language>\n";
937 }
938 if ($this->copyright!="") {
939 $feed.= " <copyright>".FeedCreator::iTrunc(htmlspecialchars($this->copyright),100)."</copyright>\n";
940 }
941 if ($this->editor!="") {
942 $feed.= " <managingEditor>".FeedCreator::iTrunc(htmlspecialchars($this->editor),100)."</managingEditor>\n";
943 }
944 if ($this->webmaster!="") {
945 $feed.= " <webMaster>".FeedCreator::iTrunc(htmlspecialchars($this->webmaster),100)."</webMaster>\n";
946 }
947 if ($this->pubDate!="") {
948 $pubDate = new FeedDate($this->pubDate);
949 $feed.= " <pubDate>".htmlspecialchars($pubDate->rfc822())."</pubDate>\n";
950 }
951 if ($this->category!="") {
952 $feed.= " <category>".htmlspecialchars($this->category)."</category>\n";
953 }
954 if ($this->docs!="") {
955 $feed.= " <docs>".FeedCreator::iTrunc(htmlspecialchars($this->docs),500)."</docs>\n";
956 }
957 if ($this->ttl!="") {
958 $feed.= " <ttl>".htmlspecialchars($this->ttl)."</ttl>\n";
959 }
960 if ($this->rating!="") {
961 $feed.= " <rating>".FeedCreator::iTrunc(htmlspecialchars($this->rating),500)."</rating>\n";
962 }
963 if ($this->skipHours!="") {
964 $feed.= " <skipHours>".htmlspecialchars($this->skipHours)."</skipHours>\n";
965 }
966 if ($this->skipDays!="") {
967 $feed.= " <skipDays>".htmlspecialchars($this->skipDays)."</skipDays>\n";
968 }
969 $feed.= $this->_createAdditionalElements($this->additionalElements, " ");
970
971 for ($i=0;$i<count($this->items);$i++) {
972 $feed.= " <item>\n";
973 $feed.= " <title>".FeedCreator::iTrunc(htmlspecialchars(strip_tags($this->items[$i]->title)),100)."</title>\n";
974 $feed.= " <link>".htmlspecialchars($this->items[$i]->link)."</link>\n";
975 $feed.= " <description>".$this->items[$i]->getDescription()."</description>\n";
976
977 if ($this->items[$i]->author!="") {
978 $feed.= " <author>".htmlspecialchars($this->items[$i]->author)."</author>\n";
979 }
980 /*
981 // on hold
982 if ($this->items[$i]->source!="") {
983 $feed.= " <source>".htmlspecialchars($this->items[$i]->source)."</source>\n";
984 }
985 */
986 if ($this->items[$i]->category!="") {
987 $feed.= " <category>".htmlspecialchars($this->items[$i]->category)."</category>\n";
988 }
989 if ($this->items[$i]->comments!="") {
990 $feed.= " <comments>".htmlspecialchars($this->items[$i]->comments)."</comments>\n";
991 }
992 if ($this->items[$i]->date!="") {
993 $itemDate = new FeedDate($this->items[$i]->date);
994 $feed.= " <pubDate>".htmlspecialchars($itemDate->rfc822())."</pubDate>\n";
995 }
996 if ($this->items[$i]->guid!="") {
997 $feed.= " <guid>".htmlspecialchars($this->items[$i]->guid)."</guid>\n";
998 }
999 $feed.= $this->_createAdditionalElements($this->items[$i]->additionalElements, " ");
1000 $feed.= " </item>\n";
1001 }
1002 $feed.= " </channel>\n";
1003 $feed.= "</rss>\n";
1004 return $feed;
1005 }
1006 }
1007
1008
1009
1010 /**
1011 * RSSCreator20 is a FeedCreator that implements RDF Site Summary (RSS) 2.0.
1012 *
1013 * @see http://backend.userland.com/rss
1014 * @since 1.3
1015 * @author Kai Blankenhorn <kaib@bitfolge.de>
1016 */
1017 class RSSCreator20 extends RSSCreator091 {
1018
1019 function RSSCreator20() {
1020 parent::_setRSSVersion("2.0");
1021 }
1022
1023 }
1024
1025
1026 /**
1027 * PIECreator01 is a FeedCreator that implements the emerging PIE specification,
1028 * as in http://intertwingly.net/wiki/pie/Syntax.
1029 *
1030 * @deprecated
1031 * @since 1.3
1032 * @author Scott Reynen <scott@randomchaos.com> and Kai Blankenhorn <kaib@bitfolge.de>
1033 */
1034 class PIECreator01 extends FeedCreator {
1035
1036 function PIECreator01() {
1037 $this->encoding = "utf-8";
1038 }
1039
1040 function createFeed() {
1041 $feed = "<?xml version=\"1.0\" encoding=\"".$this->encoding."\"?>\n";
1042 $feed.= $this->_createStylesheetReferences();
1043 $feed.= "<feed version=\"0.1\" xmlns=\"http://example.com/newformat#\">\n";
1044 $feed.= " <title>".FeedCreator::iTrunc(htmlspecialchars($this->title),100)."</title>\n";
1045 $this->truncSize = 500;
1046 $feed.= " <subtitle>".$this->getDescription()."</subtitle>\n";
1047 $feed.= " <link>".$this->link."</link>\n";
1048 for ($i=0;$i<count($this->items);$i++) {
1049 $feed.= " <entry>\n";
1050 $feed.= " <title>".FeedCreator::iTrunc(htmlspecialchars(strip_tags($this->items[$i]->title)),100)."</title>\n";
1051 $feed.= " <link>".htmlspecialchars($this->items[$i]->link)."</link>\n";
1052 $itemDate = new FeedDate($this->items[$i]->date);
1053 $feed.= " <created>".htmlspecialchars($itemDate->iso8601())."</created>\n";
1054 $feed.= " <issued>".htmlspecialchars($itemDate->iso8601())."</issued>\n";
1055 $feed.= " <modified>".htmlspecialchars($itemDate->iso8601())."</modified>\n";
1056 $feed.= " <id>".htmlspecialchars($this->items[$i]->guid)."</id>\n";
1057 if ($this->items[$i]->author!="") {
1058 $feed.= " <author>\n";
1059 $feed.= " <name>".htmlspecialchars($this->items[$i]->author)."</name>\n";
1060 if ($this->items[$i]->authorEmail!="") {
1061 $feed.= " <email>".$this->items[$i]->authorEmail."</email>\n";
1062 }
1063 $feed.=" </author>\n";
1064 }
1065 $feed.= " <content type=\"text/html\" xml:lang=\"en-us\">\n";
1066 $feed.= " <div xmlns=\"http://www.w3.org/1999/xhtml\">".$this->items[$i]->getDescription()."</div>\n";
1067 $feed.= " </content>\n";
1068 $feed.= " </entry>\n";
1069 }
1070 $feed.= "</feed>\n";
1071 return $feed;
1072 }
1073 }
1074
1075
1076 /**
1077 * AtomCreator03 is a FeedCreator that implements the atom specification,
1078 * as in http://www.intertwingly.net/wiki/pie/FrontPage.
1079 * Please note that just by using AtomCreator03 you won't automatically
1080 * produce valid atom files. For example, you have to specify either an editor
1081 * for the feed or an author for every single feed item.
1082 *
1083 * Some elements have not been implemented yet. These are (incomplete list):
1084 * author URL, item author's email and URL, item contents, alternate links,
1085 * other link content types than text/html. Some of them may be created with
1086 * AtomCreator03::additionalElements.
1087 *
1088 * @see FeedCreator#additionalElements
1089 * @since 1.6
1090 * @author Kai Blankenhorn <kaib@bitfolge.de>, Scott Reynen <scott@randomchaos.com>
1091 */
1092 class AtomCreator03 extends FeedCreator {
1093
1094 function AtomCreator03() {
1095 $this->contentType = "application/atom+xml";
1096 $this->encoding = "utf-8";
1097 }
1098
1099 function createFeed() {
1100 $feed = "<?xml version=\"1.0\" encoding=\"".$this->encoding."\"?>\n";
1101 $feed.= $this->_createGeneratorComment();
1102 $feed.= $this->_createStylesheetReferences();
1103 $feed.= "<feed version=\"0.3\" xmlns=\"http://purl.org/atom/ns#\"";
1104 if ($this->language!="") {
1105 $feed.= " xml:lang=\"".$this->language."\"";
1106 }
1107 $feed.= ">\n";
1108 $feed.= " <title>".htmlspecialchars($this->title)."</title>\n";
1109 $feed.= " <tagline>".htmlspecialchars($this->description)."</tagline>\n";
1110 $feed.= " <link rel=\"alternate\" type=\"text/html\" href=\"".htmlspecialchars($this->link)."\"/>\n";
1111 $feed.= " <id>".htmlspecialchars($this->link)."</id>\n";
1112 $now = new FeedDate();
1113 $feed.= " <modified>".htmlspecialchars($now->iso8601())."</modified>\n";
1114 if ($this->editor!="") {
1115 $feed.= " <author>\n";
1116 $feed.= " <name>".$this->editor."</name>\n";
1117 if ($this->editorEmail!="") {
1118 $feed.= " <email>".$this->editorEmail."</email>\n";
1119 }
1120 $feed.= " </author>\n";
1121 }
1122 $feed.= " <generator>".FEEDCREATOR_VERSION."</generator>\n";
1123 $feed.= $this->_createAdditionalElements($this->additionalElements, " ");
1124 for ($i=0;$i<count($this->items);$i++) {
1125 $feed.= " <entry>\n";
1126 $feed.= " <title>".htmlspecialchars(strip_tags($this->items[$i]->title))."</title>\n";
1127 $feed.= " <link rel=\"alternate\" type=\"text/html\" href=\"".htmlspecialchars($this->items[$i]->link)."\"/>\n";
1128 if ($this->items[$i]->date=="") {
1129 $this->items[$i]->date = time();
1130 }
1131 $itemDate = new FeedDate($this->items[$i]->date);
1132 $feed.= " <created>".htmlspecialchars($itemDate->iso8601())."</created>\n";
1133 $feed.= " <issued>".htmlspecialchars($itemDate->iso8601())."</issued>\n";
1134 $feed.= " <modified>".htmlspecialchars($itemDate->iso8601())."</modified>\n";
1135 $feed.= " <id>".htmlspecialchars($this->items[$i]->link)."</id>\n";
1136 $feed.= $this->_createAdditionalElements($this->items[$i]->additionalElements, " ");
1137 if ($this->items[$i]->author!="") {
1138 $feed.= " <author>\n";
1139 $feed.= " <name>".htmlspecialchars($this->items[$i]->author)."</name>\n";
1140 $feed.= " </author>\n";
1141 }
1142 if ($this->items[$i]->description!="") {
1143 $feed.= " <summary>".htmlspecialchars($this->items[$i]->description)."</summary>\n";
1144 }
1145 $feed.= " </entry>\n";
1146 }
1147 $feed.= "</feed>\n";
1148 return $feed;
1149 }
1150 }
1151
1152
1153 /**
1154 * MBOXCreator is a FeedCreator that implements the mbox format
1155 * as described in http://www.qmail.org/man/man5/mbox.html
1156 *
1157 * @since 1.3
1158 * @author Kai Blankenhorn <kaib@bitfolge.de>
1159 */
1160 class MBOXCreator extends FeedCreator {
1161
1162 function MBOXCreator() {
1163 $this->contentType = "text/plain";
1164 $this->encoding = "ISO-8859-15";
1165 }
1166
1167 function qp_enc($input = "", $line_max = 76) {
1168 $hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
1169 $lines = preg_split("/(?:\r\n|\r|\n)/", $input);
1170 $eol = "\r\n";
1171 $escape = "=";
1172 $output = "";
1173 while( list(, $line) = each($lines) ) {
1174 //$line = rtrim($line); // remove trailing white space -> no =20\r\n necessary
1175 $linlen = strlen($line);
1176 $newline = "";
1177 for($i = 0; $i < $linlen; $i++) {
1178 $c = substr($line, $i, 1);
1179 $dec = ord($c);
1180 if ( ($dec == 32) && ($i == ($linlen - 1)) ) { // convert space at eol only
1181 $c = "=20";
1182 } elseif ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) ) { // always encode "\t", which is *not* required
1183 $h2 = floor($dec/16); $h1 = floor($dec%16);
1184 $c = $escape.$hex["$h2"].$hex["$h1"];
1185 }
1186 if ( (strlen($newline) + strlen($c)) >= $line_max ) { // CRLF is not counted
1187 $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay
1188 $newline = "";
1189 }
1190 $newline .= $c;
1191 } // end of for
1192 $output .= $newline.$eol;
1193 }
1194 return trim($output);
1195 }
1196
1197
1198 /**
1199 * Builds the MBOX contents.
1200 * @return string the feed's complete text
1201 */
1202 function createFeed() {
1203 for ($i=0;$i<count($this->items);$i++) {
1204 if ($this->items[$i]->author!="") {
1205 $from = $this->items[$i]->author;
1206 } else {
1207 $from = $this->title;
1208 }
1209 $itemDate = new FeedDate($this->items[$i]->date);
1210 $feed.= "From ".strtr(MBOXCreator::qp_enc($from)," ","_")." ".date("D M d H:i:s Y",$itemDate->unix())."\n";
1211 $feed.= "Content-Type: text/plain;\n";
1212 $feed.= " charset=\"".$this->encoding."\"\n";
1213 $feed.= "Content-Transfer-Encoding: quoted-printable\n";
1214 $feed.= "Content-Type: text/plain\n";
1215 $feed.= "From: \"".MBOXCreator::qp_enc($from)."\"\n";
1216 $feed.= "Date: ".$itemDate->rfc822()."\n";
1217 $feed.= "Subject: ".MBOXCreator::qp_enc(FeedCreator::iTrunc($this->items[$i]->title,100))."\n";
1218 $feed.= "\n";
1219 $body = chunk_split(MBOXCreator::qp_enc($this->items[$i]->description));
1220 $feed.= preg_replace("~\nFrom ([^\n]*)(\n?)~","\n>From $1$2\n",$body);
1221 $feed.= "\n";
1222 $feed.= "\n";
1223 }
1224 return $feed;
1225 }
1226
1227 /**
1228 * Generate a filename for the feed cache file. Overridden from FeedCreator to prevent XML data types.
1229 * @return string the feed cache filename
1230 * @since 1.4
1231 * @access private
1232 */
1233 function _generateFilename() {
1234 $fileInfo = pathinfo($_SERVER["PHP_SELF"]);
1235 return substr($fileInfo["basename"],0,-(strlen($fileInfo["extension"])+1)).".mbox";
1236 }
1237 }
1238
1239
1240 /**
1241 * OPMLCreator is a FeedCreator that implements OPML 1.0.
1242 *
1243 * @see http://opml.scripting.com/spec
1244 * @author Dirk Clemens, Kai Blankenhorn
1245 * @since 1.5
1246 */
1247 class OPMLCreator extends FeedCreator {
1248
1249 function OPMLCreator() {
1250 $this->encoding = "utf-8";
1251 }
1252
1253 function createFeed() {
1254 $feed = "<?xml version=\"1.0\" encoding=\"".$this->encoding."\"?>\n";
1255 $feed.= $this->_createGeneratorComment();
1256 $feed.= $this->_createStylesheetReferences();
1257 $feed.= "<opml xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n";
1258 $feed.= " <head>\n";
1259 $feed.= " <title>".htmlspecialchars($this->title)."</title>\n";
1260 if ($this->pubDate!="") {
1261 $date = new FeedDate($this->pubDate);
1262 $feed.= " <dateCreated>".$date->rfc822()."</dateCreated>\n";
1263 }
1264 if ($this->lastBuildDate!="") {
1265 $date = new FeedDate($this->lastBuildDate);
1266 $feed.= " <dateModified>".$date->rfc822()."</dateModified>\n";
1267 }
1268 if ($this->editor!="") {
1269 $feed.= " <ownerName>".$this->editor."</ownerName>\n";
1270 }
1271 if ($this->editorEmail!="") {
1272 $feed.= " <ownerEmail>".$this->editorEmail."</ownerEmail>\n";
1273 }
1274 $feed.= " </head>\n";
1275 $feed.= " <body>\n";
1276 for ($i=0;$i<count($this->items);$i++) {
1277 $feed.= " <outline type=\"rss\" ";
1278 $title = htmlspecialchars(strip_tags(strtr($this->items[$i]->title,"\n\r"," ")));
1279 $feed.= " title=\"".$title."\"";
1280 $feed.= " text=\"".$title."\"";
1281 //$feed.= " description=\"".htmlspecialchars($this->items[$i]->description)."\"";
1282 $feed.= " url=\"".htmlspecialchars($this->items[$i]->link)."\"";
1283 $feed.= "/>\n";
1284 }
1285 $feed.= " </body>\n";
1286 $feed.= "</opml>\n";
1287 return $feed;
1288 }
1289 }
1290
1291
1292
1293 /**
1294 * HTMLCreator is a FeedCreator that writes an HTML feed file to a specific
1295 * location, overriding the createFeed method of the parent FeedCreator.
1296 * The HTML produced can be included over http by scripting languages, or serve
1297 * as the source for an IFrame.
1298 * All output by this class is embedded in <div></div> tags to enable formatting
1299 * using CSS.
1300 *
1301 * @author Pascal Van Hecke
1302 * @since 1.7
1303 */
1304 class HTMLCreator extends FeedCreator {
1305
1306 var $contentType = "text/html";
1307
1308 /**
1309 * Contains HTML to be output at the start of the feed's html representation.
1310 */
1311 var $header;
1312
1313 /**
1314 * Contains HTML to be output at the end of the feed's html representation.
1315 */
1316 var $footer ;
1317
1318 /**
1319 * Contains HTML to be output between entries. A separator is only used in
1320 * case of multiple entries.
1321 */
1322 var $separator;
1323
1324 /**
1325 * Used to prefix the stylenames to make sure they are unique
1326 * and do not clash with stylenames on the users' page.
1327 */
1328 var $stylePrefix;
1329
1330 /**
1331 * Determines whether the links open in a new window or not.
1332 */
1333 var $openInNewWindow = true;
1334
1335 var $imageAlign ="right";
1336
1337 /**
1338 * In case of very simple output you may want to get rid of the style tags,
1339 * hence this variable. There's no equivalent on item level, but of course you can
1340 * add strings to it while iterating over the items ($this->stylelessOutput .= ...)
1341 * and when it is non-empty, ONLY the styleless output is printed, the rest is ignored
1342 * in the function createFeed().
1343 */
1344 var $stylelessOutput ="";
1345
1346 /**
1347 * Writes the HTML.
1348 * @return string the scripts's complete text
1349 */
1350 function createFeed() {
1351 // if there is styleless output, use the content of this variable and ignore the rest
1352 if ($this->stylelessOutput!="") {
1353 return $this->stylelessOutput;
1354 }
1355
1356 //if no stylePrefix is set, generate it yourself depending on the script name
1357 if ($this->stylePrefix=="") {
1358 $this->stylePrefix = str_replace(".", "_", $this->_generateFilename())."_";
1359 }
1360
1361 //set an openInNewWindow_token_to be inserted or not
1362 if ($this->openInNewWindow) {
1363 $targetInsert = " target='_blank'";
1364 }
1365
1366 // use this array to put the lines in and implode later with "document.write" javascript
1367 $feedArray = array();
1368 if ($this->image!=null) {
1369 $imageStr = "<a href='".$this->image->link."'".$targetInsert.">".
1370 "<img src='".$this->image->url."' border='0' alt='".
1371 FeedCreator::iTrunc(htmlspecialchars($this->image->title),100).
1372 "' align='".$this->imageAlign."' ";
1373 if ($this->image->width) {
1374 $imageStr .=" width='".$this->image->width. "' ";
1375 }
1376 if ($this->image->height) {
1377 $imageStr .=" height='".$this->image->height."' ";
1378 }
1379 $imageStr .="/></a>";
1380 $feedArray[] = $imageStr;
1381 }
1382
1383 if ($this->title) {
1384 $feedArray[] = "<div class='".$this->stylePrefix."title'><a href='".$this->link."' ".$targetInsert." class='".$this->stylePrefix."title'>".
1385 FeedCreator::iTrunc(htmlspecialchars($this->title),100)."</a></div>";
1386 }
1387 if ($this->getDescription()) {
1388 $feedArray[] = "<div class='".$this->stylePrefix."description'>".
1389 str_replace("]]>", "", str_replace("<![CDATA[", "", $this->getDescription())).
1390 "</div>";
1391 }
1392
1393 if ($this->header) {
1394 $feedArray[] = "<div class='".$this->stylePrefix."header'>".$this->header."</div>";
1395 }
1396
1397 for ($i=0;$i<count($this->items);$i++) {
1398 if ($this->separator and $i > 0) {
1399 $feedArray[] = "<div class='".$this->stylePrefix."separator'>".$this->separator."</div>";
1400 }
1401
1402 if ($this->items[$i]->title) {
1403 if ($this->items[$i]->link) {
1404 $feedArray[] =
1405 "<div class='".$this->stylePrefix."item_title'><a href='".$this->items[$i]->link."' class='".$this->stylePrefix.
1406 "item_title'".$targetInsert.">".FeedCreator::iTrunc(htmlspecialchars(strip_tags($this->items[$i]->title)),100).
1407 "</a></div>";
1408 } else {
1409 $feedArray[] =
1410 "<div class='".$this->stylePrefix."item_title'>".
1411 FeedCreator::iTrunc(htmlspecialchars(strip_tags($this->items[$i]->title)),100).
1412 "</div>";
1413 }
1414 }
1415 if ($this->items[$i]->getDescription()) {
1416 $feedArray[] =
1417 "<div class='".$this->stylePrefix."item_description'>".
1418 str_replace("]]>", "", str_replace("<![CDATA[", "", $this->items[$i]->getDescription())).
1419 "</div>";
1420 }
1421 }
1422 if ($this->footer) {
1423 $feedArray[] = "<div class='".$this->stylePrefix."footer'>".$this->footer."</div>";
1424 }
1425
1426 $feed= "".join($feedArray, "\r\n");
1427 return $feed;
1428 }
1429
1430 /**
1431 * Overrrides parent to produce .html extensions
1432 *
1433 * @return string the feed cache filename
1434 * @since 1.4
1435 * @access private
1436 */
1437 function _generateFilename() {
1438 $fileInfo = pathinfo($_SERVER["PHP_SELF"]);
1439 return substr($fileInfo["basename"],0,-(strlen($fileInfo["extension"])+1)).".html";
1440 }
1441 }
1442
1443
1444 /**
1445 * JSCreator is a class that writes a js file to a specific
1446 * location, overriding the createFeed method of the parent HTMLCreator.
1447 *
1448 * @author Pascal Van Hecke
1449 */
1450 class JSCreator extends HTMLCreator {
1451 var $contentType = "text/javascript";
1452
1453 /**
1454 * writes the javascript
1455 * @return string the scripts's complete text
1456 */
1457 function createFeed()
1458 {
1459 $feed = parent::createFeed();
1460 $feedArray = explode("\n",$feed);
1461
1462 $jsFeed = "";
1463 foreach ($feedArray as $value) {
1464 $jsFeed .= "document.write('".trim(addslashes($value))."');\n";
1465 }
1466 return $jsFeed;
1467 }
1468
1469 /**
1470 * Overrrides parent to produce .js extensions
1471 *
1472 * @return string the feed cache filename
1473 * @since 1.4
1474 * @access private
1475 */
1476 function _generateFilename() {
1477 $fileInfo = pathinfo($_SERVER["PHP_SELF"]);
1478 return substr($fileInfo["basename"],0,-(strlen($fileInfo["extension"])+1)).".js";
1479 }
1480
1481 }
1482
1483
1484
1485 /*** TEST SCRIPT *********************************************************
1486
1487 //include("feedcreator.class.php");
1488
1489 $rss = new UniversalFeedCreator();
1490 $rss->useCached();
1491 $rss->title = "PHP news";
1492 $rss->description = "daily news from the PHP scripting world";
1493
1494 //optional
1495 //$rss->descriptionTruncSize = 500;
1496 //$rss->descriptionHtmlSyndicated = true;
1497 //$rss->xslStyleSheet = "http://feedster.com/rss20.xsl";
1498
1499 $rss->link = "http://www.dailyphp.net/news";
1500 $rss->feedURL = "http://www.dailyphp.net/".$PHP_SELF;
1501
1502 $image = new FeedImage();
1503 $image->title = "dailyphp.net logo";
1504 $image->url = "http://www.dailyphp.net/images/logo.gif";
1505 $image->link = "http://www.dailyphp.net";
1506 $image->description = "Feed provided by dailyphp.net. Click to visit.";
1507
1508 //optional
1509 $image->descriptionTruncSize = 500;
1510 $image->descriptionHtmlSyndicated = true;
1511
1512 $rss->image = $image;
1513
1514 // get your news items from somewhere, e.g. your database:
1515 //mysql_select_db($dbHost, $dbUser, $dbPass);
1516 //$res = mysql_query("SELECT * FROM news ORDER BY newsdate DESC");
1517 //while ($data = mysql_fetch_object($res)) {
1518 $item = new FeedItem();
1519 $item->title = "This is an the test title of an item";
1520 $item->link = "http://localhost/item/";
1521 $item->description = "<b>description in </b><br/>HTML";
1522
1523 //optional
1524 //item->descriptionTruncSize = 500;
1525 $item->descriptionHtmlSyndicated = true;
1526
1527 $item->date = time();
1528 $item->source = "http://www.dailyphp.net";
1529 $item->author = "John Doe";
1530
1531 $rss->addItem($item);
1532 //}
1533
1534 // valid format strings are: RSS0.91, RSS1.0, RSS2.0, PIE0.1, MBOX, OPML, ATOM0.3, HTML, JS
1535 echo $rss->saveFeed("RSS0.91", "feed.xml");
1536
1537
1538
1539 ***************************************************************************/
1540
1541 ?>