1 <?php
2 
3 /**
4  * FeedCreator is the abstract base implementation for concrete
5  * implementations that implement a specific format of syndication.
6  *
7  * @author  Kai Blankenhorn <kaib@bitfolge.de>
8  * @since   1.4
9  */
10 abstract class FeedCreator extends HtmlDescribable
11 {
12 
13     /**
14      * Mandatory attributes of a feed.
15      */
16     public $title, $description, $link;
17     public $format = 'BASE';
18 
19     /**
20      * Optional attributes of a feed.
21      */
22     public $syndicationURL, $image, $language, $copyright, $pubDate, $lastBuildDate, $editor, $editorEmail, $webmaster, $category, $docs, $ttl, $rating, $skipHours, $skipDays;
23 
24     /**
25      * The url of the external xsl stylesheet used to format the naked rss feed.
26      * Ignored in the output when empty.
27      */
28     public $xslStyleSheet = "";
29 
30     public $cssStyleSheet = "";
31 
32     /** @var FeedItem[] */
33     public $items = Array();
34 
35     /**
36      * Generator string
37      */
38     public $generator = "info@mypapit.net";
39 
40     /**
41      * This feed's MIME content type.
42      *
43      * @since  1.4
44      * @access private
45      */
46     protected $contentType = "application/xml";
47 
48     /**
49      * This feed's character encoding.
50      *
51      * @since 1.6.1
52      */
53     protected $encoding = "UTF-8"; //"ISO-8859-1";
54 
55     protected $_timeout;  # lib/Creator/FeedCreator.php  line 238
56 
57     /**
58      * Any additional elements to include as an associated array. All $key => $value pairs
59      * will be included unencoded in the feed in the form
60      *     <$key>$value</$key>
61      * Again: No encoding will be used! This means you can invalidate or enhance the feed
62      * if $value contains markup. This may be abused to embed tags not implemented by
63      * the FeedCreator class used.
64      */
65     public $additionalElements = Array();
66 
67     /**
68      * Adds a FeedItem to the feed.
69      *
70      * @param FeedItem $item The FeedItem to add to the feed.
71      */
72     public function addItem($item)
73     {
74         $this->items[] = $item;
75     }
76 
77     /**
78      * Get the version string for the generator
79      *
80      * @return string
81      */
82     public function version()
83     {
84         return FEEDCREATOR_VERSION." (".$this->generator.")";
85     }
86 
87     /**
88      * Truncates a string to a certain length at the most sensible point.
89      * First, if there's a '.' character near the end of the string, the string is truncated after this character.
90      * If there is no '.', the string is truncated after the last ' ' character.
91      * If the string is truncated, " ..." is appended.
92      * If the string is already shorter than $length, it is returned unchanged.
93      *
94      * @param string $string A string to be truncated.
95      * @param int $length    the maximum length the string should be truncated to
96      * @return string the truncated string
97      */
98     public static function iTrunc($string, $length)
99     {
100         if (strlen($string) <= $length) {
101             return $string;
102         }
103 
104         $pos = strrpos($string, ".");
105         if ($pos >= $length - 4) {
106             $string = substr($string, 0, $length - 4);
107             $pos = strrpos($string, ".");
108         }
109         if ($pos >= $length * 0.4) {
110             return substr($string, 0, $pos + 1)." ...";
111         }
112 
113         $pos = strrpos($string, " ");
114         if ($pos >= $length - 4) {
115             $string = substr($string, 0, $length - 4);
116             $pos = strrpos($string, " ");
117         }
118         if ($pos >= $length * 0.4) {
119             return substr($string, 0, $pos)." ...";
120         }
121 
122         return substr($string, 0, $length - 4)." ...";
123 
124     }
125 
126     /**
127      * Creates a comment indicating the generator of this feed.
128      * The format of this comment seems to be recognized by
129      * Syndic8.com.
130      */
131     protected function _createGeneratorComment()
132     {
133         return "<!-- generator=\"".FEEDCREATOR_VERSION."\" -->\n";
134     }
135 
136     /**
137      * Creates a string containing all additional elements specified in
138      * $additionalElements.
139      *
140      * @param array $elements      an associative array containing key => value pairs
141      * @param string $indentString a string that will be inserted before every generated line
142      * @return string the XML tags corresponding to $additionalElements
143      */
144     protected function _createAdditionalElements($elements, $indentString = "")
145     {
146         $ae = "";
147         if (is_array($elements)) {
148             foreach ($elements AS $key => $value) {
149                 $ae .= $indentString."<$key>$value</$key>\n";
150             }
151         }
152 
153         return $ae;
154     }
155 
156     protected function _createStylesheetReferences()
157     {
158         $xml = "";
159         if (!empty($this->cssStyleSheet)) {
160             $xml .= "<?xml-stylesheet href=\"".$this->cssStyleSheet."\" type=\"text/css\"?>\n";
161         }
162         if (!empty($this->xslStyleSheet)) {
163             $xml .= "<?xml-stylesheet href=\"".$this->xslStyleSheet."\" type=\"text/xsl\"?>\n";
164         }
165 
166         return $xml;
167     }
168 
169     /**
170      * Builds the feed's text.
171      *
172      * @return string the feed's complete text
173      */
174     abstract public function createFeed();
175 
176     /**
177      * Generate a filename for the feed cache file. The result will be $_SERVER["SCRIPT_NAME"] with the extension changed
178      * to .xml. For example: echo $_SERVER["SCRIPT_NAME"]."\n"; echo FeedCreator::_generateFilename(); would produce:
179      * /rss/latestnews.php
180      * latestnews.xml
181      *
182      * @return string the feed cache filename
183      * @since  1.4
184      * @access private
185      */
186     protected function _generateFilename()
187     {
188         $fileInfo = pathinfo($_SERVER["SCRIPT_NAME"]);
189 
190         return substr($fileInfo["basename"], 0, -(strlen($fileInfo["extension"]) + 1)).".xml";
191     }
192 
193     /**
194      * Send given file to Browser
195      *
196      * @since 1.4
197      * @param string $filename
198      */
199     protected function _redirect($filename)
200     {
201         // attention, heavily-commented-out-area
202 
203         // maybe use this in addition to file time checking
204         //header("Expires: ".date("r",time()+$this->_timeout));
205 
206         /* no caching at all, doesn't seem to work as good:
207          header("Cache-Control: no-cache");
208         header("Pragma: no-cache");
209         */
210 
211         // HTTP redirect, some feed readers' simple HTTP implementations don't follow it
212         //header("Location: ".$filename);
213 
214         header("Content-Type: ".$this->contentType."; charset=".$this->encoding."; filename=".basename($filename));
215         if (preg_match('/\.(kml|gpx)$/', $filename)) {
216             header("Content-Disposition: attachment; filename=".basename($filename));
217         } else {
218             header("Content-Disposition: inline; filename=".basename($filename));
219         }
220         readfile($filename);
221         exit();
222     }
223 
224     /**
225      * Turns on caching and checks if there is a recent version of this feed in the cache.
226      * If there is, an HTTP redirect header is sent.
227      * To effectively use caching, you should create the FeedCreator object and call this method
228      * before anything else, especially before you do the time consuming task to build the feed
229      * (web fetching, for example).
230      *
231      * @since 1.4
232      * @param string $filename optional    the filename where a recent version of the feed is saved. If not specified,
233      *                         the filename is $_SERVER["SCRIPT_NAME"] with the extension changed to .xml (see
234      *                         _generateFilename()).
235      * @param int $timeout     optional    the timeout in seconds before a cached version is refreshed (defaults to
236      *                         3600 = 1 hour)
237      */
238     public function useCached($filename = "", $timeout = 3600)
239     {
240         $this->_timeout = $timeout;
241         if ($filename == "") {
242             $filename = $this->_generateFilename();
243         }
244         if (file_exists($filename) AND (time() - filemtime($filename) < $timeout)) {
245             $this->_redirect($filename);
246         }
247     }
248 
249     /**
250      * Saves this feed as a file on the local disk. After the file is saved, a redirect
251      * header may be sent to redirect the user to the newly created file.
252      *
253      * @since 1.4
254      * @param string $filename      optional    the filename where a recent version of the feed is saved. If not
255      *                              specified, the filename is $_SERVER["SCRIPT_NAME"] with the extension changed to .xml
256      *                              (see _generateFilename()).
257      * @param bool $displayContents optional    send an HTTP redirect header or not. If true, the user will be
258      *                              automatically redirected to the created file.
259      */
260     public function saveFeed($filename = "", $displayContents = true)
261     {
262         if ($filename == "") {
263             $filename = $this->_generateFilename();
264         }
265         $feedFile = fopen($filename, "w+");
266         if ($feedFile) {
267             fputs($feedFile, $this->createFeed());
268             fclose($feedFile);
269             if ($displayContents) {
270                 $this->_redirect($filename);
271             }
272         } else {
273             echo "<br /><b>Error creating feed file, please check write permissions.</b><br />";
274         }
275     }
276 }
277