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 */
10abstract 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