From e02edbfe524f0a7dcadcb319e927b4804bdd7bc4 Mon Sep 17 00:00:00 2001 From: x2003bruneau Date: Sun, 25 Feb 2007 16:48:39 +0000 Subject: [PATCH] Add RSS Feeds git-svn-id: svn+ssh://murphy/home/svn/banana/trunk@215 9869982d-c50d-0410-be91-f2a2ec7c7c7b --- Changelog | 8 ++ banana/banana.inc.php.in | 97 ++++++++++++++++- banana/feed.inc.php | 185 ++++++++++++++++++++++++++++++++ banana/message.inc.php | 30 +++++- banana/mimepart.inc.php | 2 +- banana/page.inc.php | 36 ++++++- banana/spool.inc.php | 12 ++- banana/templates/banana-base.tpl | 2 +- banana/templates/banana-boxlist.inc.tpl | 9 +- banana/templates/banana-feed-rss2.tpl | 20 ++++ banana/templates/banana-thread.inc.tpl | 1 + img/feed.gif | Bin 0 -> 1079 bytes 12 files changed, 383 insertions(+), 19 deletions(-) create mode 100644 banana/feed.inc.php create mode 100644 banana/templates/banana-feed-rss2.tpl create mode 100644 img/feed.gif diff --git a/Changelog b/Changelog index 8ded7e9..399f0f1 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,11 @@ +Sun, 25 Feb 2007 Florent Bruneau + + * RSS Feeds + +Wed, 21 Feb 2007 Florent Bruneau + + * MBox helper written in C + ================================================================================ VERSION 1.5 diff --git a/banana/banana.inc.php.in b/banana/banana.inc.php.in index 0670860..fde1931 100644 --- a/banana/banana.inc.php.in +++ b/banana/banana.inc.php.in @@ -49,7 +49,7 @@ class Banana static public $msgshow_mimeparts = array('multipart/report', 'multipart/mixed', 'text/html', 'text/plain', 'text/enriched', 'text', 'message'); static public $msgshow_xface = true; - static public $msgshow_wrap = 78; + static public $msgshow_wrap = 80; static public $msgshow_externalimages = false; static public $msgshow_hasextimages = false; static public $msgshow_withthread = true; @@ -77,6 +77,14 @@ class Banana */ static public $msgedit_mimeparts = array('multipart/report', 'multipart/mixed', 'text/plain', 'text/enriched', 'text/html', 'text', 'message'); +### Feed configuration ### + static public $feed_active = true; + static public $feed_format = 'rss2'; + static public $feed_updateOnDemand = false; // Update the feed each time sbd check it + static public $feed_copyright = null; + static public $feed_namePrefix = 'Banana :: '; + static public $feed_size = 15; // Number of messages in the feed + ### Protocole ### /** News serveur to use */ @@ -99,6 +107,7 @@ class Banana const ACTION_BOX_NEEDED = 1; // mask const ACTION_BOX_LIST = 2; const ACTION_BOX_SUBS = 4; + const ACTION_BOX_FEED = 8; const ACTION_MSG_LIST = 3; const ACTION_MSG_READ = 5; const ACTION_MSG_NEW = 9; @@ -185,16 +194,26 @@ class Banana Banana::$first = isset($this->params['first']) ? $this->params['first'] : null; Banana::$part = isset($this->params['part']) ? $this->params['part'] : 'text'; + $action = @$this->params['action']; + if ($action == 'rss' || $action == 'rss2' || $action == 'atom') { + if ($action == 'rss') { + $action = 'rss2'; + } + Banana::$feed_format = $action; + Banana::$action = Banana::ACTION_BOX_FEED; + return; + } + // Look for the action to execute if (is_null(Banana::$group)) { - if (isset($this->params['action']) && $this->params['action'] == 'subscribe') { + if ($action == 'subscribe') { Banana::$action = Banana::ACTION_BOX_SUBS; } else { Banana::$action = Banana::ACTION_BOX_LIST; } return; } - $action = isset($this->params['action']) ? $this->params['action'] : null; + if (is_null(Banana::$artid)) { if ($action == 'new') { Banana::$action = Banana::ACTION_MSG_NEW; @@ -249,6 +268,8 @@ class Banana case Banana::ACTION_BOX_LIST: $error = $this->action_listBoxes(); break; + case Banana::ACTION_BOX_FEED: + return $this->action_feed(); // generate its own xml case Banana::ACTION_MSG_LIST: $error = $this->action_showThread(Banana::$group, Banana::$first); break; @@ -281,6 +302,16 @@ class Banana return Banana::$page->css; } + /** Return the Link to the feed of the page + */ + public function feed() + { + if (!Banana::$feed_active) { + return null; + } + return Banana::$page->makeURL(array('group' => Banana::$group, 'action' => Banana::$feed_format)); + } + /** Return the execution backtrace of the current BananaProtocole */ public function backtrace() @@ -324,6 +355,28 @@ class Banana return true; } + protected function action_feed() + { + Banana::load('feed'); + if (Banana::$group) { + $feed =& BananaFeed::getFeed(); + return $feed->toXML(); + } + if (Banana::$profile['subscribe']) { + $subfeed = null; + foreach (Banana::$profile['subscribe'] as $group) { + Banana::$group = $group; + if (Banana::$feed_updateOnDemand) { + $this->loadSpool($group); + } + $feed =& BananaFeed::getFeed(); + $subfeed =& BananaFeed::merge($subfeed, $feed, _b_('Abonnements'), _b_('Mes abonnements Banana')); + } + return $subfeed->toXML(); + } + return Banana::$page->feed(); + } + protected function action_showThread($group, $first) { Banana::$page->setPage('thread'); @@ -502,7 +555,7 @@ class Banana return false; } return true; - } + } static public function createAllSpool(array $protos) { @@ -519,7 +572,7 @@ class Banana print "** $proto **\n"; foreach (array_keys($groups) as $g) { - print "Generating spool for $g : "; + print "Generating spool for $g: "; Banana::$group = $g; $spool = $banana->loadSpool($g); if (!$banana->checkErrors()) { @@ -527,11 +580,45 @@ class Banana } print "done.\n"; unset($spool); + Banana::$spool = null; } print "\n"; } } + static public function refreshAllFeeds(array $protos) + { + Banana::load('feed'); + Banana::$feed_updateOnDemand = true; // In order to force update + foreach ($protos as $proto) { + $banana = new Banana(array(), $proto); + + if (!$banana->checkErrors()) { + continue; + } + $groups = Banana::$protocole->getBoxList(); + if (!$banana->checkErrors()) { + continue; + } + + print "** $proto **\n"; + foreach (array_keys($groups) as $g) { + print "Generating feed cache for $g: "; + Banana::$group = $g; + $spool = $banana->loadSpool($g); + if (!$banana->checkErrors()) { + break; + } + $feed =& BananaFeed::getFeed(); + print "done.\n"; + unset($feed); + unset($spool); + Banana::$spool = null; + } + print "\n"; + } + } + /**************************************************************************/ /* Private functions */ /**************************************************************************/ diff --git a/banana/feed.inc.php b/banana/feed.inc.php new file mode 100644 index 0000000..e4b3f90 --- /dev/null +++ b/banana/feed.inc.php @@ -0,0 +1,185 @@ + author, 'date' => date (UNIX TS), 'title' => subject, 'body' => html body, + * 'link' => link, 'reply' => reply) + */ + public $messages = array(); + + /** Create an empty feed + */ + private function __construct() + { + $this->version = BANANA_FEED_VERSION; + $this->group = Banana::$group; + $this->description = Banana::$protocole->getDescription(); + } + + /** Update the feed, using current settings of Banana + * Calling this function suppose that Banana::$spool is the spool of the current box + */ + public function update() + { + if (!Banana::$spool || Banana::$spool->group != $this->group) { + return false; + } + if (!Banana::$spool->ids) { + $spool_indexes = array(); + } else { + $spool_indexes = Banana::$spool->ids; + sort($spool_indexes, SORT_NUMERIC); + $spool_indexes = array_slice($spool_indexes, -Banana::$feed_size, Banana::$feed_size); + } + $feed_indexes = array_keys($this->messages); + $old = array_diff($feed_indexes, $spool_indexes); + foreach ($old as $key) { + unset($this->messages[$key]); + } + $new = array_diff($spool_indexes, $feed_indexes); + foreach ($new as $key) { + $message =& Banana::$protocole->getMessage($key); + $array = array(); + $array['author'] = $message->getAuthorName(); + $array['date'] = $message->getHeaderValue('Date'); + $array['title'] = $message->getHeaderValue('Subject'); + $array['body'] = $message->toHtml(); + $array['link'] = Banana::$page->makeUrl(array('group' => $this->group, 'artid' => $key)); + if (Banana::$protocole->canSend()) { + $array['reply'] = Banana::$page->makeUrl(array('group' => $this->group, 'artid' => $key, 'action' => 'new')); + } + $this->messages[$key] = $array; + } + $this->writeToFile(); + } + + /** Get the spool corresponding with the current settings of Banana + */ + static public function &getFeed() + { + $feed =& BananaFeed::readFromFile(); + if (!$feed) { + $feed = new BananaFeed(); + } + if (Banana::$feed_updateOnDemand) { + $feed->update(); + } + return $feed; + } + + /** Return the cache file name + */ + static private function filename() + { + $file = Banana::$spool_root . '/' . Banana::$protocole->name() . '/'; + if (!is_dir($file)) { + mkdir($file); + } + return $file . Banana::$protocole->filename() . '_feed'; + } + + /** Read a feed from a cache file + */ + static private function &readFromFile() + { + $feed = null; + $file = BananaFeed::filename(); + if (!file_exists($file)) { + return $feed; + } + $feed = unserialize(file_get_contents($file)); + if ($feed->version != BANANA_FEED_VERSION) { + $feed = null; + } + return $feed; + } + + /** Write a feed to a cache file + */ + private function writeToFile() + { + $file = BananaFeed::filename(); + file_put_contents($file, serialize($this)); + } + + /** Merge to feeds into a new one + */ + static public function &merge(&$feed1, &$feed2, $name, $description = null) + { + if (!$feed1) { + $feed = null; + $feed1 =& $feed2; + $feed2 =& $feed; + } + if ($feed1->group == $name) { + $master =& $feed1; + $slave =& $feed2; + } else if ($feed2 && $feed2->group == $name) { + $master =& $feed2; + $slave =& $feed1; + } else { + $master = new BananaFeed(); + $master->group = $name; + $master->description = $description; + foreach ($feed1->messages as $key=>$message) { + $message['title'] = '[' . $feed1->group . '] ' . $message['title']; + $master->messages[$feed1->group . '-' . $key] = $message; + } + $slave =& $feed2; + } + if (!$slave) { + return $master; + } + $messages = array(); + $m1 = reset($master->messages); + $m2 = reset($slave->messages); + for ($i = 0 ; $i < 2 * Banana::$feed_size && ($m1 || $m2) ; $i++) { + if ($m2 && (!$m1 || $m1['date'] < $m2['date'])) { + $m2['title'] = '[' . $feed2->group . '] ' . $m2['title']; + $messages[$slave->group . '-' . key($slave->messages)] = $m2; + $m2 = next($slave->messages); + } else { + $messages[key($master->messages)] = $m1; + $m1 = next($master->messages); + } + } + $master->messages =& $messages; + return $master; + } + + /** Generate the feed xml + */ + public function toXML() + { + Banana::$page->assign_by_ref('feed', $this); + return Banana::$page->feed(); + } +} + +// vim:set et sw=4 sts=4 ts=4 enc=utf-8: +?> diff --git a/banana/message.inc.php b/banana/message.inc.php index 66786da..02eeffc 100644 --- a/banana/message.inc.php +++ b/banana/message.inc.php @@ -155,10 +155,7 @@ final class BananaMessage extends BananaMimePart $mailto = '' . banana_htmlentities($regs[1]) . ''; - } - if (preg_match("/^<(.+@.+)>$/", $text, $regs)) { + if (preg_match("/^ ]+)>?$/", $text, $regs)) { $result = $mailto . $regs[1] . '">' . banana_htmlentities($regs[1]) . ''; } if (preg_match("/^([^ ]+@[^ ]+) \((.*)\)$/", $text, $regs)) { @@ -172,6 +169,31 @@ final class BananaMessage extends BananaMimePart return preg_replace("/\\\(\(|\))/","\\1",$result); } + public function getAuthorName() + { + $text = $this->getHeaderValue('From'); + $name = null; + if (preg_match("/^([^ ]+@[^ ]+) \((.*)\)$/", $text, $regs)) { + $name = $regs[2]; + } + if (preg_match("/^\"?([^<>\"]+)\"? +<(.+@.+)>$/", $text, $regs)) { + $name = preg_replace("/^'(.*)'$/", '\1', $regs[1]); + $name = stripslashes($name); + } + if ($name) { + return preg_replace("/\\\(\(|\))/","\\1", $name); + } + + if (function_exists('hook_getAuthorName') && $name = hook_getAuthorName($this)) { + return $name; + } + + if (preg_match("/([^< ]+)@([^> ]+)/", $text, $regs)) { + return $regs[1]; + } + return 'Anonymous'; + } + static public function formatDate($text) { return strftime("%A %d %B %Y, %H:%M (fuseau serveur)", strtotime($text)); diff --git a/banana/mimepart.inc.php b/banana/mimepart.inc.php index 1ca1ec8..e2fea25 100644 --- a/banana/mimepart.inc.php +++ b/banana/mimepart.inc.php @@ -417,7 +417,7 @@ class BananaMimePart public function toHtml() { - list($type, $subtype) = $this->getType(); + @list($type, $subtype) = $this->getType(); if ($type == 'image') { $part = $this->id ? $this->id : $this->filename; return '{$act.text}

