1 | = sfFeed2 plugin = |
---|
2 | |
---|
3 | The `sfFeed2Plugin` offers an object interface for feeds and feed items, feed input methods using a web feed or an array of objects as source, and feed output methods for displaying items on a page and serving feeds through a symfony application. |
---|
4 | |
---|
5 | == Possible uses == |
---|
6 | |
---|
7 | * serving a RSS/Atom feed based on model objects |
---|
8 | * Using web feeds as data source |
---|
9 | * Feed aggregator |
---|
10 | |
---|
11 | As compared with the `sfFeedPlugin`, this plugin has a cleaner code separation in classes and offers more features. The syntax differs, but many classes have the same names, therefore the two plugins are not compatible. |
---|
12 | |
---|
13 | == Contents == |
---|
14 | |
---|
15 | This plugin contains four data structure classes: |
---|
16 | |
---|
17 | * `sfFeed` |
---|
18 | * `sfFeedItem` |
---|
19 | * `sfFeedImage` |
---|
20 | * `sfFeedEnclosure` |
---|
21 | |
---|
22 | It also contains specific classes containing specific input/output methods based on specific feed formats: |
---|
23 | |
---|
24 | * `sfAtom1Feed` |
---|
25 | * `sfRssFeed` |
---|
26 | * `sfRss10Feed` |
---|
27 | * `sfRss201Feed` |
---|
28 | * `sfRss091Feed` |
---|
29 | |
---|
30 | Last but not least, the most important (and smart) class is the feed manager, which contains only static methods: |
---|
31 | |
---|
32 | * `sfFeedPeer` |
---|
33 | |
---|
34 | Unit tests are available in the SVN repository. |
---|
35 | |
---|
36 | == Installation == |
---|
37 | |
---|
38 | * Install the plugin |
---|
39 | {{{ |
---|
40 | $ symfony plugin-install http://plugins.symfony-project.com/sfFeed2Plugin |
---|
41 | }}} |
---|
42 | |
---|
43 | * Alternatively, if you don't have PEAR installed, you can download the latest package attached to this plugin's wiki page and extract it under your project's `plugins/` directory |
---|
44 | |
---|
45 | * Clear the cache to enable the autoloading to find the new class |
---|
46 | {{{ |
---|
47 | $ symfony cc |
---|
48 | }}} |
---|
49 | |
---|
50 | == Tutorials == |
---|
51 | |
---|
52 | === Building a feed from an array of objects === |
---|
53 | |
---|
54 | ==== Example data ==== |
---|
55 | |
---|
56 | Let's take an example of a simple blog application with a `Post` and an `Author` table: |
---|
57 | |
---|
58 | ||''Post'' || ''Author'' |
---|
59 | ||id || id |
---|
60 | ||author_id || first_name |
---|
61 | ||title || last_name |
---|
62 | ||description || email |
---|
63 | ||body || |
---|
64 | ||created_at || |
---|
65 | |
---|
66 | The `Post` class is extended by a `getStrippedTitle()` method that transforms the title into a string that can be used in an URI, replacing spaces by dashes, upper case by lower case, and removing all special characters: |
---|
67 | |
---|
68 | {{{ |
---|
69 | public function getStrippedTitle() |
---|
70 | { |
---|
71 | $text = strtolower($this->getTitle()); |
---|
72 | |
---|
73 | // strip all non word chars |
---|
74 | $text = preg_replace('/\W/', ' ', $text); |
---|
75 | // replace all white space sections with a dash |
---|
76 | $text = preg_replace('/\ +/', '-', $text); |
---|
77 | // trim dashes |
---|
78 | $text = preg_replace('/\-$/', '', $text); |
---|
79 | $text = preg_replace('/^\-/', '', $text); |
---|
80 | |
---|
81 | return $text; |
---|
82 | } |
---|
83 | }}} |
---|
84 | |
---|
85 | The `Author` class is extended by a custom `->getName()` method as follows: |
---|
86 | |
---|
87 | {{{ |
---|
88 | public function getName() |
---|
89 | { |
---|
90 | return $this->getFirstName().' '.$this->getLastName(); |
---|
91 | } |
---|
92 | }}} |
---|
93 | |
---|
94 | If you need more details about the way to extend the model, refer to [http://www.symfony-project.com/book/trunk/08-Inside-the-Model-Layer#Extending%20the%20Model Chapter 8]. |
---|
95 | |
---|
96 | The `routing.yml` contains the following rule: |
---|
97 | |
---|
98 | {{{ |
---|
99 | post: |
---|
100 | url: /permalink/:stripped_title |
---|
101 | param: { module: post, action: read } |
---|
102 | }}} |
---|
103 | |
---|
104 | If you need more details about the routing system, refer to [http://www.symfony-project.com/book/trunk/09-Links-and-the-Routing-System Chapter 9]. |
---|
105 | |
---|
106 | A special `feed` module is built for the occasion, and all the actions and templates will be placed in it. |
---|
107 | |
---|
108 | {{{$ symfony init-module myapp feed}}} |
---|
109 | |
---|
110 | ==== Expected result ==== |
---|
111 | |
---|
112 | The feed action has to output an [http://en.wikipedia.org/wiki/Atom_%28standard%29 Atom] feed. As a reminder of all the information that need to be included in an Atom feed, here is an example: |
---|
113 | |
---|
114 | {{{ |
---|
115 | <?xml version="1.0" encoding="utf-8"?> |
---|
116 | <feed xmlns="http://www.w3.org/2005/Atom"> |
---|
117 | |
---|
118 | <title>The mouse blog</title> |
---|
119 | <link href="http://www.myblog.com/" /> |
---|
120 | <updated>2005-12-11T16:23:51Z</updated> |
---|
121 | <author> |
---|
122 | <name>Peter Clive</name> |
---|
123 | <author_email>pclive@myblog.com</author_email> |
---|
124 | </author> |
---|
125 | <id>4543D55FF756G734</id> |
---|
126 | <icon>http://www.myblog.com/favicon.ico</icon> |
---|
127 | <entry> |
---|
128 | <title>I love mice</title> |
---|
129 | <link href="http://www.myblog.com/permalink/i-love-mice" /> |
---|
130 | <id>i-love-mice</id> |
---|
131 | <author> |
---|
132 | <name>Peter Clive</name> |
---|
133 | <author_email>pclive@myblog.com</author_email> |
---|
134 | </author> |
---|
135 | <updated>2005-12-11T16:23:51Z</updated> |
---|
136 | <summary>Ever since I bought my first mouse, I can't live without one.</summary> |
---|
137 | </entry> |
---|
138 | |
---|
139 | <entry> |
---|
140 | <title>A mouse is better than a fish</title> |
---|
141 | <link href="http://www.myblog.com/permalink/a-mouse-is-better-than-a-fish" /> |
---|
142 | <id>a-mouse-is-better-than-a-fish</id> |
---|
143 | <author> |
---|
144 | <name>Bob Walter</name> |
---|
145 | <author_email>bwalter@myblog.com</author_email> |
---|
146 | </author> |
---|
147 | <updated>2005-12-09T09:11:42Z</updated> |
---|
148 | <summary>I had a fish for four years, and now I'm sick. They smell.</summary> |
---|
149 | </entry> |
---|
150 | |
---|
151 | </feed> |
---|
152 | }}} |
---|
153 | |
---|
154 | ==== Using the creators and setters ==== |
---|
155 | |
---|
156 | To build the feed, you need to initialize it with a certain format and options, and to add feed items based on the objects resulting from a database request. With the syntax of the `sfFeed` and `sfFeedItem` class, that would give: |
---|
157 | |
---|
158 | {{{ |
---|
159 | public function executeLastPosts() |
---|
160 | { |
---|
161 | $feed = new sfAtom1Feed(); |
---|
162 | |
---|
163 | $feed->setTitle('The mouse blog'); |
---|
164 | $feed->setLink('http://www.myblog.com/'); |
---|
165 | $feed->setAuthorEmail('pclive@myblog.com'); |
---|
166 | $feed->setAuthorName('Peter Clive'); |
---|
167 | |
---|
168 | $feedImage = new sfFeedImage(); |
---|
169 | $feedImage->setFavicon('http://www.myblog.com/favicon.ico'); |
---|
170 | $feed->setImage($feedImage); |
---|
171 | |
---|
172 | $c = new Criteria; |
---|
173 | $c->addDescendingOrderByColumn(PostPeer::CREATED_AT); |
---|
174 | $c->setLimit(5); |
---|
175 | $posts = PostPeer::doSelect($c); |
---|
176 | |
---|
177 | foreach ($posts as $post) |
---|
178 | { |
---|
179 | $item = new sfFeedItem(); |
---|
180 | $item->setTitle($post->getTitle()); |
---|
181 | $item->setLink('@permalink?stripped_title='.$post->getStrippedTitle()); |
---|
182 | $item->setAuthorName($post->getAuthor()->getName()); |
---|
183 | $item->setAuthorEmail($post->getAuthor()->getEmail()); |
---|
184 | $item->setPubdate($post->getCreatedAt('U')); |
---|
185 | $item->setUniqueId($post->getStrippedTitle()); |
---|
186 | $item->setDescription($post->getDescription()); |
---|
187 | |
---|
188 | $feed->addItem($item); |
---|
189 | } |
---|
190 | |
---|
191 | $this->feed = $feed; |
---|
192 | } |
---|
193 | }}} |
---|
194 | |
---|
195 | At the end of the action, the `$feed` variable contains a `sfAtom1Feed` object which includes several `sfFeedItem` objects. To transform the object into an actual Atom feed, the `lastPostsSuccess.php` template simply contains: |
---|
196 | |
---|
197 | {{{ |
---|
198 | <?php decorate_with(false) ?> |
---|
199 | <?php echo $feed->asXml() ?> |
---|
200 | }}} |
---|
201 | |
---|
202 | The content type is automatically set by the `asXML()` method, depending on the feed format (Atom1 in this example). |
---|
203 | |
---|
204 | When called from a feed aggregator, the result of the action is now exactly the Atom feed described above: |
---|
205 | |
---|
206 | {{{http://www.myblog.com/feed/lastPosts}}} |
---|
207 | |
---|
208 | ==== Using the `initialize()` method ==== |
---|
209 | |
---|
210 | The use of all the setters for the feed and item construction can be a little annoying, since there is a lot of information to define. Both the `sfFeed` and the `sfFeedItem` classes provide an `initialize()` method that uses an associative array for a shorter syntax: |
---|
211 | |
---|
212 | {{{ |
---|
213 | public function executeLastPosts() |
---|
214 | { |
---|
215 | $feed = new sfAtom1Feed(); |
---|
216 | |
---|
217 | $feed->initialize(array( |
---|
218 | 'title' => 'The mouse blog', |
---|
219 | 'link' => 'http://www.myblog.com/', |
---|
220 | 'authorEmail' => 'pclive@myblog.com', |
---|
221 | 'authorName' => 'Peter Clive' |
---|
222 | )); |
---|
223 | |
---|
224 | $c = new Criteria; |
---|
225 | $c->addDescendingOrderByColumn(PostPeer::CREATED_AT); |
---|
226 | $c->setLimit(5); |
---|
227 | $posts = PostPeer::doSelect($c); |
---|
228 | |
---|
229 | foreach ($posts as $post) |
---|
230 | { |
---|
231 | $item = new sfFeedItem(); |
---|
232 | $item->initialize(array( |
---|
233 | 'title' => $post->getTitle(), |
---|
234 | 'link' => '@permalink?stripped_title='.$post->getStrippedTitle(), |
---|
235 | 'authorName' => $post->getAuthor()->getName(), |
---|
236 | 'authorEmail' => $post->getAuthor()->getEmail(), |
---|
237 | 'pubDate' => $post->getCreatedAt(), |
---|
238 | 'uniqueId' => $post->getStrippedTitle(), |
---|
239 | 'description' => $post->getDescription(), |
---|
240 | )); |
---|
241 | |
---|
242 | $feed->addItem($item); |
---|
243 | } |
---|
244 | |
---|
245 | $this->feed = $feed; |
---|
246 | } |
---|
247 | }}} |
---|
248 | |
---|
249 | It has exactly the same effect as the previous listing, but the syntax is clearer. |
---|
250 | |
---|
251 | ==== Using the object converter ==== |
---|
252 | |
---|
253 | As the method names that are used to build a feed item based on an object are more or less always the same, the `sfFeedPeer` can try to do it on its own: |
---|
254 | |
---|
255 | {{{ |
---|
256 | public function executeLastPosts() |
---|
257 | { |
---|
258 | $feed = new sfAtom1Feed(); |
---|
259 | |
---|
260 | $feed->initialize(array( |
---|
261 | 'title' => 'The mouse blog', |
---|
262 | 'link' => 'http://www.myblog.com/', |
---|
263 | 'authorEmail' => 'pclive@myblog.com', |
---|
264 | 'authorName' => 'Peter Clive' |
---|
265 | )); |
---|
266 | |
---|
267 | $c = new Criteria; |
---|
268 | $c->addDescendingOrderByColumn(PostPeer::CREATED_AT); |
---|
269 | $c->setLimit(5); |
---|
270 | $posts = PostPeer::doSelect($c); |
---|
271 | |
---|
272 | $postItems = sfFeedPeer::convertObjectsToItems($posts, array('routeName' => '@permalink')) |
---|
273 | $feed->addItems($postItems); |
---|
274 | |
---|
275 | $this->feed = $feed; |
---|
276 | } |
---|
277 | }}} |
---|
278 | |
---|
279 | The rules governing the `sfFeedPeer::convertObjectsToItems` algorithm are as follows: |
---|
280 | |
---|
281 | * To set the item `title`, it looks for a `getFeedTitle()`, a `getTitle()`, a `getName()` or a `__toString()` method. |
---|
282 | |
---|
283 | In the example, the `Post` object has a `getName()` method. |
---|
284 | |
---|
285 | * To set the `link`, it uses the `routeName` option if defined in the second argument of the method call. It is supposed to be a route name for the feed items. If present, the method looks in the route url for parameters for which it could find a getter in the object methods. If not, it looks for a `getFeedLink()`, `getLink()`, `getUrl()` method in the object. |
---|
286 | |
---|
287 | In the example, the route name given as parameter is `@permalink`. The routing rule contains a `:stripped_title` parameter and the `Post` object has a `getStrippedTitle()` method, so the `convertObjectsToItems` method is able to define the URIs to link to. |
---|
288 | |
---|
289 | * To set the author's email, it looks for a `getFeedAuthorEmail` or a `getAuthorEmail`. If there is no such method, it looks for a `getAuthor()`, `getUser()` or `getPerson()` method. If the result returned is an object, it looks in this object for a `getEmail` or a `getMail` method. |
---|
290 | |
---|
291 | In the example, the `Post` object has a `getAuthor()`, and the `Author` object has a `getName()`. The same kind of rules is used for the author's name and URL. |
---|
292 | |
---|
293 | * To set the publication date, it looks for a `getFeedPubdate()`, `getPubdate()`, `getCreatedAt()` or a `getDate()` method. |
---|
294 | |
---|
295 | In the example, the `Post` object has a `getCreatedAt` |
---|
296 | |
---|
297 | The same goes for the other possible fields of an Atom feed (including the categories, the summary, the unique id, etc.), and you are advised to [browse the source of the `sfFeed` class](http://www.symfony-project.com/trac/browser/plugins/sfFeed2Plugin/lib) to discover all the deduction algorithms. |
---|
298 | |
---|
299 | All in all, the way the accessors of the `Post` and `Author` objects are built allow the built-in algorithm of the `convertObjectsToItems` method to work, and the creation of the feed to be more simple. |
---|
300 | |
---|
301 | ==== Defining custom values for the feed ==== |
---|
302 | |
---|
303 | In the list of rules presented above, you can see that the first method name that the `sfFeed` object looks for is always a `getFeedXXX()`. This allows you to specify a custom value for each of the fields of a feed item by simply extended the model. |
---|
304 | |
---|
305 | For instance, if you don't want the author's email to be published in the feed, just add the following `getFeedAuthorEmail()` method to the `Post` object: |
---|
306 | |
---|
307 | {{{ |
---|
308 | public function getFeedAuthorEmail() |
---|
309 | { |
---|
310 | return ''; |
---|
311 | } |
---|
312 | }}} |
---|
313 | |
---|
314 | This method will be found before the `getAuthor()` method, and the feed will not disclose the publishers' email addresses. |
---|
315 | |
---|
316 | The other way to use a specific method for an item property is to pass a `methods` option to the `convertObjectsToItems` method: an associative array associating item properties with object methods. So, for instance, to tell the converter to use nothing for the feed author email and the `getUserFirstName()` method for the author name, you could write: |
---|
317 | |
---|
318 | {{{ |
---|
319 | $postItems = sfFeedPeer::convertObjectsToItems($posts, array( |
---|
320 | 'routeName' => '@permalink', |
---|
321 | 'methods' => array( |
---|
322 | 'authorEmail' => '', |
---|
323 | 'authorName' => 'getUserFirstName' |
---|
324 | ) |
---|
325 | )); |
---|
326 | }}} |
---|
327 | |
---|
328 | You can also pass some arguments to the user defined method by passing an array composed of the method name and an array of arguments: |
---|
329 | |
---|
330 | {{{ |
---|
331 | $postItems = sfFeedPeer::convertObjectsToItems($posts, array( |
---|
332 | 'routeName' => '@permalink', |
---|
333 | 'methods' => array( |
---|
334 | 'authorEmail' => '', |
---|
335 | 'authorName' => 'getUserFirstName', |
---|
336 | 'pubdate' => array('getPublishedAtDate', array('U')), |
---|
337 | ) |
---|
338 | )); |
---|
339 | }}} |
---|
340 | |
---|
341 | ==== Using the sfFeedPeer static methods ==== |
---|
342 | |
---|
343 | The `sfFeedPeer` class offer helper methods that facilitate the creation and population of feed items. |
---|
344 | |
---|
345 | When the feed format is determined at runtime, create feed objects using the `sfFeedPeer::newInstance()` method, which is a factory, rather that using the `new` command: |
---|
346 | |
---|
347 | {{{ |
---|
348 | $feed = sfFeedPeer::newInstance('atom1'); |
---|
349 | // same as |
---|
350 | $feed = new sfAtom1Feed(); |
---|
351 | }}} |
---|
352 | |
---|
353 | The steps described in the `executeLastPosts` listing occur in almost every feed construction process, so the `sfFeedPeer` can reduce the code above to a simpler: |
---|
354 | |
---|
355 | {{{ |
---|
356 | public function executeLastPosts() |
---|
357 | { |
---|
358 | $c = new Criteria; |
---|
359 | $c->addDescendingOrderByColumn(PostPeer::CREATED_AT); |
---|
360 | $c->setLimit(5); |
---|
361 | $posts = PostPeer::doSelect($c); |
---|
362 | |
---|
363 | $this->feed = sfFeedPeer::createFromObjects( |
---|
364 | $posts, |
---|
365 | array( |
---|
366 | 'format' => 'atom1', |
---|
367 | 'title' => 'The mouse blog', |
---|
368 | 'link' => 'http://www.myblog.com/', |
---|
369 | 'authorEmail' => 'pclive@myblog.com', |
---|
370 | 'authorName' => 'Peter Clive' |
---|
371 | 'routeName' => '@permalink', |
---|
372 | 'methods' => array('authorEmail' => '', 'authorName' => 'getUserFirstName') |
---|
373 | ) |
---|
374 | ); |
---|
375 | } |
---|
376 | }}} |
---|
377 | |
---|
378 | ==== Using other formats ==== |
---|
379 | |
---|
380 | The methods described below can be transposed to build other RSS feeds. Simply change the parameter given to the feed factory: |
---|
381 | |
---|
382 | {{{ |
---|
383 | // Atom 1 |
---|
384 | $feed = sfFeedPeer::newInstance('atom1'); |
---|
385 | // RSS 1.0 RDF Site Summary |
---|
386 | $feed = sfFeedPeer::newInstance('rss10'); |
---|
387 | // RSS 0.91 Userland |
---|
388 | $feed = sfFeedPeer::newInstance('rss091'); |
---|
389 | // RSS 2.01 rev6 |
---|
390 | $feed = sfFeedPeer::newInstance('rss201'); |
---|
391 | }}} |
---|
392 | |
---|
393 | === Fetching a feed from the web and displaying it === |
---|
394 | |
---|
395 | You may want to display the latest posts of the symfony users group in your application. The steps to retrieve this information are to fetche a feed from the Internet, create an empty feed object, and populate it with the items of the feed. You can use the `fromXML()` method and the [http://www.symfony-project.com/trac/wiki/sfWebBrowserPlugin sfWebBrowserPlugin] for that: |
---|
396 | |
---|
397 | {{{ |
---|
398 | public function executeLastPosts() |
---|
399 | { |
---|
400 | $uri = 'http://groups.google.com/group/symfony-users/feed/rss_v2_0_msgs.xml'; |
---|
401 | |
---|
402 | $browser = new sfWebBrowser(array( |
---|
403 | 'user_agent' => 'sfFeedReader/0.9', |
---|
404 | 'timeout' => 5 |
---|
405 | )); |
---|
406 | $feedString = $browser->get($uri)->getResponseText(); |
---|
407 | |
---|
408 | $feed = new sfRssFeed(); |
---|
409 | $feed->setUrl($uri); |
---|
410 | $feed->fromXml($feedString); |
---|
411 | $this->feed = $feed; |
---|
412 | } |
---|
413 | }}} |
---|
414 | |
---|
415 | Thanks to the `sfFeedPeer` shortcuts, this can be reduced to a single line: |
---|
416 | |
---|
417 | {{{ |
---|
418 | public function executeLastPosts() |
---|
419 | { |
---|
420 | $this->feed = sfFeedPeer::createFromWeb('http://groups.google.com/group/symfony-users/feed/rss_v2_0_msgs.xml'); |
---|
421 | } |
---|
422 | }}} |
---|
423 | |
---|
424 | The `createFromWeb()` method first parse the response and tries to recognize a known feed format (RSS or Atom - The recognized formats are the same ones as above). Note that this method depends on the `sfWebBrowserPlugin`, so this plugin must be installed to make the method work. |
---|
425 | |
---|
426 | Once the feed is built, it is very easy to use it for the display in the template: |
---|
427 | |
---|
428 | {{{ |
---|
429 | <h2>Latests posts from the mailing-list</h2> |
---|
430 | <ul> |
---|
431 | <?php foreach($feed->getItems() as $post): ?> |
---|
432 | <li> |
---|
433 | <?php echo format_date($post->getPubDate(), 'd/MM H:mm') ?> - |
---|
434 | <?php echo link_to($post->getTitle(), $post->getLink()) ?> |
---|
435 | by <?php echo $post->getAuthorName() ?> |
---|
436 | </li> |
---|
437 | <?php endforeach; ?> |
---|
438 | </ul> |
---|
439 | }}} |
---|
440 | |
---|
441 | === Aggregating several feeds === |
---|
442 | |
---|
443 | The `sfFeedPeer` class contains a method called `aggregate()`, which merges several feeds and reorders the items chronologically. Using it is very simple: just pass an array of feeds as parameters, and you receive a new feed object with all the items within. |
---|
444 | |
---|
445 | For instance, here is how you could display a feed of 10 posts populated with the latest posts from both the users and the devs groups: |
---|
446 | |
---|
447 | {{{ |
---|
448 | public function executeLastPosts() |
---|
449 | { |
---|
450 | $feed1 = sfFeedPeer::createFromWeb('http://groups.google.com/group/symfony-users/feed/rss_v2_0_msgs.xml'); |
---|
451 | $feed2 = sfFeedPeer::createFromWeb('http://groups.google.com/group/symfony-devs/feed/rss_v2_0_msgs.xml'); |
---|
452 | $this->feed = sfFeedPeer::aggregate(array($feed1, $feed2), array('limit' => 10)); |
---|
453 | } |
---|
454 | }}} |
---|
455 | |
---|
456 | == TODO == |
---|
457 | |
---|
458 | * unit test the `sfFeedPeer` class |
---|
459 | * Populate feedItems from a pager rather than from an array of objects |
---|
460 | * Deal with time zones (i.e. store dates in GMT, and handle input and output with a time zone) |
---|
461 | |
---|
462 | == Changelog == |
---|
463 | |
---|
464 | == Trunk == |
---|
465 | |
---|
466 | * fabien: fixed response content-type encoding |
---|
467 | * francois: Fixed encoding inside CDATA sections |
---|
468 | * fabien: fixed routing for symfony 1.1 and optimized the 1.0 version |
---|
469 | * fabien: added the possibility to pass arguments to methods that convert an object to an item |
---|
470 | * Pascal.Borreli : symfony coding practices : removed lib closing tag (#2657) |
---|
471 | * Fabian Lange : added image capabilities (#2551) |
---|
472 | |
---|
473 | === 2007-04-15 | 0.9.4 Beta === |
---|
474 | |
---|
475 | * francois: Added a `toXML` method to `sfRssFeed`, `sfRss10Feed` and `sfAtom1Feed` (based on an idea from Frank Stelzer) |
---|
476 | * Frank.Stelzer: Added a new `sfFeedPeer::createFromXml()` method |
---|
477 | * Frank.Stelzer: Fixed errors in the documentation where `sfFeed` methods were described |
---|
478 | * francois: `sfFeedPeer::createFromWeb()` now throws an exception whenever the fetched URL returns an error |
---|
479 | * Markus.Staab: Fixed a typo in `sfFeed` constructor |
---|
480 | * francois: Fixed a warning in `sfRssFeed` causing badly formatted feeds in dev env |
---|
481 | * francois: '''BC break''' `sfFeedPeer::convertObjectsToItems()` signature changed (`$objects, $options = array()`) to have similar signature to that of `sfFeedPeer::createFromObjects()` |
---|
482 | * francois: Added the ability to define one method per feed property in `sfFeedPeer::convertObjectsToItems()` |
---|
483 | * francois: `sfFeedPeer::convertObjectsToItems()` now populates the feed item content |
---|
484 | * francois: `sfFeedPeer::createFromWeb()` can now use a custom `userAgent` option to be seen as a custom user agent from the outside |
---|
485 | * francois: `sfFeedPeer::createFromWeb()` has a better detection of Atom feeds |
---|
486 | * francois: Fixed a bug in generated Atom1 feeds (summary attribute) |
---|
487 | * francois: Fixed a bug in `sfFeedPeer::aggregate()` when defining a feed format |
---|
488 | |
---|
489 | === 2007-03-13 | 0.9.3 Beta === |
---|
490 | |
---|
491 | * francois: `sfFeedPeer::createFromWeb()` is smarter at determining the type of feed it reads |
---|
492 | * francois: Added smart guessing of publication date for RSS and Atom feeds, based on URL scheme |
---|
493 | * francois: `sfFeedPeer::aggregate()` now handles items with the same date without overriding one. |
---|
494 | * francois: Fixed two bugs in `sfFeedPeer` preventing items creation from objects |
---|
495 | * francois: Items aggregated in `sfFeedPeer::aggregate` now remember their original feed properties |
---|
496 | * francois: `sfFeedPeer::aggregate` can now limit the total number of items returned |
---|
497 | * francois: Added `sfFeed::keepOnlyItems($count)` method |
---|
498 | * francois: `sfRssReed` now looks for a `dc:creator` of no regular author is found |
---|
499 | |
---|
500 | === 2007-03-03 | 0.9.2 Beta === |
---|
501 | |
---|
502 | * francois: Added a way to get an item description based on a content when no description exists |
---|
503 | * francois: Added `<content:encoded>` handling in RSS 1 and 2 feeds (based on a patch from Jeff Merlet) |
---|
504 | |
---|
505 | === 2007-02-22 | 0.9.1 Beta === |
---|
506 | |
---|
507 | * francois: Added the `sfRss10Feed` class and unit tests |
---|
508 | * francois: Changed feed type detection technique to avoid building a simpleXML element twice |
---|
509 | |
---|
510 | === 2007-02-21 | 0.9.0 Beta === |
---|
511 | |
---|
512 | * francois: Added more unit tests |
---|
513 | * francois: Improved `sfRssFeed` conversions |
---|
514 | * francois: Moved `getLatestPostDate()` method to `sfFeed` and unit tested it. |
---|
515 | * francois: '''BC break''' Renamed specialized RSS classes to `sfRss201Feed` and `sfRss901Feed`. These classes are not really useful anyway, since all their code was refactored to `sfRssFeed`. |
---|
516 | |
---|
517 | === 2007-02-19 | 0.8.1 Alpha === |
---|
518 | |
---|
519 | * francois: Added much more unit tests |
---|
520 | * francois: Improved `sfAtom1Feed` conversions |
---|
521 | * francois: Added `__toString()` and `initialize()` methods to `sfFeedEnclosure` |
---|
522 | * francois: Added `content` property to `sfFeedItem` |
---|
523 | * francois: '''BC break''' `fromXML()` methods now expect a string rather than a simpleXML object |
---|
524 | * francois: '''BC break''' `sfFeedPeer::createFromObjects()` signature changed ($objects, $parameters = array()) |
---|
525 | |
---|
526 | |
---|
527 | === 2007-02-18 | 0.8.0 Alpha === |
---|
528 | |
---|
529 | * francois: Initial release |
---|