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