{/foreach} {if $page eq 'forums'} - {include file="banana-boxlist.inc.tpl" grouplist=$groups withstats=true} + {include file="banana-boxlist.inc.tpl" grouplist=$groups withstats=true withfeed=$feed_active} {if $newgroups|@count}

{"Les nouveaux groupes suivants ont été créés depuis votre dernière visite"|b}

{include file="banana-boxlist.inc.tpl" grouplist=$newgroups withstats=true} diff --git a/banana/templates/banana-boxlist.inc.tpl b/banana/templates/banana-boxlist.inc.tpl index 921b5d3..102bf07 100644 --- a/banana/templates/banana-boxlist.inc.tpl +++ b/banana/templates/banana-boxlist.inc.tpl @@ -15,7 +15,14 @@ {"Nouveaux"|b} {/if} {"Nom"|b} - {"Description"|b} + + {if $withfeed} +
+ {imglink action=$feed_format img=feed alt="Flux"|b accesskey=f} +
+ {/if} + {"Description"|b} + {foreach from=$grouplist key=name item=grp} diff --git a/banana/templates/banana-feed-rss2.tpl b/banana/templates/banana-feed-rss2.tpl new file mode 100644 index 0000000..b1543b4 --- /dev/null +++ b/banana/templates/banana-feed-rss2.tpl @@ -0,0 +1,20 @@ + + + + {$title_prefix}{$feed->group} + {$language} + {url group=$group} + {$feed->description} + {foreach from=$feed->messages key=id item=message} + + <![CDATA[{$message.title}]]> + {$id} + {$message.link} + + {$message.author} + {$message.date|rss_date} + + {/foreach} + + +{* vim:set et sw=2 sts=2 ts=2 enc=utf-8: *} diff --git a/banana/templates/banana-thread.inc.tpl b/banana/templates/banana-thread.inc.tpl index 9bf65fd..f8fe90b 100644 --- a/banana/templates/banana-thread.inc.tpl +++ b/banana/templates/banana-thread.inc.tpl @@ -27,6 +27,7 @@ {if $protocole->canSend()}
{imglink group=$group action=new img=post alt="Nouveau message"|b accesskey=p} + {if $feed_active}{imglink group=$group action=$feed_format img=feed alt="Flux"|b accesskey=f}{/if}
{/if} {"Auteur"|b} diff --git a/img/feed.gif b/img/feed.gif new file mode 100644 index 0000000000000000000000000000000000000000..09ecd6123b532ff1bcc1f110633f5f0d19999743 GIT binary patch literal 1079 zcmd^;{ZG^d9LGNls?#6xWV%uIAjM`eYfzcy!{h`(4Don4JY5eW7-n5TkfH|OBa8!# zpr(kmyLN)Y*Mqa7K#)7Q!vi07nKfE#NwcA>A4sSsKlJL`-_d)oz4!jSKR>@SGSk<* zRHzlLOeum+NEJ2<8N$oLHX%pI6AZ#m;Z@;vp-9*(ln8GMCZSAtUpT0S=P7%EBSMv6 z5p2TY)i{JBYp50K)IObh!MYy%dYl`nF;Kseh7_De8Voe2^6_R`cGH?cTP7#j1^QM!Y-eHq!MD-q-MD)2*Jn=-Gu*igzDfR(!>DmvX*{9@R_uqKGfc={50H zIlg*&57M`vuMg1s0evR=4sgLle+A!I=vQAg463=A3wACZV!+OzouR|{4-u#$P>tV? z-@$Md!&XMB88H*8CUlgLg;5Ka%!I9kZG`QNnHje+ZYSKtcnzbcxOAM$HZIq2MddhG zTDjWFH7^kl-+H<3TvzSn>B_q819WQdIr73DNu`6i%e{*lKOdW39JEceC-uio3u65xu3! zo!0nBvh&y7smqd^pBlUq6$~$4k?(OWs3?qnbd|EFz2EMrTk>?Cea6@1KK9AhlUbox z9GVX8vhswtCv3Lb51*(^3pK<9N9$K<*Ur_oB;H7PCTneI;JZ!toj7*%RQA^;iQ74`sFk424WU0ek@6E_KPJ-Vwx6dWZl1&mD}3@ literal 0 HcmV?d00001 -- 2.1.4