+++ /dev/null
-<?php
-/********************************************************************************
-* include/NetNNTP.inc.php : NNTP subroutines
-* -------------------------
-*
-* This file is part of the banana distribution
-* Copyright: See COPYING files that comes with this distribution
-********************************************************************************/
-
-/** Class NNTP
- * implements some basic functions for NNTP protocol
- */
-class nntp
-{
- /** socket filehandle */
- var $ns;
- /** posting allowed */
- var $posting;
- /** last NNTP error code */
- var $lasterrorcode;
- /** last NNTP error text */
- var $lasterrortext;
- /** test validity */
- var $valid = true;
-
- /** constructor
- * @param $_host STRING NNTP host
- * @param $_timeout INTEGER socket timeout
- * @param $_reader BOOLEAN sends a "MODE READER" at connection if true
- */
-
- function nntp($_url, $_timeout=120, $_reader=true)
- {
- $url['port'] = 119;
- $url = parse_url($_url);
- $this->ns = fsockopen($url['host'], $url['port'], $errno, $errstr, $_timeout);
- if (!$this->ns) {
- $this->valid = false;
- return null;
- }
-
- $result = $this->gline();
- $this->posting = (substr($result, 0, 3)=="200");
- if ($_reader && ($result{0}=="2")) {
- $this->pline("MODE READER\r\n");
- $result = $this->gline();
- $this->posting = ($result{0}=="200");
- }
- if ($result{0}=="2" && isset($url['user']) && $url['user'] != 'anonymous') {
- return $this->authinfo($url['user'], $url['pass']);
- }
- return ($result{0}=="2");
- }
-
-# Socket functions
-
- /** get a line from server
- * @return STRING
- */
-
- function gline()
- {
- return rtrim(fgets($this->ns, 1200));
- }
-
- /** puts a line on server
- * @param STRING $_line line to put
- */
-
- function pline($_line)
- {
- return fputs($this->ns, $_line, strlen($_line));
- }
-
-# strict NNTP Functions [RFC 977]
-# see http://www.faqs.org/rfcs/rfc977.html
-
- /** authentification
- * @param $_user STRING login
- * @param $_pass INTEGER password
- * @return BOOLEAN true if authentication was successful
- */
-
- function authinfo($_user, $_pass)
- {
- $user = preg_replace("/(\r|\n)/", "", $_user);
- $pass = preg_replace("/(\r|\n)/", "", $_pass);
- $this->pline("AUTHINFO USER $user\r\n");
- $this->gline();
- $this->pline("AUTHINFO PASS $pass\r\n");
- $result=$this->gline();
- if ($result{0}!="2") {
- $this->lasterrorcode = substr($result, 0, 3);
- $this->lasterrortext = substr($result, 4);
- return false;
- }
- return true;
- }
-
- /** retrieves an article
- * MSGID is a numeric ID a shown in article's headers. MSGNUM is a
- * server-dependent ID (see X-Ref on many servers) and retriving
- * an article by this way will change the current article pointer.
- * If an error occur, false is returned.
- * @param $_msgid STRING MSGID or MSGNUM of article
- * @return ARRAY lines of the article
- * @see body
- * @see head
- */
-
- function article($_msgid="")
- {
- $msgid = preg_replace("/(\r|\n)/", "", $_msgid);
- $this->pline("ARTICLE $msgid\r\n");
- $result = $this->gline();
- if ($result{0} != '2') {
- $this->lasterrorcode = substr($result, 0, 3);
- $this->lasterrortext = substr($result, 4);
- return false;
- }
- $result = $this->gline();
- while ($result != ".") {
- $array[] = $result;
- $result = $this->gline();
- }
- return $array;
- }
-
- /** post a message
- * if an error occur, false is returned
- * @param $_message STRING message to post
- * @return STRING MSGID of article
- */
-
- function post($_message)
- {
- if (is_array($_message)) {
- $message=join("\n", $_message);
- } else {
- $message=$_message;
- }
- $this->pline("POST \r\n");
- $result=$this->gline();
- if ($result{0} != '3') {
- $this->lasterrorcode = substr($result, 0, 3);
- $this->lasterrortext = substr($result, 4);
- return false;
- }
- $this->pline($message."\r\n.\r\n");
- $result = $this->gline();
- if ($result{0} != '2') {
- $this->lasterrorcode = substr($result, 0, 3);
- $this->lasterrortext = substr($result, 4);
- return false;
- }
- if ($result{0} == '2') {
- if (preg_match("/(<[^@>]+@[^@>]+>)/", $result, $regs)) {
- return $regs[0];
- } else {
- return true;
- }
- }
- return false;
- }
-
- /** fetches the body of an article
- * params are the same as article
- * @param $_msgid STRING MSGID or MSGNUM of article
- * @return ARRAY lines of the article
- * @see article
- * @see head
- */
-
- function body($_msgid="")
- {
- $msgid = preg_replace("/(\r|\n)/", "", $_msgid);
- $this->pline("BODY $msgid\r\n");
- $result = $this->gline();
- if ($result{0} != '2') {
- $this->lasterrorcode = substr($result, 0, 3);
- $this->lasterrortext = substr($result, 4);
- return false;
- }
- $array = Array();
- while (($result = $this->gline()) != ".") {
- $array[] = $result;
- }
- return $array;
- }
-
- /** fetches the headers of an article
- * params are the same as article
- * @param $_msgid STRING MSGID or MSGNUM of article
- * @return ARRAY lines of the article
- * @see article
- * @see body
- */
-
- function head($_msgid="")
- {
- $msgid = preg_replace("/(\r|\n)/", "", $_msgid);
- $this->pline("HEAD $msgid\r\n");
- $result = $this->gline();
- if ($result{0}!="2") {
- $this->lasterrorcode = substr($result, 0, 3);
- $this->lasterrortext = substr($result, 4);
- return false;
- }
- $result = $this->gline();
- while ($result != ".") {
- $array[] = $result;
- $result = $this->gline();
- }
- return $array;
- }
-
- /** set current group
- * @param $_group STRING
- * @return ARRAY array : nb of articles in group, MSGNUM of first article, MSGNUM of last article, and group name
- */
-
- function group($_group)
- {
- $group = preg_replace("/(\r|\n)/", "", $_group);
- $this->pline("GROUP $group\r\n");
- $line = $this->gline();
- if ($line{0}!="2") {
- $this->lasterrorcode = substr($line, 0, 3);
- $this->lasterrortext = substr($line, 4);
- return false;
- }
- if (preg_match("/^2\d{2} (\d+) (\d+) (\d+) ([^ ]+)/", $line, $regs)) {
- return array($regs[1], $regs[2], $regs[3], $regs[4]);
- }
- return false;
- }
-
- /** set the article pointer to the previous article in current group
- * @return STRING MSGID of article
- * @see next
- */
-
- function last()
- {
- $this->pline("LAST \r\n");
- $line = $this->gline();
- if ($line{0}!="2") {
- $this->lasterrorcode = substr($result, 0, 3);
- $this->lasterrortext = substr($result, 4);
- return false;
- }
- if (preg_match("/^2\d{2} \d+ <([^>]+)>/", $line, $regs)) {
- return "<{$regs[1]}>";
- }
- return false;
- }
-
- /** set the article pointer to the next article in current group
- * @return STRING MSGID of article
- * @see last
- */
-
- function next()
- {
- $this->pline("NEXT \r\n");
- $line = $this->gline();
- if ($line{0}!="2") {
- $this->lasterrorcode = substr($result, 0, 3);
- $this->lasterrortext = substr($result, 4);
- return false;
- }
- if (preg_match("/^2\d{2} \d+ <([^>]+)>/", $line, $regs)) {
- return "<{$regs[1]}>";
- }
- return false;
- }
-
- /** set the current article pointer
- * @param $_msgid STRING MSGID or MSGNUM of article
- * @return BOOLEAN true if authentication was successful, error code otherwise
- * @see article
- * @see body
- */
-
- function nntpstat($_msgid)
- {
- $msgid = preg_replace("/(\r|\n)/", "", $_msgid);
- $this->pline("STAT $msgid\r\n");
- $line = $this->gline();
- if ($line{0}!="2") {
- $this->lasterrorcode = substr($result, 0, 3);
- $this->lasterrortext = substr($result, 4);
- return false;
- }
- if (preg_match("/^2\d{2} \d+ <([^>]+)>/", $line, $regs)) {
- return "<{$regs[1]}>";
- }
- return false;
- }
-
- /** returns true if posting is allowed
- * @return BOOLEAN true if posting is allowed lines
- */
-
- function postok()
- {
- return ($this->posting);
- }
-
- /** retreive the group list
- * @return ARRAY group name => (MSGNUM of first article, MSGNUM of last article, NNTP flags)
- * @see newgroups, liste
- */
- function _grouplist()
- {
- global $banana;
-
- if (substr($this->gline(), 0, 1)!="2") {
- return false;
- }
- $result = $this->gline();
- $array = Array();
- while ($result != ".") {
- preg_match("/([^ ]+) (\d+) (\d+) (.)/", $result, $regs);
- if (!isset($banana->grp_pattern) || preg_match('@'.$banana->grp_pattern.'@', $regs[1])) {
- $array[$regs[1]] = array(intval($regs[2]), intval($regs[3]), intval($regs[4]));
- }
- $result = $this->gline();
- }
- return $array;
- }
-
- /** gets information about all active newsgroups
- * @return ARRAY group name => (MSGNUM of first article, MSGNUM of last article, NNTP flags)
- * @see newgroups
- */
-
- function liste()
- {
- $this->pline("LIST\r\n");
- return $this->_grouplist();
- }
-
- /** get information about recent newsgroups
- * same as list, but information are limited to newgroups created after $_since
- * @param $_since INTEGER unix timestamp
- * @param $_distributions STRING distributions
- * @return ARRAY same format as liste
- * @see liste
- */
-
- function newgroups($_since, $_distributions="")
- {
-#assume $_since is a unix timestamp
- $distributions = preg_replace("/(\r|\n)/", "", $_distributions);
- $this->pline("NEWGROUPS ".gmdate("ymd His", $_since)
- ." GMT $distributions\r\n");
- return $this->_grouplist();
- }
-
- /** gets a list of new articles
- * @param $_since INTEGER unix timestamp
- * @parma $_groups STRING pattern of intersting groups
- * @return ARRAY MSGID of new articles
- */
-
- function newnews($_since, $_groups="*", $_distributions="")
- {
- $distributions = preg_replace("/(\r|\n)/", "", $_distributions);
- $groups = preg_replace("/(\r|\n)/", "", $_groups);
- $array = array();
-#assume $since is a unix timestamp
- $this->pline("NEWNEWS $_groups ".gmdate("ymd His", $_since)." GMT $distributions\r\n");
- if (substr($this->gline(), 0, 1)!="2") {
- return false;
- }
- while (($result = $this->gline()) != ".") {
- $array[] = $result;
- }
- return $array;
- }
-
- /** Tell the remote server that I am not a user client, but probably another news server
- * @return BOOLEAN true if sucessful
- */
-
- function slave()
- {
- $this->pline("SLAVE \r\n");
- return (substr($this->gline(), 0, 1)=="2");
- }
-
- /** implements IHAVE method
- * @param $_msgid STRING MSGID of article
- * @param $_message STRING article
- * @return BOOLEAN
- */
-
- function ihave($_msgid, $_message=false)
- {
- $msgid = preg_replace("/(\r|\n)/", "", $_msgid);
- if (is_array($message)) {
- $message = join("\n", $_message);
- } else {
- $message = $_message;
- }
- $this->pline("IHAVE $msgid \r\n");
- $result = $this->gline();
- if ($message && ($result{0}=="3")) {
- $this->pline("$message\r\n.\r\n");
- $result = $this->gline();
- }
- return ($result{0}=="2");
- }
-
- /** closes connection to server
- */
-
- function quit()
- {
- $this->pline("QUIT\r\n");
- $this->gline();
- fclose($this->ns);
- }
-
-# NNTP Extensions [RFC 2980]
-
- /** Returns the date on the remote server
- * @return INTEGER timestamp
- */
-
- function date()
- {
- $this->pline("DATE \r\n");
- $result = $this->gline();
- if (preg_match("/^111 (\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/", $result, $r)) {
- return gmmktime($r[4], $r[5], $r[6], $r[2], $r[3], $r[1]);
- }
- return false;
- }
-
- /** returns group descriptions
- * @param $_pattern STRING pattern of intersting groups
- * @return ARRAY group name => description
- */
-
- function xgtitle($_pattern="*")
- {
- $pattern = preg_replace("/[\r\n]/", "", $_pattern);
- $this->pline("XGTITLE $pattern \r\n");
- if (substr($this->gline(), 0, 1)!="2") return false;
- $result = $this->gline();
- while ($result != ".") {
- preg_match("/([^ \t]+)[ \t]+(.+)$/", $result, $regs);
- $array[$regs[1]] = $regs[2];
- $result = $this->gline();
- }
- return $array;
- }
-
- /** obtain the header field $hdr for all the messages specified
- * @param $_hdr STRING name of the header (eg: 'From')
- * @param $_range STRING range of articles
- * @return ARRAY MSGNUM => header value
- */
-
- function xhdr($_hdr, $_range="")
- {
- $hdr = preg_replace("/(\r|\n)/", "", $_hdr);
- $range = preg_replace("/(\r|\n)/", "", $_range);
- $this->pline("XHDR $hdr $range \r\n");
- if (substr($this->gline(), 0, 1)!="2") {
- return false;
- }
-
- $array = array();
- while (($result = $this->gline()) != '.') {
- if (preg_match("/([^ \t]+) (.*)$/", $result, $regs)) {
- $array[$regs[1]] = $regs[2];
- }
- }
- return $array;
- }
-
- /** obtain the header field $_hdr matching $_pat for all the messages specified
- * @param $_hdr STRING name of the header (eg: 'From')
- * @param $_range STRING range of articles
- * @param $_pat STRING pattern
- * @return ARRAY MSGNUM => header value
- */
-
- function xpat($_hdr, $_range, $_pat)
- {
- $hdr = preg_replace("/(\r|\n)/", "", $_hdr);
- $range = preg_replace("/(\r|\n)/", "", $_range);
- $pat = preg_replace("/(\r|\n)/", "", $_pat);
- $this->pline("XPAT $hdr $range $pat\r\n");
- if (substr($this->gline(), 0, 1)!="2") {
- return false;
- }
- $result = $this->gline();
- while ($result != ".") {
- preg_match("/([^ \t]+) (.*)$/", $result, $regs);
- $array[$regs[1]] = $regs[2];
- $result = $this->gline();
- }
- return $array;
- }
-}
-
-// vim:set et sw=4 sts=4 ts=4
-?>
<?php
/********************************************************************************
-* install.d/config.inc.php : configuration file
+* banana/banana.inc.php : banana main file
* --------------------------
*
* This file is part of the banana distribution
class Banana
{
- var $maxspool = 3000;
+ static public $maxspool = 3000;
- var $hdecode = array('from','name','organization','subject');
- var $parse_hdr = array('content-disposition', 'content-transfer-encoding',
- 'content-type', 'content-id', 'date', 'followup-to',
- 'from', 'message-id', 'newsgroups', 'organization',
- 'references', 'subject', 'x-face');
- var $show_hdr = array('from', 'newsgroups', 'followup', 'date',
- 'organization', 'references', 'x-face');
+ static public $parse_hdr = array('content-disposition', 'content-transfer-encoding',
+ 'content-type', 'content-id', 'date', 'followup-to',
+ 'from', 'message-id', 'newsgroups', 'organization',
+ 'references', 'subject', 'x-face', 'in-reply-to',
+ 'to', 'cc', 'reply-to');
+ static public $show_hdr = array('from', 'newsgroups', 'followup-to', 'to', 'cc', 'reply-to',
+ 'organization', 'date', 'references', 'in-reply-to');
/** Favorites MIMEtypes to use, by order for reading multipart messages
*/
- var $body_mime = array('text/plain', 'text/html', 'text/richtext');
+ static public $body_mime = array('text/html', 'text/plain', 'text/enriched', 'text', 'message');
+
/** Indicate wether posting attachment is allowed
*/
- var $can_attach = true;
+ static public $can_attach = true;
/** Maximum allowed file size for attachment
*/
- var $maxfilesize = 100000;
+ static public $maxfilesize = 100000;
/** Indicate wether x-face should be skinned as specials data or not
*/
- var $formatxface = true;
+ static public $formatxface = true;
/** Regexp for selecting newsgroups to show (if empty, match all newsgroups)
* ex : '^xorg\..*' for xorg.*
*/
- var $grp_pattern;
+ static public $grp_pattern = null;
- var $tbefore = 5;
- var $tafter = 5;
- var $tmax = 50;
+ static public $tbefore = 5;
+ static public $tafter = 5;
+ static public $tmax = 50;
- var $wrap = 74;
+ static public $wrap = 78;
+
/** Match an url
- * Should be included in a regexp delimited using ! (eg: "!$url_regexp!i")
+ * Should be included in a regexp delimited using /, !, , or @ (eg: "/$url_regexp/i")
* If it matches, return 3 main parts :
* \\1 and \\3 are delimiters
* \\2 is the url
* $matches[2] = "http://www.polytechnique.org"
* $matches[3] = "]"
*/
- var $url_regexp = '(["\[])?((?:https?|ftp|news)://(?:&|\.*,*[a-z@0-9~%$£µ&i#\-+=_/\?])*)(["\]])?';
+ static public $url_regexp = '(["\[])?((?:[a-z]+:\/\/|www\.)(?:[\.\,\;\!]*[a-z\@0-9~%$£µ&i#\-+=_\/\?]+)+)(["\]])?';
-
- /** Boundary for multipart messages
- */
- var $boundary = 'bananaBoundary42';
/** Global headers to use for messages
*/
- var $custom = "Mime-Version: 1.0\nUser-Agent: Banana @VERSION@\n";
- /** Global headers to use from multipart messages
- */
- var $custom_mp = "Content-Type: multipart/mixed; boundary=\"bananaBoundary42\"\nContent-Transfer-Encoding: 7bit\n";
- /** Body type when using plain text
- */
- var $custom_plain= "Content-Type: text/plain; charset=utf-8\nContent-Transfert-Encoding: 8bit\n";
+ static public $custom_hdr = array('Mime-Version' => '1.0', 'User-Agent' => 'Banana @VERSION@');
/** News serveur to use
*/
- var $host = 'news://localhost:119/';
+ static public $host = 'news://localhost:119/';
/** User profile
*/
- var $profile = Array( 'name' => 'Anonymous <anonymouse@example.com>', 'sig' => '', 'org' => '',
- 'customhdr' =>'', 'display' => 0, 'lastnews' => 0, 'locale' => 'fr_FR', 'subscribe' => array());
-
- var $state = Array('group' => null, 'artid' => null, 'action' => null);
- var $nntp;
- var $groups;
- var $newgroups;
- var $post;
- var $spool;
-
- var $get;
-
- function Banana()
- {
- $this->_require('NetNNTP');
- setlocale(LC_ALL, $this->profile['locale']);
- $this->nntp = new nntp($this->host);
- if (!$this->nntp || !$this->nntp->valid) {
- $this->nntp = null;
- }
- }
+ static public $profile = Array( 'From' => 'Anonymous <anonymouse@example.com>', 'sig' => '',
+ 'Organization' => '', 'custom_hdr' => array(), 'display' => 0,
+ 'lastnews' => 0, 'locale' => 'fr_FR', 'subscribe' => array());
+
+ static public $protocole = null;
+ static public $spool = null;
+ static public $message = null;
+ static public $page = null;
+
+ static public $group = null;
+ static public $artid = null;
+ static public $action = null;
+ static public $part = null;
+ static public $first = null;
+
+ static public $debug_nntp = false;
+ static public $debug_smarty = false;
+
+
+ // Actions
+ const ACTION_BOX_NEEDED = 1; // mask
+ const ACTION_BOX_LIST = 2;
+ const ACTION_BOX_SUBS = 4;
+ const ACTION_MSG_LIST = 3;
+ const ACTION_MSG_READ = 5;
+ const ACTION_MSG_NEW = 9;
+ const ACTION_MSG_CANCEL = 17;
+
+ // Box list view
+ const BOXES_ALL = 0;
+ const BOXES_SUB = 1;
+ const BOXES_NEW = 2;
+
+ // Spool view mode
+ const SPOOL_ALL = 0;
+ const SPOOL_UNREAD = 1;
+
+ /** Class parameters storage
+ */
+ public $params;
- /** Run Banana
- * @param STRING class Name of the class to use
- * @param ARRAY myget If defined is used instead of get
+ /** Build the instance of Banana
+ * This constructor only call \ref loadParams, connect to the server, and build the Smarty page
+ * @param protocole Protocole to use
*/
- function run($class = 'Banana', $myget = null)
+ public function __construct($params = null, $protocole = 'NNTP', $pageclass = 'BananaPage')
{
- global $banana;
-
- Banana::_require('misc');
- $banana = new $class();
-
- if (is_null($myget)) {
- $banana->get = $_GET;
+ Banana::load('text.func');
+ if (is_null($params)) {
+ $this->params = $_GET;
} else {
- $banana->get = $myget;
+ $this->params = $params;
}
+ $this->loadParams();
- if (!$banana->nntp) {
- $banana->state['page'] = 'error';
- return makeTable('<p class="error">'._b_('Impossible de contacter le serveur').'</p>');
- }
+ // connect to protocole handler
+ Banana::load($protocole);
+ $classname = 'Banana' . $protocole;
+ Banana::$protocole = new $classname(Banana::$group);
- $group = empty($banana->get['group']) ? null : $banana->get['group'];
- if (!is_null($group)
- && isset($banana->grp_pattern) && !preg_match('/' . $banana->grp_pattern . '/', $group)) {
- $banana->state['page'] = 'error';
- return makeTable('<p class="error">'
- . $group . _b_(' : ce newsgroup n\'existe pas ou vous n\'avez pas l\'autorisation d\'y accéder')
- . '</p>');
+ // build the page
+ if ($pageclass == 'BananaPage') {
+ Banana::load('page');
}
- $artid = empty($banana->get['artid']) ? null : strtolower($banana->get['artid']);
- $partid = !isset($banana->get['part']) ? -1 : $banana->get['part'];
- $action = !isset($banana->get['action']) ? null : $banana->get['action'];
- $banana->state = Array ('group' => $group, 'artid' => $artid, 'action' => $action);
-
- if (is_null($group)) {
- if (isset($banana->get['subscribe'])) {
- $banana->state['page'] = 'subscribe';
- return makeTable($banana->action_listSubs());
- } elseif (isset($_POST['validsubs'])) {
- $banana->action_saveSubs();
- }
- $banana->state['page'] = 'forums';
- return makeTable($banana->action_listGroups());
-
- } elseif (is_null($artid)) {
- if (isset($_POST['action']) && $_POST['action'] == 'new') {
- return makeTable($banana->action_doFup($group, isset($_POST['artid']) ? intval($_POST['artid']) : -1));
- } elseif ($action == 'new') {
- $banana->state['page'] = 'action';
- return makeTable($banana->action_newFup($group));
- } else {
- $banana->state['page'] = 'group';
- return makeTable($banana->action_showThread($group, isset($banana->get['first']) ? intval($banana->get['first']) : 1));
- }
+ Banana::$page = new $pageclass;
+ }
- } else {
- if (isset($_POST['action']) && $_POST['action']=='cancel') {
- $res = $banana->action_cancelArticle($group, $artid);
+ /** Fill state vars (Banana::$group, Banana::$artid, Banana::$action, Banana;:$part, Banana::$first)
+ */
+ protected function loadParams()
+ {
+ Banana::$group = isset($this->params['group']) ? $this->params['group'] : null;
+ Banana::$artid = isset($this->params['artid']) ? $this->params['artid'] : null;
+ Banana::$first = isset($this->params['first']) ? $this->params['first'] : null;
+ Banana::$part = isset($this->params['part']) ? $this->params['part'] : 'text';
+
+ // Look for the action to execute
+ if (is_null(Banana::$group)) {
+ if (isset($this->params['subscribe'])) {
+ Banana::$action = Banana::ACTION_BOX_SUBS;
} else {
- $res = '';
+ Banana::$action = Banana::ACTION_BOX_LIST;
}
-
- if (!is_null($action)) {
- $banana->state['page'] = 'action';
- switch ($action) {
- case 'cancel':
- $res .= $banana->action_showArticle($group, $artid, $partid);
- if ($banana->post->checkcancel()) {
- $form = '<p class="error">'._b_('Voulez-vous vraiment annuler ce message ?').'</p>'
- . '<form action="'
- . htmlentities(makeLink(Array('group' => $group,
- 'artid' => $artid)))
- . '" method="post"><p>'
- . '<input type="hidden" name="action" value="cancel" />'
- . '<input type="submit" value="Annuler !" />'
- . '</p></form>';
- return makeTable($form . $res);
- }
- return makeTable("" . $res);
-
- case 'new':
- return makeTable($banana->action_newFup($group, $artid));
- }
- }
-
- if (isset($banana->get['pj'])) {
- $view = false;
- if ($action == 'view') {
- $view = true;
- }
- $att = $banana->action_getAttachment($group, $artid, $banana->get['pj'], $view);
- return makeTable($res . $att);
+ return;
+ }
+ $action = isset($this->params['action']) ? $this->params['action'] : null;
+ if (is_null(Banana::$artid)) {
+ if ($action == 'new') {
+ Banana::$action = Banana::ACTION_MSG_NEW;
+ } else {
+ Banana::$action = Banana::ACTION_MSG_LIST;
}
-
- $banana->state['page'] = 'message';
- return makeTable($banana->action_showArticle($group, $artid, $partid));
+ return;
+ }
+ switch ($action) {
+ case 'new':
+ Banana::$action = Banana::ACTION_MSG_NEW;
+ return;
+ case 'cancel':
+ Banana::$action = Banana::ACTION_MSG_CANCEL;
+ return;
+ default:
+ Banana::$action = Banana::ACTION_MSG_READ;
}
}
- /**************************************************************************/
- /* actions */
- /**************************************************************************/
-
- function action_saveSubs()
+ /** Register an action to show on banana page
+ * @param action_code HTML code of the action
+ * @param pages ARRAY pages where to show the action (null == every pages)
+ * @return true if success
+ */
+ public function registerAction($action_code, array $pages = null)
{
- return;
+ return Banana::$page->registerAction($action_code, $pages);
}
- function action_listGroups()
+ /** Register a new page
+ * @param name Name of the page
+ * @param text Text for the tab of the page
+ * @param template Template path for the page if null, the page is not handled by banana
+ * @return true if success
+ */
+ public function registerPage($name, $text, $template = null)
{
- $this->_newGroup();
-
- $res = $this->groups->to_html();
- if (count($this->newgroups->overview)) {
- $res .= '<p>'._b_('Les forums suivants ont été créés depuis ton dernier passage :').'</p>';
- $res .= $this->newgroups->to_html();
- }
-
- $this->nntp->quit();
- return $res;
+ return Banana::$page->registerPage($name, $text, $template);
}
- function action_listSubs()
+ /** Run Banana
+ * This function need user profile to be initialised
+ */
+ public function run()
{
- $this->_require('groups');
- $this->groups = new BananaGroups(BANANA_GROUP_ALL);
+ // Configure locales
+ setlocale(LC_ALL, Banana::$profile['locale']);
- $res = $this->groups->to_html(true);
+ // Check if the state is valid
+ if (Banana::$protocole->lastErrNo()) {
+ return Banana::$page->kill(_b_('Une erreur a été rencontrée lors de la connexion au serveur') . '<br />'
+ . Banana::$protocole->lastError());
+ }
+ if (!Banana::$protocole->isValid()) {
+ return Banana::$page->kill(_b_('Connexion non-valide'));
+ }
+ if (Banana::$action & Banana::ACTION_BOX_NEEDED) {
+ if(isset(Banana::$grp_pattern) && !preg_match('/' . Banana::$grp_pattern . '/', $group)) {
+ Banana::$page->setPage('group');
+ return Banana::$page->kill(_b_("Ce newsgroup n'existe pas ou vous n'avez pas l'autorisation d'y accéder"));
+ }
+ }
- $this->nntp->quit();
- return $res;
+ // Dispatch to the action handlers
+ switch (Banana::$action) {
+ case Banana::ACTION_BOX_SUBS:
+ $error = $this->action_subscribe();
+ break;
+ case Banana::ACTION_BOX_LIST:
+ $error = $this->action_listBoxes();
+ break;
+ case Banana::ACTION_MSG_LIST:
+ $error = $this->action_showThread(Banana::$group, Banana::$first);
+ break;
+ case Banana::ACTION_MSG_READ:
+ $error = $this->action_showMessage(Banana::$group, Banana::$artid, Banana::$part);
+ break;
+ case Banana::ACTION_MSG_NEW:
+ $error = $this->action_newMessage(Banana::$group, Banana::$artid);
+ break;
+ case Banana::ACTION_MSG_CANCEL:
+ $error = $this->action_cancelMessage(Banana::$group, Banana::$artid);
+ break;
+ default:
+ $error = _b_("L'action demandée n'est pas supportée par Banana");
+ }
+
+ // Generate the page
+ if (is_string($error)) {
+ return Banana::$page->kill($error);
+ }
+ return Banana::$page->run();
}
- function action_showThread($group, $first)
+ /**************************************************************************/
+ /* actions */
+ /**************************************************************************/
+ protected function action_saveSubs($groups)
{
- if (!$this->_newSpool($group, $this->profile['display'], $this->profile['lastnews'])) {
- return '<p class="error">'._b_('Impossible charger la liste des messages de ') . $group . '</p>';
- }
-
- if ($first > count($this->spool->overview)) {
- $first = count($this->spool->overview);
- }
-
- $first = $first - ($first % $this->tmax) + 1;
-
- $pages = displayPages($first);
- $res = $pages . $this->spool->to_html($first, $first+$this->tmax) . $pages;
-
- $this->nntp->quit();
-
- return $res;
+ Banana::$profile['subscribe'] = $groups;
+ return true;
}
- function action_showArticle($group, $id, $part)
+ protected function action_subscribe()
{
- if (!$this->_newSpool($group, $this->profile['display'], $this->profile['lastnews'])) {
- return '<p class="error">'._b_('Impossible charger la liste des messages de ') . $group . '</p>';
+ Banana::$page->setPage('subscribe');
+ if (isset($_POST['validsubs'])) {
+ $this->action_saveSubs(array_keys($_POST['subscribe']));
+ Banana::$page->redirect();
}
-
- if (!$this->_newPost($id)) {
- if ($this->nntp->lasterrorcode == "423") {
- $this->spool->delid($id);
- }
- $this->nntp->quit();
- return '<p class="error">'._b_('Impossible d\'accéder au message. Le message a peut-être été annulé').'</p>';
- }
-
- $res = $this->post->to_html($part);
-
- $this->nntp->quit();
-
- return $res;
+ $groups = Banana::$protocole->getBoxList(Banana::BOXES_ALL);
+ Banana::$page->assign('groups', $groups);
+ return true;
}
- function action_getAttachment($group, $id, $pjid, $action)
+ protected function action_listBoxes()
{
- if (!$this->_newSpool($group, $this->profile['display'], $this->profile['lastnews'])) {
- return '<p class="error">'._b_('Impossible charger la liste des messages').'</p>';
- }
-
- if (!$this->_newPost($id)) {
- if ($this->nntp->lasterrorcode == "423") {
- $this->spool->delid($id);
- }
- $this->nntp->quit();
- return '<p class="error">'._b_('Impossible d\'accéder au message. Le message a peut-être été annulé').'</p>';
- }
-
- $this->nntp->quit();
- if ($this->post->get_attachment($pjid, $action)) {
- return "";
- } else {
- return '<p calss="error">'._b_('Impossible d\'accéder à la pièce jointe.').'</p>';
- }
+ Banana::$page->setPage('forums');
+ $groups = Banana::$protocole->getBoxList(Banana::BOXES_SUB, Banana::$profile['lastnews'], true);
+ $newgroups = Banana::$protocole->getBoxList(Banana::BOXES_NEW, Banana::$profile['lastnews'], true);
+ Banana::$page->assign('groups', $groups);
+ Banana::$page->assign('newgroups', $newgroups);
+ return true;
}
- function action_cancelArticle($group, $id)
+ protected function action_showThread($group, $first)
{
- if (!$this->_newSpool($group, $this->profile['display'], $this->profile['lastnews'])) {
- return '<p class="error">'._b_('Impossible charger la liste des messages').'</p>';
- }
-
- if (!$this->_newPost($id)) {
- return '<p class="error">'._b_('Impossible de trouver le message à annuler').'</p>';
- }
- $mid = array_search($id, $this->spool->ids);
-
- if (!$this->post->checkcancel()) {
- return '<p class="error">'._b_('Vous n\'avez pas les permissions pour annuler ce message').'</p>';
- }
- $msg = 'From: '.$this->profile['name']."\n"
- . "Newsgroups: $group\n"
- . "Subject: cmsg $mid\n"
- . $this->custom
- . "Control: cancel $mid\n"
- . "\n"
- . "Message canceled with Banana";
- if ($this->nntp->post($msg)) {
- $ndx = $this->spool->getndx($artid) - 1;
- if ($ndx > 50) {
- $ndx = 0;
- }
- $this->spool->delid($id);
- $this->nntp->quit();
- redirectInBanana(Array('group' => $group,
- 'first' => $ndx));
- } else {
- return '<p class="error">'._b_('Impossible d\'annuler le message').'</p>';
+ Banana::$page->setPage('thread');
+ if (!$this->loadSpool($group)) {
+ return _b_('Impossible charger la liste des messages de ') . $group;
}
+ $groups = Banana::$protocole->getBoxList(Banana::BOXES_SUB, Banana::$profile['lastnews'], true);
+ Banana::$page->assign('msgbypage', Banana::$tmax);
+ Banana::$page->assign('groups', $groups);
+ return true;
}
- function action_newFup($group, $id = -1)
+ protected function action_showMessage($group, $artid, $partid = 'text')
{
- $subject = $body = '';
- $target = $group;
-
- if (@$_POST['action'] == 'new') {
- $subject = $_POST['subject'];
- $body = $_POST['body'];
- $target = $_POST['newsgroups'];
- $followup = $_POST['followup'];
- $this->state['page'] = 'action';
- $this->state['group'] = $group;
- $this->state['action'] = 'new';
- if ($id != -1) {
- $this->state['artid'] = $id;
+ Banana::$page->setPage('message');
+ if ($partid == 'text') {
+ $this->loadSpool($group);
+ }
+ $msg =& $this->loadMessage($group, $artid);
+ if (is_null($msg)) {
+ $this->loadSpool($group);
+ $this->removeMessage($group, $artid);
+ return _b_('Le message demandé n\'existe pas. Il est possible qu\'il ait été annulé');
+ }
+ if ($partid == 'xface') {
+ $msg->getXFace();
+ exit;
+ } elseif ($partid != 'text') {
+ $part = $msg->getPartById($partid);
+ if (!is_null($part)) {
+ $part->send(true);
}
- } elseif ($id > 0) {
- $this->nntp->group($group);
- if ($this->_newPost($id)) {
- $subject = 'Re: ' . preg_replace("/^re\s*:\s*/i", '', $this->post->headers['subject']);
- $body = to_entities(utf8_encode($this->post->name." "._b_("a écrit"))." :\n"
- . wrap($this->post->get_body(), "> "))
- . ($this->profile['sig'] ? "\n\n-- \n". $this->profile['sig'] : '');
- $target = isset($this->post->headers['followup-to']) ?
- $this->post->headers['followup-to'] : $this->post->headers['newsgroups'];
- $followup = null;
+ $part = $msg->getFile($partid);
+ if (!is_null($part)) {
+ $part->send();
}
- } else {
- $targe = $group;
- $subject = $followup = null;
- $body = $this->profile['sig'] ? "\n\n-- \n". $this->profile['sig'] : '';
- }
-
- $this->nntp->quit();
-
- $html = '<form enctype="multipart/form-data" action="'
- . htmlentities(makeLink(Array('group' => $group)))
- . '" method="post" accept-charset="utf-8">'
- . '<table class="bicol" cellpadding="0" cellspacing="0">'
- . '<tr><th colspan="2">' . _b_('En-têtes') . '</th></tr>'
- . '<tr><td>' . _b_('Nom') . '</td>'
- . '<td>' . htmlentities($this->profile['name']) . '</td></tr>'
- . '<tr><td>' . _b_('Sujet') . '</td>'
- . '<td><input type="text" name="subject" value="' . htmlentities($subject) . '" size="60" /></td></tr>'
- . '<tr><td>' . _b_('Forums') . '</td>'
- . '<td><input type="text" name="newsgroups" value="' . htmlentities($target) . '" size="60" /></td></tr>'
- . '<tr><td>' . _b_('Suivi à') . '</td>'
- . '<td><input type="text" name="followup" value="' . htmlentities($followup). '" size="60" /></td></tr>'
- . '<tr><td>' . _b_('Organisation') . '</td>'
- . '<td>' . $this->profile['org'] . '</td></tr>'
- . '<tr><th colspan="2">' . _b_('Corps') . '</th></tr>'
- . '<tr><td colspan="2"><textarea name="body" cols="74" rows="16">'
- . $body . '</textarea></td></tr>';
- if ($this->can_attach) {
- $html .= '<tr><th colspan="2">' . _b_('Pièce jointe') . '</th></tr>'
- . '<tr><td colspan="2">'
- . '<input type="hidden" name="MAX_FILE_SIZE" value="' . $this->maxfilesize . '" />'
- . '<input type="file" name="newpj" size="40"/></td></tr>';
- }
- $html .= '<tr><th colspan="2">';
- if ($id != -1) {
- $html .= '<input type="hidden" name="artid" value="' . $id . '" />';
+ exit;
}
- $html .= '<input type="hidden" name="action" value="new" />'
- . '<input type="submit" value="' . _b_('Envoyer le message') . '" /></th></tr>'
- . '</table></form>';
-
- return $html;
+ $groups = Banana::$protocole->getBoxList(Banana::BOXES_SUB, Banana::$profile['lastnews'], true);
+ Banana::$page->assign('groups', $groups);
+ Banana::$page->assign_by_ref('message', $msg);
+ Banana::$page->assign('headers', Banana::$show_hdr);
+ return true;
}
- function action_doFup($group, $artid = -1)
+ protected function action_newMessage($group, $artid)
{
- if ( ! (is_utf8($_POST['subject']) && is_utf8($_POST['body']))) {
- foreach(Array('subject', 'body') as $key) {
- $_POST[$key] = utf8_encode($_POST[$key]);
+ Banana::$page->setPage('new');
+ if (!Banana::$protocole->canSend()) {
+ return _b_('Vous n\'avez pas le droit de poster');
+ }
+ $hdrs = Banana::$protocole->requestedHeaders();
+ $headers = array();
+ foreach ($hdrs as $header) {
+ $headers[$header] = array('name' => BananaMessage::translateHeaderName($header));
+ if (isset(Banana::$profile[$header])) {
+ $headers[$header]['fixed'] = Banana::$profile[$header];
}
}
-
- $forums = preg_split('/\s*(,|;)\s*/', $_POST['newsgroups']);
- $fup = $_POST['followup'];
- if (sizeof($forums) > 1) {
- if (empty($fup)) {
- $fup = $forums[0];
+ if (isset($_POST['sendmessage'])) {
+ $hdr_values = array();
+ foreach ($hdrs as $header) {
+ $hdr_values[$header] = isset($headers[$header]['fixed']) ? $headers[$header]['fixed'] : @$_POST[$header];
}
- }
- $to = implode(',', $forums);
-
- if (!$this->_newSpool($group, $this->profile['display'], $this->profile['lastnews'])) {
- return '<p class="error">'._b_('Impossible charger la liste des messages').'</p>';
- }
-
- $body = preg_replace("/\n\.[ \t\r]*\n/m", "\n..\n", $_POST['body']);
- $msg = 'From: ' . $this->profile['name'] . "\n"
- . "Newsgroups: ". $to . "\n"
- . "Subject: " . headerEncode($_POST['subject'], 128) . "\n"
- . (empty($this->profile['org']) ? '' : "Organization: {$this->profile['org']}\n")
- . (empty($fup) ? '' : 'Followup-To: ' . $fup . "\n");
-
- if ($artid != -1) {
- $this->_require('post');
- $post = new BananaPost($artid);
- if (!$post || !$post->valid) {
- return '<p class="error">'._b_('Impossible charger le message d\'origine').'</p>';
+ if ($artid) {
+ $old =& $this->loadMessage($group, $artid);
+ $hdr_values['References'] = $old->getHeaderValue('references') . $old->getHeaderValue('message-id');
}
- $refs = ( isset($post->headers['references']) ? $post->headers['references']." " : "" );
- $msg .= "References: $refs{$post->headers['message-id']}\n";
- }
-
- $body_headers = $this->custom_plain;
- $body = wrap($body, "");
-
- // include attachment in the body
- $uploaded = $this->_upload('newpj');
- switch ($uploaded['error']) {
- case UPLOAD_ERR_OK:
- $this->custom = $this->custom_mp.$this->custom;
- $body = $this->_make_part($body_headers, $body);
- $file_head = 'Content-Type: '.$uploaded['type'].'; name="'.$uploaded['name']."\"\n"
- . 'Content-Transfer-Encoding: '.$uploaded['encoding']."\n"
- . 'Content-Disposition: attachment; filename="'.$uploaded['name']."\"\n";
- $body .= $this->_make_part($file_head, $uploaded['data']);
- $body .= "\n--".$this->boundary.'--';
- break;
-
- case UPLOAD_ERR_INI_SIZE: case UPLOAD_ERR_FORM_SIZE:
- return '<p class="error">'._b_('Fichier trop gros pour être envoyé : ')
- .$uploaded['name'].'</p>'.$this->action_showThread($group, $artid);
-
- case UPLOAD_ERR_PARTIAL:
- return '<p class="error">'._b_('Erreur lors de l\'upload de ')
- .$uploaded['name'].'</p>'.$this->action_showThread($group, $artid);
-
- case UPLOAD_ERR_NO_FILE:
- return '<p class="error">'._b_('Le fichier spécifié n\'existe pas : ')
- .$uploaded['name'].'</p>'.$this->action_showThread($group, $artid);
-
- case UPLOAD_ERR_NO_TMP_DIR:
- return '<p class="error">'._b_('Une erreur est survenue sur le serveur lors de l\'upload de ')
- .$uploaded['name'].'</p>'.$this->action_showThread($group, $artid);
-
- default:
- $this->custom = $body_headers.$this->custom;
- }
-
- // finalise and post the message
- $msg .= $this->custom.$this->profile['customhdr']."\n".$body;
-
- if ($this->nntp->post($msg)) {
- $dir = Array('group' => $group);
- if ($artid != -1) {
- $dir['artid'] = $artid;
+ $msg = null;
+ if (empty($hdr_values['Subject'])) {
+ Banana::$page->trig(_b_('Le message doit avoir un sujet'));
+ } elseif (Banana::$can_attach && isset($_FILES['attachment'])) {
+ $uploaded = $_FILES['attachment'];
+ if (!is_uploaded_file($uploaded['tmp_name'])) {
+ Banana::$page->trig(_b_('Une erreur est survenue lors du téléchargement du fichier'));
+ } else {
+ $msg = BananaMessage::newMessage($hdr_values, $_POST['body'], $uploaded);
+ }
+ } else {
+ $msg = BananaMessage::newMessage($hdr_values, $_POST['body']);
+ }
+ if (!is_null($msg)) {
+ if (Banana::$protocole->send($msg)) {
+ Banana::$page->redirect(array('group' => $group, 'artid' => $artid));
+ }
+ Banana::$page->trig(_b_('Une erreur est survenue lors de l\'envoi du message :') . '<br />'
+ . Banana::$protocole->lastError());
}
- redirectInBanana($dir);
} else {
- return '<p class="error">' . _b_('Impossible de poster le message. Le serveur a retourné l\'erreur :') . '</p>'
- . '<pre class="error">' . utf8_encode($this->nntp->lasterrortext) .'</pre>'
- . $this->action_newFup($group, $artid);
- }
- }
-
- /**************************************************************************/
- /* Private functions */
- /**************************************************************************/
-
- function _newSpool($group, $disp=0, $since='') {
- $this->_require('spool');
- if (!$this->spool || $this->spool->group != $group) {
- $this->spool = new BananaSpool($group, $disp, $since);
- if (!$this->spool || !$this->spool->valid) {
- $this->spool = null;
- return false;
+ if (!is_null($artid)) {
+ $msg =& $this->loadMessage($group, $artid);
+ $body = $msg->getSender() . _b_(' a écrit :') . "\n" . $msg->quote();
+ $subject = $msg->getHeaderValue('subject');
+ $headers['Subject']['user'] = 'Re: ' . preg_replace("/^re\s*:\s*/i", '', $subject);
+ $target = $msg->getHeaderValue($hdrs['reply']);
+ if (empty($target)) {
+ $target = $group;
+ }
+ $headers[$hdrs['dest']]['user'] =& $target;
+ } else {
+ $body = '';
+ $headers[$hdrs['dest']]['user'] = $group;
}
+ if (Banana::$profile['sig']) {
+ $body .= "\n\n-- \n" . Banana::$profile['sig'];
+ }
+ Banana::$page->assign('body', $body);
}
- if (count($this->profile['subscribe']) > 0) {
- $this->_newGroup(false);
- }
+
+ Banana::$page->assign('maxfilesize', Banana::$maxfilesize);
+ Banana::$page->assign('can_attach', Banana::$can_attach);
+ Banana::$page->assign('headers', $headers);
return true;
}
- function _newPost($id)
+ protected function action_cancelMessage($group, $artid)
{
- $this->_require('post');
- $this->post = new BananaPost($id);
- if (!$this->post || !$this->post->valid) {
- $this->post = null;
- return false;
+ Banana::$page->setPage('cancel');
+ $msg =& $this->loadMessage($group, $artid);
+ if (!$msg->canCancel()) {
+ return _b_('Vous n\'avez pas les droits suffisants pour supprimer ce message');
+ }
+ if (isset($_POST['cancel'])) {
+ $this->loadSpool($group);
+ $ndx = Banana::$spool->getNdX($id) - 1;
+ if (!Banana::$protocole->cancel($msg)) {
+ return _b_('Une erreur s\'est produite lors de l\'annulation du message :') . '<br />'
+ . Banana::$protocole->lastError();
+ }
+ if ($ndx < 50) {
+ $ndx = 0;
+ }
+ $this->removeMessage($group, $artid);
+ Banana::$page->redirect(Array('group' => $group, 'first' => $ndx));
}
+ Banana::$page->assign_by_ref('message', $msg);
return true;
}
- function _newGroup($showNew = true)
+ /**************************************************************************/
+ /* Private functions */
+ /**************************************************************************/
+
+ private function loadSpool($group)
{
- $this->_require('groups');
- $this->groups = new BananaGroups(BANANA_GROUP_SUB);
- if ($showNew && $this->groups->type == BANANA_GROUP_SUB) {
- $this->newgroups = new BananaGroups(BANANA_GROUP_NEW);
+ Banana::load('spool');
+ if (!Banana::$spool || Banana::$spool->group != $group) {
+ if ($group == @$_SESSION['banana_group'] && isset($_SESSION['banana_spool'])) {
+ Banana::$spool = unserialize($_SESSION['banana_spool']);
+ }
+ BananaSpool::getSpool($group, Banana::$profile['lastnews']);
+ $_SESSION['banana_group'] = $group;
+ $_SESSION['banana_spool'] = serialize(Banana::$spool);
+ Banana::$spool->setMode(Banana::$profile['display'] ? Banana::SPOOL_UNREAD : Banana::SPOOL_ALL);
}
+ return true;
}
- function _require($file)
+ private function &loadMessage($group, $artid)
{
- require_once (dirname(__FILE__).'/'.$file.'.inc.php');
+ Banana::load('message');
+ if ($group == @$_SESSION['banana_group'] && $artid == @$_SESSION['banana_artid']
+ && isset($_SESSION['banana_message'])) {
+ $message = unserialize($_SESSION['banana_message']);
+ Banana::$show_hdr = $_SESSION['banana_showhdr'];
+ } else {
+ $message = Banana::$protocole->getMessage($artid);
+ $_SESSION['banana_group'] = $group;
+ $_SESSION['banana_artid'] = $artid;
+ $_SESSION['banana_message'] = serialize($message);
+ $_SESSION['banana_showhdr'] = Banana::$show_hdr;
+ }
+ Banana::$message =& $message;
+ return $message;
}
- function _upload($file)
+ private function removeMessage($group, $artid)
{
- if ($_FILES[$file]['name'] == "") {
- return Array( 'error' => -1 );
- }
-
- // upload
- $_FILES[$file]['tmp_name'];
-
- // test if upload is ok
- $file = $_FILES[$file];
- if ($file['size'] == 0 || $file['error'] != 0) {
- if ($file['error'] == 0) {
- $file['error'] = -1;
+ Banana::$spool->delId($artid);
+ if ($group == $_SESSION['banana_group']) {
+ $_SESSION['banana_spool'] = serialize(Banana::$spool);
+ if ($artid == $_SESSION['banana_artid']) {
+ unset($_SESSION['banana_message']);
+ unset($_SESSION['banana_showhdr']);
+ unset($_SESSION['banana_artid']);
}
- return $file;
- }
-
- // adding custum data
- $mime = rtrim(shell_exec('file -bi '.$file['tmp_name'])); //Because mime_content_type don't work :(
- $encod = 'base64';
- if (preg_match("@([^ ]+/[^ ]+); (.*)@", $mime, $format)) {
- $mime = $format[1];
- $encod = $format[2];
- }
- $data = fread(fopen($file['tmp_name'], 'r'), $file['size']);
- if ($encod == 'base64') {
- $data = chunk_split(base64_encode($data));
}
- $file['name'] = basename($file['name']);
- $file['type'] = $mime;
- $file['encoding'] = $encod;
- $file['data'] = $data;
-
- return $file;
+ return true;
}
- function _make_part($headers, $body)
+ static private function load($file)
{
- return "\n--".$this->boundary."\n".$headers."\n".$body;
+ $file = strtolower($file) . '.inc.php';
+ if (!@include_once dirname(__FILE__) . "/$file") {
+ require_once $file;
+ }
}
}
+++ /dev/null
-<?php
-/********************************************************************************
-* include/groups.inc.php : class for group lists
-* ------------------------
-*
-* This file is part of the banana distribution
-* Copyright: See COPYING files that comes with this distribution
-********************************************************************************/
-
-/** class for group lists
- */
-
-define ( 'BANANA_GROUP_ALL', 0 );
-define ( 'BANANA_GROUP_SUB', 1 );
-define ( 'BANANA_GROUP_NEW', 2 );
-
-class BananaGroups {
- /** group list */
- var $overview = Array();
- /** last update */
- var $date;
-
- var $type;
-
- /** constructor
- */
-
- function BananaGroups($_type = BANANA_GROUP_SUB) {
- global $banana;
-
- $this->type = $_type;
- $this->load();
-
- if (empty($this->overview) && $_type == BANANA_GROUP_SUB) {
- $this->type = BANANA_GROUP_ALL;
- $this->load();
- }
- }
-
- /** Load overviews
- */
- function load()
- {
- global $banana;
-
- $desc = $banana->nntp->xgtitle();
- if ($this->type == BANANA_GROUP_NEW) {
- $list = $banana->nntp->newgroups($banana->profile['lastnews']);
- } else {
- $list = $banana->nntp->liste();
- if ($this->type == BANANA_GROUP_SUB) {
- $mylist = Array();
- foreach ($banana->profile['subscribe'] as $g) {
- if (isset($list[$g])) {
- $mylist[$g] = $list[$g];
- }
- }
- $list = $mylist;
- }
- }
-
- foreach ($list as $g=>$l) {
- $this->overview[$g][0] = isset($desc[$g]) ? $desc[$g] : '-';
- $this->overview[$g][1] = $l[0];
- }
- ksort($this->overview);
- }
-
- /** updates overview
- * @param date INTEGER date of last update
- */
- function update($_date) {
- global $banana;
- $serverdate = $banana->nntp->date();
- if (!$serverdate) $serverdate=time();
- $newlist = $banana->nntp->newgroups($_date);
- if (!$newlist) return false;
- $this->date = $serverdate;
- foreach (array_keys($newlist) as $g) {
- $groupstat = $banana->nntp->group($g);
- $groupdesc = $banana->nntp->xgtitle($g);
- $this->overview[$g][0]=($groupdesc?$groupdesc:"-");
- $this->overview[$g][1]=$groupstat[0];
- }
- return true;
- }
-
- function to_html($show_form = false)
- {
- global $banana;
- if (empty($this->overview)) {
- return;
- }
-
- $html = '<table class="bicol banana_group" cellspacing="0" cellpadding="2">'."\n";
- $html .= '<tr><th>'._b_('Total').'</th><th>';
- if ($show_form) {
- $html .= _b_('Abo.').'</th><th>';
- } elseif ($this->type == BANANA_GROUP_SUB) {
- $html .= _b_('Nouveaux').'</th><th>';
- }
- $html .= _b_('Nom').'</th><th>'._b_('Description').'</th></tr>'."\n";
-
- $b = true;
- foreach ($this->overview as $g => $d) {
- $b = !$b;
- $ginfo = $banana->nntp->group($g);
- $new = count($banana->nntp->newnews($banana->profile['lastnews'],$g));
-
- $html .= '<tr class="'.($b ? 'pair' : 'impair').'">'."\n";
- $html .= "<td class='all'>{$ginfo[0]}</td>";
- if ($show_form) {
- $html .= '<td class="new"><input type="checkbox" name="subscribe[]" value="'.$g.'"';
- if (in_array($g, $banana->profile['subscribe'])) {
- $html .= ' checked="checked"';
- }
- $html .= ' /></td>';
- } elseif ($this->type == BANANA_GROUP_SUB) {
- $html .= '<td class="new">'.($new ? $new : '-').'</td>';
- }
- $html .= '<td class="grp">' . makeHREF(Array('group' => $g), $g) . '</td><td class="dsc">' . $d[0] . '</td></tr>';
- }
-
- $html .= '</table>';
-
- if ($show_form) {
- return '<form method="post" action="' . htmlentities(makeLink(Array())) . '">'
- . '<div class="center"><input type="submit" value="Valider" name="validsubs" /></div>'
- . $html . '<div class="center"><input type="submit" value="Valider" name="validsubs" /></div></form>';
- }
-
- return $html;
- }
-}
-
-// vim:set et sw=4 sts=4 ts=4
-?>
--- /dev/null
+<?php
+/********************************************************************************
+* banana/protocoleinterface.inc.php : interface for box access
+* ------------------------
+*
+* This file is part of the banana distribution
+* Copyright: See COPYING files that comes with this distribution
+********************************************************************************/
+
+require_once dirname(__FILE__) . '/banana.inc.php';
+require_once dirname(__FILE__) . '/protocoleinterface.inc.php';
+require_once dirname(__FILE__) . '/message.inc.php';
+
+class BananaMBox implements BananaProtocoleInterface
+{
+ private $boxname;
+
+ private $file = null;
+ private $filesize = null;
+ private $current_id = null;
+ private $at_beginning = false;
+ private $file_cache = null;
+
+ private $_lasterrno = 0;
+ private $_lasterror = null;
+
+ private $count = null;
+ private $new_messages = null;
+ private $messages = null;
+
+ /** Build a protocole handler plugged on the given box
+ */
+ public function __construct($box = null)
+ {
+ $this->boxname = $box;
+ $filename = $this->getFileName($box);
+ if (is_null($filename)) {
+ return;
+ }
+ $this->filesize = filesize($filename);
+ $this->file = @fopen($filename, 'r');
+ if (!$this->file) {
+ $this->_lasterrno = 1;
+ $this->_lasterror = _b_('Can\'t open file');
+ $this->file = null;
+ }
+ $this->current_id = 0;
+ $this->at_beginning = true;
+ }
+
+ /** Close the file
+ */
+ public function __destruct()
+ {
+ if ($this->file) {
+ fclose($this->file);
+ }
+ }
+
+ /** Indicate if the Protocole handler has been succesfully built
+ */
+ public function isValid()
+ {
+ return is_null($this->boxname) || !is_null($this->file);
+ }
+
+ /** Indicate last error n°
+ */
+ public function lastErrNo()
+ {
+ return $this->_lasterrno;;
+ }
+
+ /** Indicate last error text
+ */
+ public function lastError()
+ {
+ return $this->_lasterror;
+ }
+
+ /** Return the description of the current box
+ */
+ public function getDescription()
+ {
+ return null;
+ }
+
+ /** Return the list of the boxes
+ * @param mode Kind of boxes to list
+ * @param since date of last check (for new boxes and new messages)
+ * @param withstats Indicated whether msgnum and unread must be set in the result
+ * @return Array(boxname => array(desc => boxdescripton, msgnum => number of message, unread =>number of unread messages)
+ */
+ public function getBoxList($mode = Banana::BOXES_ALL, $since = 0, $withstats = false)
+ {
+ return array($this->boxname => array('desc' => '', 'msgnum' => 0, 'unread' => 0));
+ }
+
+ /** Return a message
+ * @param id Id of the emssage (can be either an Message-id or a message index)
+ * @param msg_headers Headers to process
+ * @param is_msgid If is set, $id is en Message-Id
+ * @return A BananaMessage or null if the given id can't be retreived
+ */
+ public function getMessage($id, array $msg_headers = array(), $is_msgid = false)
+ {
+ if ($is_msgid || !is_numeric($id)) {
+ if (is_null(Banana::$spool)) {
+ return null;
+ }
+ $id = Banana::$spool->ids[$id];
+ }
+ $message = $this->readMessages(array($id));
+ if (empty($message)) {
+ return null;
+ }
+ $msg = new BananaMessage($message[$id]['message']);
+ return $msg;
+ }
+
+ private function getCount()
+ {
+ $this->count = count(Banana::$spool->overview);
+ $max = @max(array_keys(Banana::$spool->overview));
+ if ($max && Banana::$spool->overview[$max]->storage['next'] == $this->filesize) {
+ $this->new_messages = 0;
+ } else {
+ $this->new_messages = $this->countMessages($this->count);
+ $this->count += $this->new_messages;
+ }
+ }
+
+ /** Return the indexes of the messages presents in the Box
+ * @return Array(number of messages, MSGNUM of the first message, MSGNUM of the last message)
+ */
+ public function getIndexes()
+ {
+ if (is_null($this->count)) {
+ $this->getCount();
+ }
+ return array($this->count, 0, $this->count - 1);
+ }
+
+ /** Return the message headers (in BananaMessage) for messages from firstid to lastid
+ * @return Array(id => array(headername => headervalue))
+ */
+ public function &getMessageHeaders($firstid, $lastid, array $msg_headers = array())
+ {
+ $msg_headers = array_map('strtolower', $msg_headers);
+ $messages =& $this->readMessages(range($firstid, $lastid), true);
+ $msg_headers = array_map('strtolower', $msg_headers);
+ $headers = array();
+ foreach ($msg_headers as $header) {
+ foreach ($messages as $id=>&$message) {
+ if (!isset($headers[$id])) {
+ $headers[$id] = array('beginning' => $message['beginning'], 'end' => $message['end']);
+ }
+ if ($header == 'date') {
+ $headers[$id][$header] = strtotime($message['message'][$header]);
+ } else {
+ $headers[$id][$header] = $message['message'][$header];
+ }
+ }
+ }
+ unset($this->messages);
+ unset($messages);
+ return $headers;
+ }
+
+ /** Add storage data in spool overview
+ */
+ public function updateSpool(array &$messages)
+ {
+ foreach ($messages as $id=>&$data) {
+ if (isset(Banana::$spool->overview[$id])) {
+ Banana::$spool->overview[$id]->storage['offset'] = $data['beginning'];
+ Banana::$spool->overview[$id]->storage['next'] = $data['end'];
+ }
+ }
+ }
+
+ /** Return the indexes of the new messages since the give date
+ * @return Array(MSGNUM of new messages)
+ */
+ public function getNewIndexes($since)
+ {
+ if (is_null($this->new_messages)) {
+ $this->getCount();
+ }
+ return range($this->count - $this->new_messages, $this->count - 1);
+ }
+
+ /** Return wether or not the protocole can be used to add new messages
+ */
+ public function canSend()
+ {
+ return true;
+ }
+
+ /** Return false because we can't cancel a mail
+ */
+ public function canCancel()
+ {
+ return false;
+ }
+
+ /** Return the list of requested headers
+ * @return Array('header1', 'header2', ...) with the key 'dest' for the destination header
+ * and 'reply' for the reply header, eg:
+ * * for a mail: Array('From', 'Subject', 'dest' => 'To', 'Cc', 'Bcc', 'reply' => 'Reply-To')
+ * * for a post: Array('From', 'Subject', 'dest' => 'Newsgroups', 'reply' => 'Followup-To')
+ */
+ public function requestedHeaders()
+ {
+ return Array('From', 'Subject', 'dest' => 'To', 'Cc', 'Bcc', 'reply' => 'Reply-To');
+ }
+
+ /** Send a message
+ * @return true if it was successfull
+ */
+ public function send(BananaMessage &$message)
+ {
+ return true;
+ }
+
+ /** Cancel a message
+ * @return true if it was successfull
+ */
+ public function cancel(BananaMessage &$message)
+ {
+ return false;
+ }
+
+ /** Return the protocole name
+ */
+ public function name()
+ {
+ return 'MBOX';
+ }
+
+#######
+# Filesystem functions
+#######
+
+ protected function getFileName($box)
+ {
+ if (is_null($box)) {
+ return null;
+ }
+ @list($mail, $domain) = explode('@', $box);
+ if ($mail == 'staff') {
+ return '/home/x2003bruneau/staff.polytechnique.org_innovation.mbox';
+ } else {
+ return '/var/mail/' . $mail;
+ }
+ }
+
+#######
+# MBox parser
+#######
+
+ /** Go to the given message
+ */
+ private function goTo($id)
+ {
+ if ($this->current_id == $id && $this->at_beginning) {
+ return true;
+ }
+ if ($id == 0) {
+ fseek($this->file, 0);
+ $this->current_id = 0;
+ $this->at_beginning = true;
+ return true;
+ } elseif (isset(Banana::$spool->overview[$id]) || isset($this->messages[$id])) {
+ if (isset(Banana::$spool->overview[$id])) {
+ $pos = Banana::$spool->overview[$id]->storage['offset'];
+ } else {
+ $pos = $this->messages[$id]['beginning'];
+ }
+ if (fseek($this->file, $pos) == 0) {
+ $this->current_id = $id;
+ $this->at_beginning = true;
+ return true;
+ } else {
+ $this->current_id = null;
+ $this->_lasterrno = 2;
+ $this->_lasterror = _b_('Can\'t find message ') . $id;
+ return false;
+ }
+ } else {
+ $max = @max(array_keys(Banana::$spool->overview));
+ if (is_null($max)) {
+ $max = 0;
+ }
+ if ($id <= $max && $max != 0) {
+ $this->current_id = null;
+ $this->_lasterrno = 3;
+ $this->_lasterror = _b_('Invalid message index ') . $id;
+ return false;
+ }
+ if (!$this->goTo($max)) {
+ return false;
+ }
+ if (feof($this->file)) {
+ $this->current_id = null;
+ $this->_lasterrno = 4;
+ $this->_lasterror = _b_('Requested index does not exists or file has been truncated');
+ return false;
+ }
+ while ($this->readCurrentMessage(true) && $this->current_id < $id);
+ if ($this->current_id == $id) {
+ return true;
+ }
+ $this->current_id = null;
+ $this->_lasterrno = 5;
+ $this->_lasterror = _b_('Requested index does not exists or file has been truncated');
+ return false;
+ }
+ }
+
+ private function countMessages($from = 0)
+ {
+ $this->messages =& $this->readMessages(array($from), true, true);
+ return count($this->messages);
+ }
+
+ /** Read the current message (identified by current_id)
+ * @param needFrom_ BOOLEAN is true if the first line *must* be a From_ line
+ * @param alignNext BOOLEAN is true if the buffer must be aligned at the beginning of the next From_ line
+ * @return message sources (without storage data)
+ */
+ private function &readCurrentMessage($stripBody = false, $needFrom_ = true, $alignNext = true)
+ {
+ $file_cache =& $this->file_cache;
+ if ($file_cache && $file_cache != ftell($this->file)) {
+ $file_cache = null;
+ }
+ $msg = array();
+ $canFrom_ = false;
+ $inBody = false;
+ while(!feof($this->file)) {
+ // Process file cache
+ if ($file_cache) { // this is a From_ line
+ $needFrom_ = false;
+ $this->at_beginning = false;
+ $file_cache = null;
+ continue;
+ }
+
+ // Read a line
+ $line = rtrim(fgets($this->file), "\r\n");
+
+ // Process From_ line
+ if ($needFrom_ || !$msg || $canFrom_) {
+ if (substr($line, 0, 5) == 'From ') { // this is a From_ line
+ if ($needFrom_) {
+ $needFrom = false;
+ } elseif (!$msg) {
+ continue;
+ } else {
+ $this->current_id++; // we are finally in the next message
+ if ($alignNext) { // align the file pointer at the beginning of the new message
+ $this->at_beginning = true;
+ $file_cache = ftell($this->file);
+ }
+ break;
+ }
+ } elseif ($needFrom_) {
+ return $msg;
+ }
+ }
+
+ // Process non-From_ lines
+ if (substr($line, 0, 6) == '>From ') { // remove inline From_ quotation
+ $line = substr($line, 1);
+ }
+ if (!$stripBody || !$inBody) {
+ $msg[] = $line; // add the line to the message source
+ }
+ $canFrom_ = empty($line); // check if next line can be a From_ line
+ if ($canFrom_ && !$inBody && $stripBody) {
+ $inBody = true;
+ }
+ $this->at_beginning = false;
+ }
+ if (!feof($this->file) && !$canFrom_) {
+ $msg = array();
+ }
+ return $msg;
+ }
+
+ /** Read message with the given ids
+ * @param ids ARRAY of ids to look for
+ * @param strip BOOLEAN if true, only headers are retrieved
+ * @param from BOOLEAN if true, process all messages from max(ids) to the end of the mbox
+ * @return Array(Array('message' => message sources (or parsed message headers if $strip is true),
+ * 'beginning' => offset of message beginning,
+ * 'end' => offset of message end))
+ */
+ private function &readMessages(array $ids, $strip = false, $from = false)
+ {
+ if (!is_null($this->messages)) {
+ return $this->messages;
+ }
+ sort($ids);
+ $messages = array();
+ while ((count($ids) || $from) && !feof($this->file)) {
+ if (count($ids)) {
+ $id = array_shift($ids);
+ } else {
+ $id++;
+ }
+ if ($id != $this->current_id || !$this->at_beginning) {
+ if (!$this->goTo($id)) {
+ continue;
+ }
+ }
+ $beginning = ftell($this->file);
+ $message =& $this->readCurrentMessage($strip, false);
+ if ($strip) {
+ $message =& BananaMimePart::parseHeaders($message);
+ }
+ $end = ftell($this->file);
+ $messages[$id] = array('message' => $message, 'beginning' => $beginning, 'end' => $end);
+ }
+ return $messages;
+ }
+}
+
+// vim:set et sw=4 sts=4 ts=4:
+?>
--- /dev/null
+<?php
+/********************************************************************************
+ * * banana/message.func.inc.php : function to display messages
+ * * ------------------------
+ * *
+ * * This file is part of the banana distribution
+ * * Copyright: See COPYING files that comes with this distribution
+ * ********************************************************************************/
+
+require_once dirname(__FILE__) . '/mimepart.inc.php';
+require_once dirname(__FILE__) . '/banana.inc.php';
+
+// {{{ Plain Text Functions
+
+function banana_isFlowed($line)
+{
+ return ctype_space(substr($line, -1)) && $line != '-- ';
+}
+
+function banana_removeQuotes($line, &$quote_level, $strict = true)
+{
+ $quote_level = 0;
+ if (empty($line)) {
+ return '';
+ }
+ while ($line{0} == '>') {
+ $line = substr($line, 1);
+ if (!$strict && ctype_space($line{0})) {
+ $line = substr($line, 1);
+ }
+ $quote_level++;
+ }
+ if (ctype_space($line{0})) {
+ $line = substr($line, 1);
+ }
+ return $line;
+}
+
+function banana_quote($line, $level, $mark = '>')
+{
+ $lines = explode("\n", $line);
+ foreach ($lines as &$line) {
+ if ($level > 0 && substr($line, 0, strlen($mark)) != $mark) {
+ $line = ' ' . $line;
+ }
+ for ($i = 0 ; $i < $level ; $i++) {
+ $line = $mark . $line;
+ }
+ }
+ return implode("\n", $lines);
+}
+
+function banana_unflowed($text)
+{
+ $lines = explode("\n", $text);
+ $text = '';
+ while (!is_null($line = array_shift($lines))) {
+ $level = 0;
+ $line = banana_removeQuotes($line, $level);
+ while (banana_isFlowed($line)) {
+ $lvl = 0;
+ if (is_null($nl = array_shift($lines))) {
+ break;
+ }
+ $nl = banana_removeQuotes($nl, $lvl);
+ $line .= $nl;
+ }
+ $text .= banana_quote($line, $level) . "\n";
+ }
+ return $text;
+}
+
+function banana_wordwrap($text, $quote_level)
+{
+ if ($quote_level > 0) {
+ $length = Banana::$wrap - $quote_level - 1;
+ return banana_quote(wordwrap($text, $length), $quote_level);
+
+ }
+ return wordwrap($text, Banana::$wrap);
+}
+
+function banana_catchFormats($text)
+{
+ $formatting = Array('/' => 'em', // match / first in order not to match closing markups </...> <> </>
+ '_' => 'u',
+ '*' => 'strong');
+ $url = Banana::$url_regexp;
+ preg_match_all("/$url/i", $text, $urls);
+ $text = str_replace($urls[0], "&&&urls&&&", $text);
+ foreach ($formatting as $limit=>$mark) {
+ $limit = preg_quote($limit, '/');
+ $text = preg_replace("/$limit\\b(.*?)\\b$limit/s",
+ "<$mark>\\1</$mark>", $text);
+ }
+ return preg_replace('/&&&urls&&&/e', 'array_shift($urls[0])', $text);
+}
+
+// {{{ URL Catcher tools
+
+function banana__cutlink($link)
+{
+ $link = banana_html_entity_decode($link, ENT_QUOTES);
+ if (strlen($link) > Banana::$wrap) {
+ $link = substr($link, 0, Banana::$wrap - 3) . "...";
+ }
+ return banana_htmlentities($link, ENT_QUOTES);
+}
+
+function banana__cleanURL($url)
+{
+ $url = str_replace('@', '%40', $url);
+ if (strpos($url, '://') === false) {
+ $url = 'http://' . $url;
+ }
+ return '<a href="'.$url.'" title="'.$url.'">' . banana__cutlink($url) . '</a>';
+}
+
+function banana__catchMailLink($email)
+{
+ $mid = '<' . $email . '>';
+ if (isset(Banana::$spool->ids[$mid])) {
+ return Banana::$page->makeLink(Array('group' => Banana::$group,
+ 'artid' => Banana::$spool->ids[$mid],
+ 'text' => $email));
+ } elseif (strpos($email, '$') !== false) {
+ return $email;
+ }
+ return '<a href="mailto:' . $email . '">' . $email . '</a>';
+}
+
+// }}}
+
+function banana_catchURLs($text)
+{
+ $url = Banana::$url_regexp;
+
+ $res = preg_replace("/&(lt|gt|quot);/", " &\\1; ", $text);
+ $res = preg_replace("/$url/ie", "'\\1'.banana__cleanurl('\\2').'\\3'", $res);
+ $res = preg_replace('/(["\[])?(?:mailto:|news:)?([a-z0-9.\-+_\$]+@([\-.+_]?[a-z0-9])+)(["\]])?/ie',
+ "'\\1' . banana__catchMailLink('\\2') . '\\4'",
+ $res);
+ $res = preg_replace("/ &(lt|gt|quot); /", "&\\1;", $res);
+ return $res;
+}
+
+// {{{ Quotes catcher functions
+
+function banana__replaceQuotes($text, $regexp)
+{
+ return stripslashes(preg_replace("@(^|<pre>|\n)$regexp@i", '\1', $text));
+}
+
+// }}}
+
+function banana_catchQuotes($res, $strict = true)
+{
+ if ($strict) {
+ $regexp = ">";
+ } else {
+ $regexp = "> *";
+ }
+ while (preg_match("/(^|<pre>|\n)$regexp/i", $res)) {
+ $res = preg_replace("/(^|<pre>|\n)(($regexp.*(?:\n|$))+)/ie",
+ "'\\1</pre><blockquote><pre>'"
+ ." . banana__replaceQuotes('\\2', '$regexp')"
+ ." . '</pre></blockquote><pre>'",
+ $res);
+ }
+ return $res;
+}
+
+function banana_catchSignature($res)
+{
+ $res = preg_replace("@<pre>-- ?\n@", "<pre>\n-- \n", $res);
+ $parts = preg_split("/\n-- ?\n/", $res);
+ $sign = '</pre><hr style="width: 100%; margin: 1em 0em; " /><pre>';
+ return join($sign, $parts);
+}
+
+function banana_plainTextToHtml($text, $strict = true)
+{
+ $text = banana_htmlentities($text);
+ $text = banana_catchFormats($text);
+ $text = banana_catchURLs($text);
+ $text = banana_catchQuotes($text, $strict);
+ $text = banana_catchSignature($text);
+ return banana_cleanHtml('<pre>' . $text . '</pre>');
+}
+
+function banana_wrap($text, $base_level = 0, $strict = true)
+{
+ $lines = explode("\n", $text);
+ $text = '';
+ $buffer = array();
+ $level = 0;
+ while (!is_null($line = array_shift($lines))) {
+ $lvl = 0;
+ $line = banana_removeQuotes($line, $lvl, $strict);
+ if($lvl != $level && !empty($buffer)) {
+ $text .= banana_wordwrap(implode("\n", $buffer), $level + $base_level) . "\n";
+ $level = $lvl;
+ $buffer = array();
+ }
+ $buffer[] = $line;
+ }
+ if (!empty($buffer)) {
+ $text .= banana_wordwrap(implode("\n", $buffer), $level + $base_level);
+ }
+ return $text;
+}
+
+function banana_formatPlainText(BananaMimePart &$part, $base_level = 0)
+{
+ $text = $part->getText();
+ if ($part->isFlowed()) {
+ $text = banana_unflowed($text);
+ }
+ $text = banana_wrap($text, $base_level, $part->isFlowed());
+ return banana_plainTextToHtml($text, $part->isFlowed());
+}
+
+function banana_quotePlainText(BananaMimePart &$part)
+{
+ $text = $part->getText();
+ if ($part->isFlowed()) {
+ $text = banana_unflowed($text);
+ }
+ return banana_wrap($text, 1);
+}
+
+// }}}
+// {{{ HTML Functions
+
+function banana_htmlentities($text, $quote = ENT_COMPAT)
+{
+ return htmlentities($text, $quote, 'UTF-8');
+}
+
+function banana_html_entity_decode($text, $quote = ENT_COMPAT)
+{
+ return html_entity_decode($text, $quote, 'UTF-8');
+}
+
+function banana_removeEvilAttributes($tagSource)
+{
+ $stripAttrib = 'javascript:|onclick|ondblclick|onmousedown|onmouseup|onmouseover|'.
+ 'onmousemove|onmouseout|onkeypress|onkeydown|onkeyup';
+ return stripslashes(preg_replace("/$stripAttrib/i", '', $tagSource));
+}
+
+/**
+ * @return string
+ * @param string
+ * @desc Strip forbidden tags and delegate tag-source check to removeEvilAttributes()
+ */
+function banana_cleanHtml($source)
+{
+ $allowedTags = '<h1><b><i><a><ul><li><pre><hr><blockquote><img><br><font><div>'
+ . '<p><small><big><sup><sub><code><em><strong><table><tr><td><th>';
+ $source = strip_tags($source, $allowedTags);
+ $source = preg_replace('/<(.*?)>/ie', "'<'.banana_removeEvilAttributes('\\1').'>'", $source);
+
+ if (function_exists('tidy_repair_string')) {
+ $tidy_on = Array(
+ 'drop-empty-paras', 'drop-proprietary-attributes',
+ 'hide-comments', 'logical-emphasis', 'output-xhtml',
+ 'replace-color', 'show-body-only'
+ );
+ $tidy_off = Array('join-classes', 'clean'); // 'clean' may be a good idea, but it is too aggressive
+
+ foreach($tidy_on as $opt) {
+ tidy_setopt($opt, true);
+ }
+ foreach($tidy_off as $opt) {
+ tidy_setopt($opt, false);
+ }
+ tidy_setopt('alt-text', '[ inserted by TIDY ]');
+ tidy_setopt('wrap', '120');
+ tidy_set_encoding('utf8');
+ return tidy_repair_string($source);
+ }
+ return $source;
+}
+
+function banana_catchHtmlSignature($res)
+{
+ $res = preg_replace("@(</p>)\n?-- ?\n?(<p[^>]*>|<br[^>]*>)@", "\\1<br/>-- \\2", $res);
+ $res = preg_replace("@<br[^>]*>\n?-- ?\n?(<p[^>]*>)@", "<br/>-- <br/>\\2", $res);
+ $res = preg_replace("@(<pre[^>]*>)\n?-- ?\n@", "<br/>-- <br/>\\1", $res);
+ $parts = preg_split("@(:?<p[^>]*>\n?-- ?\n?</p>|<br[^>]*>\n?-- ?\n?<br[^>]*>)@", $res);
+ $sign = '<hr style="width: 100%; margin: 1em 0em; " />';
+ return join($sign, $parts);
+}
+
+// {{{ Link to part catcher tools
+
+function banana__linkAttachment($cid)
+{
+ return banana_htmlentities(
+ Banana::$page->makeUrl(Array('group' => Banana::$group,
+ 'artid' => Banana::$artid,
+ 'part' => $cid)));
+}
+
+// }}}
+
+function banana_hideExternalImages($text)
+{
+ return preg_replace("/<img[^>]*?src=['\"](?!cid).*?>/i",
+ Banana::$page->makeImg(array('img' => 'invalid')),
+ $text);
+}
+
+function banana_catchPartLinks($text)
+{
+ return preg_replace('/cid:([^\'" ]+)/e', "banana__linkAttachment('\\1')", $text);
+}
+
+// {{{ HTML to Plain Text tools
+
+function banana__convertFormats($res)
+{
+ $table = array('em|i' => '/',
+ 'strong|b' => '*',
+ 'u' => '_');
+ foreach ($table as $tags=>$format) {
+ $res = preg_replace("!</?($tags)( .*?)?>!is", $format, $res);
+ }
+ return $res;
+}
+
+function banana__convertQuotes($res)
+{
+ return preg_replace('!<blockquote.*?>([^<]*)</blockquote>!ies',
+ "\"\n\" . banana_quote(banana__convertQuotes('\\1' . \"\n\"), 1, '>')",
+ $res);
+}
+
+// }}}
+
+function banana_htmlToPlainText($res)
+{
+ $res = str_replace("\n", '', $res);
+ $res = banana__convertFormats($res);
+ $res = trim(strip_tags($res, '<div><br><p><blockquote>'));
+ $res = preg_replace("@</?(br|p|div).*?>@si", "\n", $res);
+ $res = banana__convertQuotes($res);
+ return banana_html_entity_decode($res);
+}
+
+function banana_formatHtml(BananaMimePart &$part)
+{
+ $text = $part->getText();
+ $text = banana_catchHtmlSignature($text);
+ $text = banana_hideExternalImages($text);
+ $text = banana_catchPartLinks($text);
+ return banana_cleanHtml($text);
+}
+
+function banana_quoteHtml(BananaMimePart &$part)
+{
+ $text = $part->getText();
+ $text = banana_htmlToPlainText($text);
+ return banana_wrap($text, 1);
+}
+
+// }}}
+// {{{ Richtext Functions
+
+/** Convert richtext to html
+ */
+function banana_richtextToHtml($source)
+{
+ $tags = Array('bold' => 'b',
+ 'italic' => 'i',
+ 'smaller' => 'small',
+ 'bigger' => 'big',
+ 'underline' => 'u',
+ 'subscript' => 'sub',
+ 'superscript' => 'sup',
+ 'excerpt' => 'blockquote',
+ 'paragraph' => 'p',
+ 'nl' => 'br'
+ );
+
+ // clean unsupported tags
+ $protectedTags = '<signature><lt><comment><'.join('><', array_keys($tags)).'>';
+ $source = strip_tags($source, $protectedTags);
+
+ // convert richtext tags to html
+ foreach (array_keys($tags) as $tag) {
+ $source = preg_replace('@(</?)'.$tag.'([^>]*>)@i', '\1'.$tags[$tag].'\2', $source);
+ }
+
+ // some special cases
+ $source = preg_replace('@<signature>@i', '<br>-- <br>', $source);
+ $source = preg_replace('@</signature>@i', '', $source);
+ $source = preg_replace('@<lt>@i', '<', $source);
+ $source = preg_replace('@<comment[^>]*>((?:[^<]|<(?!/comment>))*)</comment>@i', '<!-- \1 -->', $source);
+ return banana_cleanHtml($source);
+}
+
+function banana_formatRichText(BananaMimePart &$part)
+{
+ $text = $part->getText();
+ $text = banana_richtextToHtml($text);
+ $text = banana_catchHtmlSignature($text);
+ return banana_cleanHtml($text);
+}
+
+// }}}
+
+// vim:set et sw=4 sts=4 ts=4:
+?>
--- /dev/null
+<?php
+/********************************************************************************
+* banana/message.inc.php : class for messages
+* ------------------------
+*
+* This file is part of the banana distribution
+* Copyright: See COPYING files that comes with this distribution
+********************************************************************************/
+
+require_once dirname(__FILE__) . '/mimepart.inc.php';
+require_once dirname(__FILE__) . '/message.func.inc.php';
+require_once dirname(__FILE__) . '/banana.inc.php';
+
+final class BananaMessage extends BananaMimePart
+{
+ private $msg_headers = array();
+
+ public function __construct($data = null)
+ {
+ parent::__construct($data);
+ if (!is_null($data)) {
+ if (isset($this->headers['in-reply-to']) && isset($this->headers['references'])) {
+ unset($this->headers['in-reply-to']);
+ }
+ Banana::$show_hdr = array_intersect(Banana::$show_hdr, array_keys($this->headers));
+ Banana::$message =& $this;
+ }
+ }
+
+ public function hasHeader($hdr)
+ {
+ return isset($this->headers[$hdr]);
+ }
+
+ static public function newMessage(array $headers, $body, array $file = null)
+ {
+ $msg = new BananaMessage();
+ $msg->msg_headers = $headers;
+ $msg->makeTextPart($body, 'text/plain', '8bits', 'UTF-8', 'fixed');
+ if (!is_null($file)) {
+ $msg->addAttachment($file);
+ }
+ return $msg;
+ }
+
+ static public function translateHeaderName($hdr)
+ {
+ switch (strtolower($hdr)) {
+ case 'from': return _b_('De');
+ case 'subject': return _b_('Sujet');
+ case 'newsgroups': return _b_('Forums');
+ case 'followup-to': return _b_('Suivi à');
+ case 'to': return _b_('À');
+ case 'cc': return _b_('Copie à');
+ case 'bcc': return _b_('Copie cachée à');
+ case 'reply-to': return _b_('Répondre à');
+ case 'date': return _b_('Date');
+ case 'organization': return _b_('Organisation');
+ case 'in-reply-to':
+ case 'references': return _b_('Références');
+ case 'x-face': return _b_('Image');
+ }
+ return $hdr;
+ }
+
+ public function translateHeaderValue($hdr)
+ {
+ if (!isset($this->headers[$hdr])) {
+ return null;
+ }
+ $text = $this->headers[$hdr];
+
+ if (function_exists('hook_formatDisplayHeader')
+ && $res = hook_formatDisplayHeader($hdr, $text)) {
+ return $res;
+ }
+ switch ($hdr) {
+ case "date":
+ return BananaMessage::formatDate($text);
+
+ case "followup-to": case "newsgroups":
+ $groups = preg_split("/[\t ]*,[\t ]*/", $text);
+ $res = '';
+ foreach ($groups as $g) {
+ $res .= Banana::$page->makeLink(Array('group' => $g, 'text' => $g)) . ', ';
+ }
+ return substr($res,0, -2);
+
+ case "from":
+ return BananaMessage::formatFrom($text);
+
+ case "references": case "in-reply-to":
+ $rsl = "";
+ $parents = preg_grep('/^\d+$/', $this->getTranslatedReferences());
+ $p = array_pop($parents);
+
+ $parents = array();
+ while (!is_null($p)) {
+ array_unshift($parents, $p);
+ $p = Banana::$spool->overview[$p]->parent;
+ }
+ $ndx = 1;
+ foreach ($parents as $p) {
+ $rsl .= Banana::$page->makeLink(Array('group' => Banana::$spool->group,
+ 'artid' => $p, 'text' => $ndx++)) . ' ';
+ }
+ return $rsl;
+
+ case "subject":
+ $link = null;
+ $text = stripslashes($text);
+ if (function_exists('hook_getSubject')) {
+ $link = hook_getSubject($text);
+ }
+ return banana_catchFormats($text) . $link;
+
+ default:
+ return $text;
+ }
+ }
+
+ public function getSender()
+ {
+ $from = $this->headers['from'];
+ $name = trim(preg_replace('/<[^ ]*>/', '', $from));
+ if (empty($name)) {
+ return $from;
+ }
+ return $name;
+ }
+
+ public function getHeaderValue($hdr)
+ {
+ $hdr = strtolower($hdr);
+ if (!isset($this->headers[$hdr])) {
+ return null;
+ }
+ if ($hdr == 'date') {
+ return strtotime($this->headers['date']);
+ } else {
+ return $this->headers[$hdr];
+ }
+ }
+
+ public function getHeaders()
+ {
+ $this->msg_headers = array_merge($this->msg_headers, Banana::$custom_hdr, Banana::$profile['custom_hdr']);
+ $headers = array_map(array($this, 'encodeHeader'), $this->msg_headers);
+ return array_merge($headers, parent::getHeaders());
+ }
+
+ static public function formatFrom($text)
+ {
+# From: mark@cbosgd.ATT.COM
+# From: <mark@cbosgd.ATT.COM>
+# From: mark@cbosgd.ATT.COM (Mark Horton)
+# From: Mark Horton <mark@cbosgd.ATT.COM>
+ $mailto = '<a href="mailto:';
+
+ $result = banana_htmlentities($text);
+ if (preg_match("/^([^ ]+@[^ ]+)$/", $text, $regs)) {
+ $result = $mailto . $regs[1] . '">' . banana_htmlentities($regs[1]) . '</a>';
+ }
+ if (preg_match("/^<(.+@.+)>$/", $text, $regs)) {
+ $result = $mailto . $regs[1] . '">' . banana_htmlentities($regs[1]) . '</a>';
+ }
+ if (preg_match("/^([^ ]+@[^ ]+) \((.*)\)$/", $text, $regs)) {
+ $result = $mailto . $regs[1] . '">' . banana_htmlentities($regs[2]) . '</a>';
+ }
+ if (preg_match("/^\"?([^<>\"]+)\"? +<(.+@.+)>$/", $text, $regs)) {
+ $nom = preg_replace("/^'(.*)'$/", '\1', $regs[1]);
+ $nom = stripslashes($nom);
+ $result = $mailto . $regs[2] . '">' . banana_htmlentities($nom) . '</a>';
+ }
+ return preg_replace("/\\\(\(|\))/","\\1",$result);
+ }
+
+ static public function formatDate($text)
+ {
+ return utf8_encode(strftime("%A %d %B %Y, %H:%M (fuseau serveur)", strtotime($text)));
+ }
+
+ public function translateHeaders()
+ {
+ $result = array();
+ foreach (array_keys($this->headers) as $name) {
+ $value = $this->translateHeaderValue($name);
+ if (!is_null($value)) {
+ $result[$this->translateHeaderName($name)] = $value;
+ }
+ }
+ return $result;
+ }
+
+ public function getReferences()
+ {
+ $text = $this->headers['references'];
+ $text = str_replace("><","> <", $text);
+ return preg_split('/\s/', $text);
+ }
+
+ public function getTranslatedReferences()
+ {
+ return BananaMessage::formatReferences($this->headers);
+ }
+
+ static public function formatReferences(array &$refs)
+ {
+ if (isset($refs['references'])) {
+ $text = str_replace('><', '> <', $refs['references']);
+ return preg_split('/\s/', strtr($text, Banana::$spool->ids));
+ } elseif (isset($refs['in-reply-to'])) {
+ return array(Banana::$spool->ids[$refs['in-reply-to']]);
+ } else {
+ return array();
+ }
+ }
+
+ public function hasXFace()
+ {
+ return Banana::$formatxface && isset($this->headers['x-face']);
+ }
+
+ public function getXFace()
+ {
+ header('Content-Type: image/gif');
+ $xface = $this->headers['x-face'];
+ passthru('echo ' . escapeshellarg($xface)
+ . '| uncompface -X '
+ . '| convert -transparent white xbm:- gif:-');
+ exit;
+ }
+
+ public function getFormattedBody($type = null)
+ {
+ $types = Banana::$body_mime;
+ if (!is_null($type)) {
+ array_unshift($types, $type);
+ }
+ foreach ($types as $type) {
+ @list($type, $subtype) = explode('/', $type);
+ $parts = $this->getParts($type, $subtype);
+ if (empty($parts)) {
+ continue;
+ }
+ foreach ($parts as &$part) {
+ list($type, $subtype) = $part->getType();
+ switch ($subtype) {
+ case 'html': return banana_formatHtml($part);
+ case 'enriched': case 'richtext': return banana_formatRichText($part);
+ default: return banana_formatPlainText($part);
+ }
+ }
+ }
+ return null;
+ }
+
+ public function quote()
+ {
+ $part = $this->toPlainText();
+ if (is_null($part)) {
+ return banana_quoteHtml($this->toHtml());
+ }
+ return banana_quotePlainText($part);
+ }
+
+ public function canCancel()
+ {
+ if (!Banana::$protocole->canCancel()) {
+ return false;
+ }
+ if (function_exists('hook_checkcancel')) {
+ return hook_checkcancel($this->headers);
+ }
+ return Banana::$profile['name'] == $this->headers['from'];
+ }
+
+ public function canSend()
+ {
+ return Banana::$protocole->canSend();
+ }
+}
+
+// vim:set et sw=4 sts=4 ts=4:
+?>
--- /dev/null
+<?php
+/********************************************************************************
+* banana/mimepart.inc.php : class for MIME parts
+* ------------------------
+*
+* This file is part of the banana distribution
+* Copyright: See COPYING files that comes with this distribution
+********************************************************************************/
+
+class BananaMimePart
+{
+ public $headers = null; /* Should be protected */
+
+ private $id = null;
+ private $content_type = null;
+ private $charset = null;
+ private $encoding = null;
+ private $disposition = null;
+ private $boundary = null;
+ private $filename = null;
+ private $format = null;
+
+ private $body = null;
+ private $multipart = null;
+
+ protected function __construct($data = null)
+ {
+ if (!is_null($data)) {
+ $this->fromRaw($data);
+ }
+ }
+
+ protected function makeTextPart($body, $content_type, $encoding, $charset = null, $format = 'fixed')
+ {
+ $this->body = $body;
+ $this->charset = $charset;
+ $this->encoding = $encoding;
+ $this->content_type = $content_type;
+ $this->format = strtolower($format);
+ $this->parse();
+ }
+
+ protected function makeDataPart($body, $content_type, $encoding, $filename, $disposition, $id = null)
+ {
+ $this->body = $body;
+ $this->content_type = $content_type;
+ $this->encoding = $encoding;
+ $this->filename = $filename;
+ $this->disposition = $disposition;
+ $this->id = $id;
+ if (is_null($content_type) || $content_type == 'application/octet-stream') {
+ $this->decodeContent();
+ $this->content_type = BananaMimePart::getMimeType($body, false);
+ }
+ }
+
+ protected function makeFilePart($file, $content_type =null, $disposition = 'attachment')
+ {
+ $body = file_get_contents($file['tmp_name']);
+ if ($body === false || strlen($body) != $file['size']) {
+ return false;
+ }
+ if (is_null($content_type) || $content_type == 'application/octet-stream') {
+ $content_type = BananaMimePart::getMimeType($file['tmp_name']);
+ }
+ if (substr($content_type, 0, 5) == 'text/') {
+ $encoding = '8bit';
+ } else {
+ $encoding = 'base64';
+ $body = chunk_split(base64_encode($body));
+ }
+ $this->filename = $file['name'];
+ $this->content_type = $content_type;
+ $this->disposition = $disposition;
+ $this->body = $body;
+ $this->encoding = $encoding;
+ return true;
+ }
+
+ protected function makeMultiPart($body, $content_type, $encoding, $boundary)
+ {
+ $this->body = $body;
+ $this->content_type = $content_type;
+ $this->encoding = $encoding;
+ $this->boundary = $boundary;
+ $this->parse();
+ }
+
+ protected function convertToMultiPart()
+ {
+ if (!$this->isType('multipart', 'mixed')) {
+ $newpart = $this;
+ $this->content_type = 'multipart/mixed';
+ $this->encoding = '8bit';
+ $this->multipart = array($newpart);
+ $this->headers = null;
+ $this->charset = null;
+ $this->disposition = null;
+ $this->filename = null;
+ $this->boundary = null;
+ $this->body = null;
+ $this->format = null;
+ $this->id = null;
+ }
+ }
+
+ public function addAttachment(array $file, $content_type = null, $disposition = 'attachment')
+ {
+ $newpart = new BananaMimePart;
+ if (!is_uploaded_file($file['tmp_name'])) {
+ return false;
+ }
+ if ($newpart->makeFilePart($file, $content_type, $disposition)) {
+ $this->convertToMultiPart();
+ $this->multipart[] = $newpart;
+ return true;
+ }
+ return false;
+ }
+
+ protected function getHeader($title, $filter = null)
+ {
+ if (!isset($this->headers[$title])) {
+ return null;
+ }
+ $header =& $this->headers[$title];
+ if (is_null($filter)) {
+ return trim($header);
+ } elseif (preg_match($filter, $header, $matches)) {
+ return trim($matches[1]);
+ }
+ return null;
+ }
+
+ protected function fromRaw($data)
+ {
+ if (is_array($data)) {
+ if (array_key_exists('From', $data)) {
+ $this->headers = array_map(array($this, 'decodeHeader'), array_change_key_case($data));
+ return;
+ } else {
+ $lines = $data;
+ }
+ } else {
+ $lines = explode("\n", $data);
+ }
+ $headers = BananaMimePart::parseHeaders($lines);
+ $this->headers =& $headers;
+ if (empty($headers) || empty($lines)) {
+ return;
+ }
+ $content = join("\n", $lines);
+ $test = trim($content);
+ if (empty($test)) {
+ return;
+ }
+
+ $content_type = strtolower($this->getHeader('content-type', '/^\s*([^ ;]+?)(;|$)/'));
+ if (empty($content_type)) {
+ $encoding = '8bit';
+ $charset = 'CP1252';
+ $content_type = 'text/plain';
+ $format = strtolower($this->getHeader('x-rfc2646', '/format="?([^"]+?)"?\s*(;|$)/i'));
+ } else {
+ $encoding = strtolower($this->getHeader('content-transfer-encoding'));
+ $disposition = $this->getHeader('content-disposition', '/(inline|attachment)/i');
+ $boundary = $this->getHeader('content-type', '/boundary="?([^"]+?)"?\s*(;|$)/i');
+ $charset = strtolower($this->getHeader('content-type', '/charset="?([^"]+?)"?\s*(;|$)/i'));
+ $filename = $this->getHeader('content-disposition', '/filename="?([^"]+?)"?\s*(;|$)/i');
+ $format = strtolower($this->getHeader('content-type', '/format="?([^"]+?)"?\s*(;|$)/i'));
+ $id = $this->getHeader('content-id', '/<(.*?)>/');
+ if (empty($filename)) {
+ $filename = $this->getHeader('content-type', '/name="?([^"]+)"?/');
+ }
+ }
+ list($type, $subtype) = explode('/', $content_type);
+ switch ($type) {
+ case 'text': case 'message':
+ $this->makeTextPart($content, $content_type, $encoding, $charset, $format);
+ break;
+ case 'multipart':
+ $this->makeMultiPart($content, $content_type, $encoding, $boundary);
+ break;
+ default:
+ $this->makeDataPart($content, $content_type, $encoding, $filename, $disposition, $id);
+ }
+ }
+
+ private function parse()
+ {
+ if ($this->isType('multipart')) {
+ $this->splitMultipart();
+ } else {
+ $parts = $this->findUUEncoded();
+ if (count($parts)) {
+ $this->convertToMultiPart();
+ $this->multipart = array_merge(array($textpart), $parts);
+ }
+ }
+ }
+
+ private function splitMultipart()
+ {
+ $this->decodeContent();
+ if (is_null($this->multipart)) {
+ $this->multipart = array();
+ }
+ $boundary =& $this->boundary;
+ $parts = preg_split("/\n--" . preg_quote($boundary, '/') . "(--|\n)/", $this->body, -1, PREG_SPLIT_NO_EMPTY);
+ foreach ($parts as &$part) {
+ $newpart = new BananaMimePart($part);
+ if (!is_null($newpart->content_type)) {
+ $this->multipart[] = $newpart;
+ }
+ }
+ $this->body = null;
+ }
+
+ public static function getMimeType($data, $is_filename = true)
+ {
+ if ($is_filename) {
+ $type = mime_content_type($arg);
+ } else {
+ $arg = escapeshellarg($data);
+ $type = preg_replace('/;.*/', '', trim(shell_exec("echo $arg | file -bi -")));
+ }
+ return empty($type) ? 'application/octet-stream' : $type;
+ }
+
+ private function findUUEncoded()
+ {
+ $this->decodeContent();
+ $parts = array();
+ if (preg_match_all("/\n(begin \d+ ([^\r\n]+)\r?(?:\n(?!end)[^\n]*)*\nend)/",
+ $this->body, $matches, PREG_SET_ORDER)) {
+ foreach ($matches as &$match) {
+ $data = convert_uudecode($match[1]);
+ $mime = BananaMimePart::getMimeType($data, false);
+ if ($mime != 'application/x-empty') {
+ $this->body = trim(str_replace($match[0], '', $this->body));
+ $newpart = new BananaMimePart;
+ $newpart->makeDataPart($data, $mime, '8bit', $match[2], 'attachment');
+ $parts[] = $newpart;
+ }
+ }
+ }
+ return $parts;
+ }
+
+ static private function _decodeHeader($charset, $c, $str)
+ {
+ $s = ($c == 'Q' || $c == 'q') ? quoted_printable_decode($str) : base64_decode($str);
+ $s = @iconv($charset, 'UTF-8', $s);
+ return str_replace('_', ' ', $s);
+ }
+
+ static public function decodeHeader(&$val, $key)
+ {
+ if (preg_match('/[\x80-\xff]/', $val)) {
+ if (!is_utf8($val)) {
+ $val = utf8_encode($val);
+ }
+ } else {
+ $val = preg_replace('/(=\?.*?\?[bq]\?.*?\?=) (=\?.*?\?[bq]\?.*?\?=)/i', '\1\2', $val);
+ $val = preg_replace('/=\?(.*?)\?([bq])\?(.*?)\?=/ie', 'BananaMimePart::_decodeHeader("\1", "\2", "\3")', $val);
+ }
+ }
+
+ static public function &parseHeaders(array &$lines)
+ {
+ $headers = array();
+ while (count($lines)) {
+ $line = array_shift($lines);
+ if (preg_match('/^[\t\r ]+/', $line) && isset($hdr)) {
+ $headers[$hdr] .= ' ' . trim($line);
+ } elseif (!empty($line)) {
+ if (preg_match("/:[ \t\r]*/", $line)) {
+ list($hdr, $val) = split(":[ \t\r]*", $line, 2);
+ $hdr = strtolower($hdr);
+ if (in_array($hdr, Banana::$parse_hdr)) {
+ $headers[$hdr] = $val;
+ } else {
+ unset($hdr);
+ }
+ }
+ } else {
+ break;
+ }
+ }
+ array_walk($headers, array('BananaMimePart', 'decodeHeader'));
+ return $headers;
+ }
+
+ static public function encodeHeader($value, $trim = 0)
+ {
+ if ($trim) {
+ if (strlen($value) > $trim) {
+ $value = substr($value, 0, $trim);
+ }
+ }
+ if (preg_match('/[\x80-\xff]/', $value)) {
+ return '=?UTF-8?B?' . base64_encode($value) . '?=';
+ }
+ return $value;
+ }
+
+ private function decodeContent()
+ {
+ $encodings = Array('quoted-printable' => 'quoted_printable_decode',
+ 'base64' => 'base64_decode',
+ 'x-uuencode' => 'convert_uudecode');
+ foreach ($encodings as $encoding => $callback) {
+ if ($this->encoding == $encoding) {
+ $this->body = $callback($this->body);
+ $this->encoding = '8bit';
+ break;
+ }
+ }
+ if (!$this->isType('text')) {
+ return;
+ }
+
+ if (!is_null($this->charset)) {
+ $body = iconv($this->charset, 'UTF-8//IGNORE', $this->body);
+ if (empty($body)) {
+ return;
+ }
+ $this->body = $body;
+ } else {
+ $this->body = utf8_encode($this->body);
+ }
+ $this->charset = 'utf-8';
+ }
+
+ public function send($force_inline = false)
+ {
+ $this->decodeContent();
+ if ($force_inline) {
+ $dispostion = $this->disposition;
+ $this->disposition = 'inline';
+ }
+ $headers = $this->getHeaders();
+ foreach ($headers as $key => $value) {
+ header("$key: $value");
+ }
+ if ($force_inline) {
+ $this->disposition = $disposition;
+ }
+ echo $this->body;
+ exit;
+ }
+
+ private function setBoundary()
+ {
+ if ($this->isType('multipart') && is_null($this->boundary)) {
+ $this->boundary = '--banana-bound-' . time() . rand(0, 255) . '-';
+ }
+ }
+
+ public function getHeaders()
+ {
+ $headers = array();
+ $this->setBoundary();
+ $headers['Content-Type'] = $this->content_type . ";"
+ . ($this->filename ? " name=\"{$this->filename}\";" : '')
+ . ($this->charset ? " charset=\"{$this->charset}\";" : '')
+ . ($this->boundary ? " boundary=\"{$this->boundary}\";" : "");
+ if ($this->encoding) {
+ $headers['Content-Transfer-Encoding'] = $this->encoding;
+ }
+ if ($this->disposition) {
+ $headers['Content-Disposition'] = $this->disposition
+ . ($this->filename ? "; filename=\"{$this->filename}\"" : '');
+ }
+ return array_map(array($this, 'encodeHeader'), $headers);
+ }
+
+ public function hasBody()
+ {
+ if (is_null($this->content) && !$this->isType('multipart')) {
+ return false;
+ }
+ return true;
+ }
+
+ public function get($with_headers = false)
+ {
+ $content = "";
+ if ($with_headers) {
+ foreach ($this->getHeaders() as $key => $value) {
+ $content .= "$key: $value\n";
+ }
+ $content .= "\n";
+ }
+ if ($this->isType('multipart')) {
+ $this->setBoundary();
+ foreach ($this->multipart as &$part) {
+ $content .= "\n--{$this->boundary}\n" . $part->get(true);
+ }
+ $content .= "\n--{$this->boundary}--";
+ } else {
+ $content .= $this->body;
+ }
+ return $content;
+ }
+
+ public function getText()
+ {
+ if (!$this->isType('text')) {
+ return null;
+ }
+ $this->decodeContent();
+ return $this->body;
+ }
+
+ protected function getType()
+ {
+ return explode('/', $this->content_type);
+ }
+
+ protected function isType($type, $subtype = null)
+ {
+ list($mytype, $mysub) = $this->getType();
+ return ($mytype == $type) && (is_null($subtype) || $mysub == $subtype);
+ }
+
+ public function isFlowed()
+ {
+ return $this->format == 'flowed';
+ }
+
+ public function getFilename()
+ {
+ return $this->filename;
+ }
+
+ protected function getParts($type, $subtype = null)
+ {
+ $parts = array();
+ if ($this->isType($type, $subtype)) {
+ return array($this);
+ } elseif ($this->isType('multipart')) {
+ foreach ($this->multipart as &$part) {
+ $parts = array_merge($parts, $part->getParts($type, $subtype));
+ }
+ }
+ return $parts;
+ }
+
+ public function toPlainText()
+ {
+ $parts = $this->getParts('text', 'plain');
+ return (count($parts) ? $parts[0] : null);
+ }
+
+ public function toHtml()
+ {
+ $parts = $this->getParts('text', 'html');
+ return (count($parts) ? $parts[0] : null);
+ }
+
+ public function toRichText()
+ {
+ $parts = $this->getParts('text', 'enriched');
+ return (count($parts) ? $parts[0] : null);
+ }
+
+ public function getFile($filename)
+ {
+ if ($this->filename == $filename) {
+ return $this;
+ } elseif ($this->isType('multipart')) {
+ foreach ($this->multipart as &$part) {
+ $file = $part->getFile($filename);
+ if (!is_null($file)) {
+ return $file;
+ }
+ }
+ }
+ return null;
+ }
+
+ public function getAttachments()
+ {
+ if (!is_null($this->filename)) {
+ return array($this);
+ } elseif ($this->isType('multipart')) {
+ $parts = array();
+ foreach ($this->multipart as &$part) {
+ $parts = array_merge($parts, $part->getAttachments());
+ }
+ return $parts;
+ }
+ return array();
+ }
+
+ public function getPartById($id)
+ {
+ if ($this->id == $id) {
+ return $this;
+ } elseif ($this->isType('multipart')) {
+ foreach ($this->multipart as &$part) {
+ $res = $part->getPartById($id);
+ if (!is_null($res)) {
+ return $res;
+ }
+ }
+ }
+ return null;
+ }
+}
+
+// vim:set et sw=4 sts=4 ts=4:
+?>
+++ /dev/null
-<?php
-/********************************************************************************
- * include/misc.inc.php : Misc functions
- * -------------------------
- *
- * This file is part of the banana distribution
- * Copyright: See COPYING files that comes with this distribution
- ********************************************************************************/
-
-/********************************************************************************
- * MISC
- */
-
-function _b_($str) { return utf8_decode(dgettext('banana', utf8_encode($str))); }
-
-function to_entities($str) {
- require_once dirname(__FILE__).'/utf8.php';
- return utf8entities(htmlentities($str, ENT_NOQUOTES, 'UTF-8'));
-}
-
-function is_utf8($s) { return iconv('utf-8', 'utf-8', $s) == $s; }
-
-function textFormat_translate($format)
-{
- switch (strtolower($format)) {
- case 'plain': return _b_('Texte brut');
- case 'richtext': return _b_('Texte enrichi');
- case 'html': return _b_('HTML');
- default: return $format;
- }
-}
-
-/** Redirect to the page with the given parameter
- * @ref makeLink
- */
-function redirectInBanana($params)
-{
- header('Location: ' . makeLink($params));
-}
-
-/** Make a link using the given parameters
- * @param ARRAY params, the parameters with
- * key => value
- * Known key are:
- * - group = group name
- * - artid/first = article id the the group
- * - subscribe = to show the subscription page
- * - action = action to do (new, cancel, view)
- * - part = to show the given MIME part of the article
- * - pj = to get the given attachment
- * - xface = to make a link to an xface
- *
- * Can be overloaded by defining a hook_makeLink function
- */
-function makeLink($params)
-{
- if (function_exists('hook_makeLink')
- && $res = hook_makeLink($params)) {
- return $res;
- }
- $proto = empty($_SERVER['HTTPS']) ? 'http://' : 'https://';
- $host = $_SERVER['HTTP_HOST'];
- $file = $_SERVER['PHP_SELF'];
-
- if (isset($params['xface'])) {
- $file = dirname($file) . '/xface.php';
- $get = 'face=' . urlencode(base64_encode($params['xface']));
- } else if (count($params) != 0) {
- $get = '?';
- foreach ($params as $key=>$value) {
- if (strlen($get) != 1) {
- $get .= '&';
- }
- $get .= $key . '=' . $value;
- }
- } else {
- $get = '';
- }
-
- return $proto . $host . $file . $get;
-}
-
-/** Format a link to be use in a link
- * @ref makeLink
- */
-function makeHREF($params, $text = null, $popup = null, $class = null, $accesskey = null)
-{
- $link = makeLink($params);
- if (is_null($text)) {
- $text = $link;
- }
- if (!is_null($accesskey)) {
- $popup .= ' (raccourci : ' . $accesskey . ')';
- }
- if (!is_null($popup)) {
- $popup = ' title="' . $popup . '"';
- }
- if (!is_null($class)) {
- $class = ' class="' . $class . '"';
- }
- $target = null;
- if (isset($params['action']) && $params['action'] == 'view') {
- $target = ' target="_blank"';
- }
- if (!is_null($accesskey)) {
- $accesskey = ' accesskey="' . $accesskey . '"';
- }
-
- return '<a href="' . htmlentities($link) . '"'
- . $target . $popup . $class . $accesskey
- . '>' . $text . '</a>';
-}
-
-/** Format tree images links
- * @param img STRING Image name (without extension)
- * @param alt STRING alternative string
- * @param width INT to force width of the image (null if not defined)
- *
- * This function can be overloaded by defining hook_makeImg()
- */
-function makeImg($img, $alt, $height = null, $width = null)
-{
- if (function_exists('hook_makeImg')
- && $res = hook_makeImg($img, $alt, $height, $width)) {
- return $res;
- }
-
- if (!is_null($width)) {
- $width = ' width="' . $width . '"';
- }
- if (!is_null($height)) {
- $height = ' height="' . $height . '"';
- }
-
- $proto = empty($_SERVER['HTTPS']) ? 'http://' : 'https://';
- $host = $_SERVER['HTTP_HOST'];
- $file = dirname($_SERVER['PHP_SELF']) . '/img/' . $img;
- $url = $proto . $host . $file;
-
- return '<img src="' . $url . '"' . $height . $width . ' alt="' . $alt . '" />';
-}
-
-/** Make a link using an image
- */
-function makeImgLink($params, $img, $alt, $height = null, $width = null, $class = null, $accesskey = null)
-{
- return makeHREF($params,
- makeImg($img, ' [' . $alt . ']', $height, $width),
- $alt,
- $class,
- $accesskey);
-}
-
-/********************************************************************************
- * HTML STUFF
- * Taken from php.net
- */
-
-/**
- * @return string
- * @param string
- * @desc Strip forbidden tags and delegate tag-source check to removeEvilAttributes()
- */
-function removeEvilTags($source)
-{
- $allowedTags = '<h1><b><i><a><ul><li><pre><hr><blockquote><img><br><font><p><small><big><sup><sub><code><em>';
- $source = preg_replace('|</div>|i', '<br />', $source);
- $source = strip_tags($source, $allowedTags);
- return preg_replace('/<(.*?)>/ie', "'<'.removeEvilAttributes('\\1').'>'", $source);
-}
-
-/**
- * @return string
- * @param string
- * @desc Strip forbidden attributes from a tag
- */
-function removeEvilAttributes($tagSource)
-{
- $stripAttrib = 'javascript:|onclick|ondblclick|onmousedown|onmouseup|onmouseover|'.
- 'onmousemove|onmouseout|onkeypress|onkeydown|onkeyup';
- return stripslashes(preg_replace("/$stripAttrib/i", '', $tagSource));
-}
-
-/** Convert html to plain text
- */
-function htmlToPlainText($res)
-{
- $res = trim(html_entity_decode(strip_tags($res, '<div><br><p>')));
- $res = preg_replace("@</?(br|p|div)[^>]*>@i", "\n", $res);
- if (!is_utf8($res)) {
- $res = utf8_encode($res);
- }
- return $res;
-}
-
-/** Match **, // and __ to format plain text
- */
-function formatPlainText($text)
-{
- $formatting = Array('\*' => 'strong',
- '_' => 'u',
- '/' => 'em');
- foreach ($formatting as $limit=>$mark) {
- $text = preg_replace('@(^|\W)' . $limit . '(\w+)' . $limit . '(\W|$)@'
- ,'\1<' . $mark . '>\2</' . $mark . '>\3'
- , $text);
- }
- return $text;
-}
-
-/********************************************************************************
- * RICHTEXT STUFF
- */
-
-/** Convert richtext to html
- */
-function richtextToHtml($source)
-{
- $tags = Array('bold' => 'b',
- 'italic' => 'i',
- 'smaller' => 'small',
- 'bigger' => 'big',
- 'underline' => 'u',
- 'subscript' => 'sub',
- 'superscript' => 'sup',
- 'excerpt' => 'blockquote',
- 'paragraph' => 'p',
- 'nl' => 'br'
- );
-
- // clean unsupported tags
- $protectedTags = '<signature><lt><comment><'.join('><', array_keys($tags)).'>';
- $source = strip_tags($source, $protectedTags);
-
- // convert richtext tags to html
- foreach (array_keys($tags) as $tag) {
- $source = preg_replace('@(</?)'.$tag.'([^>]*>)@i', '\1'.$tags[$tag].'\2', $source);
- }
-
- // some special cases
- $source = preg_replace('@<signature>@i', '<br>-- <br>', $source);
- $source = preg_replace('@</signature>@i', '', $source);
- $source = preg_replace('@<lt>@i', '<', $source);
- $source = preg_replace('@<comment[^>]*>((?:[^<]|<(?!/comment>))*)</comment>@i', '<!-- \1 -->', $source);
- return removeEvilAttributes($source);
-}
-
-/********************************************************************************
- * HEADER STUFF
- */
-
-function _headerdecode($charset, $c, $str) {
- $s = ($c == 'Q' || $c == 'q') ? quoted_printable_decode($str) : base64_decode($str);
- $s = iconv($charset, 'iso-8859-15', $s);
- return str_replace('_', ' ', $s);
-}
-
-function headerDecode($value) {
- $val = preg_replace('/(=\?[^?]*\?[BQbq]\?[^?]*\?=) (=\?[^?]*\?[BQbq]\?[^?]*\?=)/', '\1\2', $value);
- return preg_replace('/=\?([^?]*)\?([BQbq])\?([^?]*)\?=/e', '_headerdecode("\1", "\2", "\3")', $val);
-}
-
-function headerEncode($value, $trim = 0) {
- if ($trim) {
- if (strlen($value) > $trim) {
- $value = substr($value, 0, $trim) . "[...]";
- }
- }
- return "=?UTF-8?B?".base64_encode($value)."?=";
-}
-
-function header_translate($hdr) {
- switch ($hdr) {
- case 'from': return _b_('De');
- case 'subject': return _b_('Sujet');
- case 'newsgroups': return _b_('Forums');
- case 'followup-to': return _b_('Suivi-à');
- case 'date': return _b_('Date');
- case 'organization': return _b_('Organisation');
- case 'references': return _b_('Références');
- case 'x-face': return _b_('Image');
- default:
- if (function_exists('hook_headerTranslate')
- && $res = hook_headerTranslate($hdr)) {
- return $res;
- }
- return $hdr;
- }
-}
-
-function formatDisplayHeader($_header,$_text) {
- global $banana;
- if (function_exists('hook_formatDisplayHeader')
- && $res = hook_formatDisplayHeader($_header, $_text)) {
- return $res;
- }
-
- switch ($_header) {
- case "date":
- return formatDate($_text);
-
- case "followup-to":
- case "newsgroups":
- $res = "";
- $groups = preg_split("/[\t ]*,[\t ]*/",$_text);
- foreach ($groups as $g) {
- $res .= makeHREF(Array('group' => $g), $g) . ', ';
- }
- return substr($res,0, -2);
-
- case "from":
- return formatFrom($_text);
-
- case "references":
- $rsl = "";
- $ndx = 1;
- $text = str_replace("><","> <",$_text);
- $text = preg_split("/[ \t]/",strtr($text,$banana->spool->ids));
- $parents = preg_grep("/^\d+$/",$text);
- $p = array_pop($parents);
- $par_ok = Array();
-
- while ($p) {
- $par_ok[]=$p;
- $p = $banana->spool->overview[$p]->parent;
- }
- foreach (array_reverse($par_ok) as $p) {
- $rsl .= makeHREF(Array('group' => $banana->spool->group,
- 'artid' => $p),
- $ndx) . ' ';
- $ndx++;
- }
- return $rsl;
-
- case "x-face":
- return '<img src="' . makeLink(Array('xface' => headerDecode($_text))) .'" alt="x-face" />';
-
- case "subject":
- $link = null;
- if (function_exists('hook_getSubject')) {
- $link = hook_getSubject($_text);
- }
- return formatPlainText($_text) . $link;
-
- default:
- return htmlentities($_text);
- }
-}
-
-/********************************************************************************
- * FORMATTING STUFF
- */
-
-function formatDate($_text) {
- return strftime("%A %d %B %Y, %H:%M (fuseau serveur)", strtotime($_text));
-}
-
-function fancyDate($stamp) {
- $today = intval(time() / (24*3600));
- $dday = intval($stamp / (24*3600));
-
- if ($today == $dday) {
- $format = "%H:%M";
- } elseif ($today == 1 + $dday) {
- $format = _b_('hier')." %H:%M";
- } elseif ($today < 7 + $dday) {
- $format = '%a %H:%M';
- } else {
- $format = '%a %e %b';
- }
- return strftime($format, $stamp);
-}
-
-function formatFrom($text) {
-# From: mark@cbosgd.ATT.COM
-# From: mark@cbosgd.ATT.COM (Mark Horton)
-# From: Mark Horton <mark@cbosgd.ATT.COM>
- $mailto = '<a href="mailto:';
-
- $result = htmlentities($text);
- if (preg_match("/^([^ ]+)@([^ ]+)$/",$text,$regs)) {
- $result="$mailto{$regs[1]}@{$regs[2]}\">".htmlentities($regs[1]."@".$regs[2])."</a>";
- }
- if (preg_match("/^([^ ]+)@([^ ]+) \((.*)\)$/",$text,$regs)) {
- $result="$mailto{$regs[1]}@{$regs[2]}\">".htmlentities($regs[3])."</a>";
- }
- if (preg_match("/^\"?([^<>\"]+)\"? +<(.+)@(.+)>$/",$text,$regs)) {
- $result="$mailto{$regs[2]}@{$regs[3]}\">".htmlentities($regs[1])."</a>";
- }
- return preg_replace("/\\\(\(|\))/","\\1",$result);
-}
-
-function makeTab($link, $text)
-{
- return Array(makeHREF($link, $text),
- $text);
-}
-
-function displayTabs()
-{
- global $banana;
- extract($banana->state);
- if (function_exists('hook_shortcuts') && $cstm = hook_shortcuts()) {
- $res = $cstm;
- }
-
- $res['subscribe'] = makeTab(Array('subscribe' => 1), _b_('Abonnements'));
- $res['forums'] = makeTab(Array(), _b_('Les forums'));
-
- if (!is_null($group)) {
- $res['group'] = makeTab(Array('group' => $group), $group);
- if (is_null($artid)) {
- if (@$action == 'new') {
- $res['action'] = makeTab(Array('group' => $group,
- 'action' => 'new'),
- _b_('Nouveau Message'));
- }
- } else {
- $res['message'] = makeTab(Array('group' => $group,
- 'artid' => $artid),
- _b_('Message'));
- if (!is_null($action)) {
- if ($action == 'new') {
- $res['action'] = makeTab(Array('group' => $group,
- 'artid' => $artid,
- 'action' => 'new'),
- _b_('Réponse'));
- } elseif ($action == 'cancel') {
- $res['action'] = makeTab(Array('group' => $group,
- 'artid' => $artid,
- 'action' => 'cancel'),
- _b_('Annuler'));
- }
- }
- }
- }
- $ret = '<ul id="onglet">';
- foreach ($res as $name=>$onglet) {
- if ($name != @$page) {
- $ret .= '<li>' . $onglet[0] . '</li>';
- } else {
- $ret .= '<li class="actif">' . $onglet[1] . '</li>';
- }
- }
- $ret .= '</ul>';
- return $ret;
-}
-
-function displayPages($first = -1)
-{
- global $banana;
- extract($banana->state);
- $res = null;
- if (!is_null($group) && is_null($artid)
- && sizeof($banana->spool->overview)>$banana->tmax) {
- $res .= '<div class="pages">';
- $n = intval(log(count($banana->spool->overview), 10))+1;
- $i = 1;
- for ($ndx = 1 ; $ndx <= sizeof($banana->spool->overview) ; $ndx += $banana->tmax) {
- if ($first==$ndx) {
- $res .= '<strong>' . $i . '</strong> ';
- } else {
- $res .= makeHREF(Array('group' => $group,
- 'first' => $ndx),
- $i,
- $ndx . '-' . min($ndx+$banana->tmax-1,sizeof($banana->spool->overview)))
- . ' ';
- }
- $i++;
- }
- $res .= '</div>';
- }
- return $res;
-}
-
-function makeTable($text)
-{
- $links = null;
- if (function_exists('hook_browsingAction')) {
- $links = hook_browsingAction();
- }
-
- return '<table class="cadre_a_onglet" cellpadding="0" cellspacing="0" width="100%">'
- . '<tr><td>'
- . displayTabs()
- . '</td></tr>'
- . '<tr><td class="conteneur_tab">'
- . $links
- . $text
- . '</td></tr>'
- . '</table>';
-}
-
-/********************************************************************************
- * FORMATTING STUFF : BODY
- */
-
-function autoformat($text, $force = 0)
-{
- global $banana;
- $length = $banana->wrap;
- $force = $force ? 1 : 0;
- $cmd = 'echo ' . escapeshellarg($text)
- . ' | perl -MText::Autoformat -e \'autoformat {left=>1, right=>' . $length . ', all=>' . $force . ' };\'';
-
- exec($cmd, $result, $ret);
- if ($ret != 0) {
- $result = split("\n", $text);
- }
- return $result;
-}
-
-function wrap($text, $_prefix="", $_force=false, $firstpass = true)
-{
- $parts = preg_split("/\n-- ?\n/", $text);
- if (count($parts) >1) {
- $sign = "\n-- \n" . array_pop($parts);
- $text = join("\n-- \n", $parts);
- } else {
- $sign = '';
- }
-
- global $banana;
- $url = $banana->url_regexp;
- $length = $banana->wrap;
- $max = $length + ($length/10);
- $splits = split("\n", $text);
- $result = array();
- $next = array();
- $format = false;
- foreach ($splits as $line) {
- if ($_force || strlen($line) > $max) {
- if (preg_match("!^(.*)($url)(.*)!i", $line, $matches)
- && strlen($matches[2]) > $length && strlen($matches) < 900) {
- if (strlen($matches[1]) != 0) {
- array_push($next, rtrim($matches[1]));
- if (strlen($matches[1]) > $max) {
- $format = true;
- }
- }
-
- if ($format) {
- $result = array_merge($result, autoformat(join("\n", $next), $firstpass));
- } else {
- $result = array_merge($result, $next);
- }
- $format = false;
- $next = array();
- array_push($result, $matches[2]);
-
- if (strlen($matches[6]) != 0) {
- array_push($next, ltrim($matches[6]));
- if (strlen($matches[6]) > $max) {
- $format = true;
- }
- }
- } else {
- array_push($next, $line);
- $format = true;
- }
- } else {
- array_push($next, $line);
- }
- }
- if ($format) {
- $result = array_merge($result, autoformat(join("\n", $next), $firstpass));
- } else {
- $result = array_merge($result, $next);
- }
-
- $break = "\n";
- $prefix = null;
- if (!$firstpass) {
- $break .= $_prefix;
- $prefix = $_prefix;
- }
- $result = $prefix.join($break, $result).($prefix ? '' : $sign);
- if ($firstpass) {
- return wrap($result, $_prefix, $_force, false);
- }
- return $result;
-}
-
-function cutlink($link)
-{
- global $banana;
-
- if (strlen($link) > $banana->wrap) {
- $link = substr($link, 0, $banana->wrap - 3)."...";
- }
- return $link;
-}
-
-function cleanurl($url)
-{
- $url = str_replace('@', '%40', $url);
- return '<a href="'.$url.'" title="'.$url.'">'.cutlink($url).'</a>';
-}
-
-function catchMailLink($email)
-{
- global $banana;
- $mid = '<' . $email . '>';
- if (isset($banana->spool->ids[$mid])) {
- return makeHREF(Array('group' => $banana->state['group'],
- 'artid' => $banana->spool->ids[$mid]),
- $email);
- } elseif (strpos($email, '$') !== false) {
- return $email;
- }
- return '<a href="mailto:' . $email . '">' . $email . '</a>';
-}
-
-/** Remove quotation marks
- */
-function replaceQuotes($text)
-{
- return stripslashes(preg_replace("@(^|<pre>|\n)>[ \t\r]*@i", '\1', $text));
-}
-
-function formatbody($_text, $format='plain', $flowed=false)
-{
- if ($format == 'html') {
- $res = html_entity_decode(to_entities(removeEvilTags($_text)));
- } else if ($format == 'richtext') {
- $res = html_entity_decode(to_entities(richtextToHtml($_text)));
- } else {
- $res = to_entities(wrap($_text, "", $flowed));
- $res = formatPlainText($res);
- }
-
- if ($format != 'html') {
- global $banana;
- $url = $banana->url_regexp;
- $res = preg_replace("/(<|>|")/", " \\1 ", $res);
- $res = preg_replace("!$url!ie", "'\\1'.cleanurl('\\2').'\\3'", $res);
- $res = preg_replace('/(["\[])?(?:mailto:|news:)?([a-z0-9.\-+_\$]+@([\-.+_]?[a-z0-9])+)(["\]])?/ie',
- "'\\1' . catchMailLink('\\2') . '\\4'",
- $res);
- $res = preg_replace("/ (<|>|") /", "\\1", $res);
-
- if ($format == 'richtext') {
- $format = 'html';
- }
- }
-
- if ($format == 'html') {
- $res = preg_replace("@(</p>)\n?-- ?\n?(<p[^>]*>|<br[^>]*>)@", "\\1<br/>-- \\2", $res);
- $res = preg_replace("@<br[^>]*>\n?-- ?\n?(<p[^>]*>)@", "<br/>-- <br/>\\2", $res);
- $res = preg_replace("@(<pre[^>]*>)\n?-- ?\n@", "<br/>-- <br/>\\1", $res);
- $parts = preg_split("@(:?<p[^>]*>\n?-- ?\n?</p>|<br[^>]*>\n?-- ?\n?<br[^>]*>)@", $res);
- $sign = '<hr style="width: 100%; margin: 1em 0em; " />';
- } else {
- while (preg_match("@(^|<pre>|\n)>@i", $res)) {
- $res = preg_replace("@(^|<pre>|\n)((>[^\n]*(?:\n|$))+)@ie",
- "'\\1</pre><blockquote><pre>'"
- ." . replaceQuotes('\\2')"
- ." . '</pre></blockquote><pre>'",
- $res);
- }
- $res = preg_replace("@<pre>-- ?\n@", "<pre>\n-- \n", $res);
- $parts = preg_split("/\n-- ?\n/", $res);
- $sign = '</pre><hr style="width: 100%; margin: 1em 0em; " /><pre>';
- }
-
- return join($sign, $parts);
-}
-
-// vim:set et sw=4 sts=4 ts=4
-?>
--- /dev/null
+<?php
+/********************************************************************************
+* banana/nntp.inc.php : NNTP protocole handler
+* ------------------------
+*
+* This file is part of the banana distribution
+* Copyright: See COPYING files that comes with this distribution
+********************************************************************************/
+
+require_once dirname(__FILE__) . '/banana.inc.php';
+require_once dirname(__FILE__) . '/message.inc.php';
+require_once dirname(__FILE__) . '/nntpcore.inc.php';
+require_once dirname(__FILE__) . '/protocoleinterface.inc.php';
+
+class BananaNNTP extends BananaNNTPCore implements BananaProtocoleInterface
+{
+ private $groupname = null;
+ private $description = null;
+ private $ingroup = null;
+
+ private $mode = null;
+ private $boxes = null;
+
+ /** Build a protocole handler plugged on the given box
+ */
+ public function __construct($box = null)
+ {
+ $url = parse_url(Banana::$host);
+ if ($url['scheme'] == 'nntps' || $url['scheme'] == 'snntp') {
+ $url['host'] = 'ssl://' . $url['host'];
+ }
+ if (!isset($url['port'])) {
+ $url['port'] = 119;
+ }
+ if (!isset($url['user'])) {
+ parent::__construct($url['host'], $url['port']);
+ } else {
+ parent::__construct($url['host'], $url['port'], 120, false);
+ $this->authinfo($url['user'], $url['pass']);
+ }
+ $this->groupname = $box;
+ }
+
+ /** Return the descript;ion of the current box
+ */
+ public function getDescription()
+ {
+ if ($this->description) {
+ return $this->description;
+ }
+ $descs = $this->xgtitle($this->groupname);
+ if (isset($descs[$this->groupname])) {
+ $this->description = $descs[$this->groupname];
+ }
+ return $this->description;
+ }
+
+ /** Return the list of the boxes
+ * @param mode Kind of boxes to list
+ * @param since date of last check (for new boxes and new messages)
+ * @return Array(boxname => array(desc => boxdescripton, msgnum => number of message, unread =>number of unread messages)
+ */
+ public function getBoxList($mode = Banana::BOXES_ALL, $since = 0, $withstats = false)
+ {
+ if (!is_array($this->boxes) || $this->mode != $mode) {
+ $descs = $this->xgtitle();
+ if ($mode == Banana::BOXES_NEW && $since) {
+ $list = $this->newgroups($since);
+ } else {
+ $list = $this->listGroups();
+ if ($mode == Banana::BOXES_SUB) {
+ $sub = array_flip(Banana::$profile['subscribe']);
+ $list = array_intersect_key($list, $sub);
+ }
+ }
+ $this->boxes = array();
+ foreach ($list as $group=>&$infos) {
+ if (isset($descs[$group])) {
+ $desc = $descs[$group];
+ if (!is_utf8($desc)) {
+ $desc = utf8_encode($desc);
+ }
+ $this->boxes[$group] = array('desc' => $desc);
+ } else {
+ $this->boxes[$group] = array('desc' => null);
+ }
+ }
+ ksort($this->boxes);
+ }
+ if ($withstats) {
+ foreach ($this->boxes as $group=>&$desc) {
+ list($msgnum, $first, $last, $groupname) = $this->group($group);
+ $this->ingroup = $group;
+ $new = count($this->newnews($group, $since));
+ $desc['msgnum'] = $msgnum;
+ $desc['unread'] = $new;
+ }
+ }
+ return $this->boxes;
+ }
+
+ /** Return a message
+ * @param id Id of the emssage (can be either an Message-id or a message index)
+ * @param msg_headers Headers to process
+ * @param is_msgid If is set, $id is en Message-Id
+ * @return A BananaMessage or null if the given id can't be retreived
+ */
+ public function getMessage($id, array $msg_headers = array(), $is_msgid = false)
+ {
+ if (!$is_msgid && $this->groupname != $this->ingroup) {
+ if (is_null(Banana::$spool)) {
+ $this->group($this->groupname);
+ $this->ingroup = $this->groupname;
+ } else {
+ $id = array_search($id, Banana::$spool->ids);
+ }
+ }
+ $data = $this->article($id);
+ if ($data !== false) {
+ return new BananaMessage($data);
+ }
+ return null;
+ }
+
+ /** Return the indexes of the messages presents in the Box
+ * @return Array(number of messages, MSGNUM of the first message, MSGNUM of the last message)
+ */
+ public function getIndexes()
+ {
+ list($msgnum, $first, $last, $groupname) = $this->group($this->groupname);
+ $this->ingroup = $this->groupname;
+ return array($msgnum, $first, $last);
+ }
+
+ /** Return the message headers (in BananaMessage) for messages from firstid to lastid
+ * @return Array(id => array(headername => headervalue))
+ */
+ public function &getMessageHeaders($firstid, $lastid, array $msg_headers = array())
+ {
+ $messages = array();
+ foreach ($msg_headers as $header) {
+ $headers = $this->xhdr($header, $firstid, $lastid);
+ array_walk($headers, array('BananaMimePart', 'decodeHeader'));
+ $header = strtolower($header);
+ if ($header == 'date') {
+ $headers = array_map('strtotime', $headers);
+ }
+ foreach ($headers as $id=>&$value) {
+ if (!isset($messages[$id])) {
+ $messages[$id] = array();
+ }
+ $messages[$id][$header] =& $value;
+ }
+ }
+ return $messages;
+ }
+
+ /** Add protocole specific data in the spool
+ */
+ public function updateSpool(array &$messages)
+ {
+ return true;
+ }
+
+ /** Return the indexes of the new messages since the give date
+ * @return Array(MSGNUM of new messages)
+ */
+ public function getNewIndexes($since)
+ {
+ return $this->newnews($this->groupname, $since);
+ }
+
+ /** Return true if can post
+ */
+ public function canSend()
+ {
+ return $this->isValid();
+ }
+
+ /** Return true if can cancel
+ */
+ public function canCancel()
+ {
+ return $this->isValid();
+ }
+
+ /** Return the list of requested header for a new post
+ */
+ public function requestedHeaders()
+ {
+ return Array('From', 'Subject', 'dest' => 'Newsgroups', 'reply' => 'Followup-To', 'Organization');
+ }
+
+ /** Send the message
+ */
+ public function send(BananaMessage &$message)
+ {
+ $sources = $message->get(true);
+ return $this->post($sources);
+ }
+
+ /** Cancel the message
+ */
+ public function cancel(BananaMessage &$message)
+ {
+ $headers = Array('From' => Banana::$profile['From'],
+ 'Newsgroups' => $this->groupname,
+ 'Subject' => 'cmsg ' . $message->getHeaderValue('message-id'),
+ 'Control' => 'cancel ' . $message->getHeaderValue('message-id'));
+ $headers = array_merge($headers, Banana::$custom_hdr);
+ $body = 'Message canceled with Banana';
+ $msg = BananaMessage::newMessage($headers, $body);
+ return $this->send($msg);
+ }
+
+ /** Return the protocole name
+ */
+ public function name()
+ {
+ return 'NNTP';
+ }
+}
+
+/*
+require_once dirname(__FILE__) . '/spool.inc.php';
+$time = microtime(true);
+$nntp = new BananaNNTP('xorg.promo.x2002');
+if (!$nntp->isValid()) {
+ echo "Beuh !\n";
+ exit;
+}
+Banana::$protocole =& $nntp;
+Banana::$spool =& BananaSpool::getSpool('xorg.promo.x2002');
+$msg = $nntp->getMessage(3424);
+echo '<html><head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <link rel="stylesheet" type="text/css" href="css/banana.css"/>
+</head><body><table class="banana_msg"><tr><td>';
+//echo $msg->getFormattedBody('plain');
+echo $msg->getFormattedBody();
+echo '</td></tr></table></body></html>', "\n";
+$end = microtime(true);
+echo ($end - $time) . "s\n";
+*/
+
+// vim:set et sw=4 sts=4 ts=4:
+?>
--- /dev/null
+<?php
+/********************************************************************************
+* include/nntpcore.inc.php : NNTP subroutines
+* -------------------------
+*
+* This file is part of the banana distribution
+* Copyright: See COPYING files that comes with this distribution
+********************************************************************************/
+
+require_once dirname(__FILE__) . '/banana.inc.php';
+
+/** Class NNTPCore
+ * implements some basic functions for NNTP protocol
+ */
+class BananaNNTPCore
+{
+ /** socket filehandle */
+ private $ns;
+ /** posting allowed */
+ private $posting;
+ /** last NNTP error code */
+ private $lasterrorcode;
+ /** last NNTP error text */
+ private $lasterrortext;
+ /** last NNTP result code */
+ private $lastresultcode;
+ /** last NNTP result */
+ private $lastresulttext;
+
+ /** debug mode */
+ private $debug = false;
+
+ /** constructor
+ * @param $host STRING NNTP host
+ * @parma $port STRING NNTP port
+ * @param $timeout INTEGER socket timeout
+ * @param $reader BOOLEAN sends a "MODE READER" at connection if true
+ */
+ public function __construct($host, $port = 119, $timeout = 120, $reader = true)
+ {
+ if (Banana::$debug_nntp) {
+ $this->debug = true;
+ }
+ $this->ns = fsockopen($host, $port, $errno, $errstr, $timeout);
+ $this->lasterrorcode = $errno;
+ $this->lasterrortext = $errstr;
+ if (is_null($this->ns)) {
+ return;
+ }
+
+ $this->checkState();
+ $this->posting = ($this->lastresultcode == '200');
+ if ($reader && $this->posting) {
+ $this->execLine('MODE READER');
+ $this->posting = ($this->lastresultcode == '200');
+ }
+ if (!$this->posting) {
+ $this->quit();
+ }
+ }
+
+ public function __destruct()
+ {
+ $this->quit();
+ }
+
+# Accessors
+
+ public function isValid()
+ {
+ return !is_null($this->ns) && $this->posting;
+ }
+
+ public function lastErrNo()
+ {
+ return $this->lasterrorcode;
+ }
+
+ public function lastError()
+ {
+ return $this->lasterrortext;
+ }
+
+# Socket functions
+
+ /** get a line from server
+ * @return STRING
+ */
+ private function getLine()
+ {
+ return rtrim(fgets($this->ns, 1200));
+ }
+
+ /** fetch data (and on delimitor)
+ * @param STRING $delim string indicating and of transmission
+ */
+ private function fetchResult($callback = null)
+ {
+ $array = Array();
+ while (($result = $this->getLine()) != '.') {
+ if (!is_null($callback)) {
+ list($key, $result) = call_user_func($callback, $result);
+ if (is_null($result)) {
+ continue;
+ }
+ if (is_null($key)) {
+ $array[] = $result;
+ } else {
+ $array[$key] = $result;
+ }
+ } else {
+ $array[] = $result;
+ }
+ }
+ return $array;
+ }
+
+ /** puts a line on server
+ * @param STRING $line line to put
+ */
+ private function putLine($line, $format = false)
+ {
+ if ($format) {
+ $line = str_replace(array("\r", "\n"), '', $line);
+ $line .= "\r\n";
+ }
+ if ($this->debug) {
+ $db_line = preg_replace('/PASS .*/', 'PASS *******', $line);
+ echo $db_line;
+ }
+ return fputs($this->ns, $line, strlen($line));
+ }
+
+ /** put a message (multiline)
+ */
+ private function putMessage($message = false)
+ {
+ if (is_array($message)) {
+ $message = join("\n", $_message);
+ }
+ if ($message) {
+ $this->putLine("$message\r\n", false);
+ }
+ return $this->execLine('.');
+ }
+
+
+ /** exec a command a check result
+ * @param STRING $line line to exec
+ */
+ private function execLine($line, $strict_state = true)
+ {
+ if (!$this->putLine($line, true)) {
+ return null;
+ }
+ return $this->checkState($strict_state);
+ }
+
+ /** check if last command was successfull (read one line)
+ * @param BOOL $strict indicate if 1XX codes are interpreted as errors (true) or success (false)
+ */
+ private function checkState($strict = true)
+ {
+ $result = $this->getLine();
+ if ($this->debug) {
+ echo "$result\n";
+ }
+ $this->lastresultcode = substr($result, 0, 3);
+ $this->lastresulttext = substr($result, 4);
+ $c = $this->lastresultcode{0};
+ if ($c == '2' || (($c == '1' || $c == '3') && !$strict)) {
+ return true;
+ } else {
+ $this->lasterrorcode = $this->lastresultcode;
+ $this->lasterrortext = $this->lastresulttext;
+ return false;
+ }
+ }
+
+# strict NNTP Functions [RFC 977]
+# see http://www.faqs.org/rfcs/rfc977.html
+
+ /** authentification
+ * @param $user STRING login
+ * @param $pass INTEGER password
+ * @return BOOLEAN true if authentication was successful
+ */
+ protected function authinfo($user, $pass)
+ {
+ if ($this->execLine("AUTHINFO USER $user", false)) {
+ return $this->execline("AUTHINFO PASS $pass");
+ }
+ return false;
+ }
+
+ /** retrieves an article
+ * MSGID is a numeric ID a shown in article's headers. MSGNUM is a
+ * server-dependent ID (see X-Ref on many servers) and retriving
+ * an article by this way will change the current article pointer.
+ * If an error occur, false is returned.
+ * @param $_msgid STRING MSGID or MSGNUM of article
+ * @return ARRAY lines of the article
+ * @see body
+ * @see head
+ */
+ protected function article($msgid = "")
+ {
+ if (!$this->execLine("ARTICLE $msgid")) {
+ return false;
+ }
+ return $this->fetchResult();
+ }
+
+ /** post a message
+ * if an error occur, false is returned
+ * @param $_message STRING message to post
+ * @return STRING MSGID of article
+ */
+ protected function post($message)
+ {
+ if (!$this->execLine("POST ", false)) {
+ return false;
+ }
+ if (!$this->putMessage($message)) {
+ return false;
+ }
+ if (preg_match("/(<[^@>]+@[^@>]+>)/", $this->lastresulttext, $regs)) {
+ return $regs[0];
+ } else {
+ return true;
+ }
+ }
+
+ /** fetches the body of an article
+ * params are the same as article
+ * @param $_msgid STRING MSGID or MSGNUM of article
+ * @return ARRAY lines of the article
+ * @see article
+ * @see head
+ */
+ protected function body($msgid = '')
+ {
+ if ($this->execLine("BODY $msgid")) {
+ return false;
+ }
+ return $this->fetchResult();
+ }
+
+ /** fetches the headers of an article
+ * params are the same as article
+ * @param $_msgid STRING MSGID or MSGNUM of article
+ * @return ARRAY lines of the article
+ * @see article
+ * @see body
+ */
+ protected function head($msgid = '')
+ {
+ if (!$this->execLine("HEAD $msgid")) {
+ return false;
+ }
+ return $this->fetchResult();
+ }
+
+ /** set current group
+ * @param $_group STRING
+ * @return ARRAY array : nb of articles in group, MSGNUM of first article, MSGNUM of last article, and group name
+ */
+ protected function group($group)
+ {
+ if (!$this->execLine("GROUP $group")) {
+ return false;
+ }
+ $array = explode(' ', $this->lastresulttext);
+ if (count($array) >= 4) {
+ return array_slice($array, 0, 4);
+ }
+ return false;
+ }
+
+ /** set the article pointer to the previous article in current group
+ * @return STRING MSGID of article
+ * @see next
+ */
+ protected function last()
+ {
+ if (!$this->execLine("LAST ")) {
+ return false;
+ }
+ if (preg_match("/^\d+ (<[^>]+>)/", $this->lastresulttext, $regs)) {
+ return $regs[1];
+ }
+ return false;
+ }
+
+ /** set the article pointer to the next article in current group
+ * @return STRING MSGID of article
+ * @see last
+ */
+
+ protected function next()
+ {
+ if (!$this->execLine('NEXT ')) {
+ return false;
+ }
+ if (preg_match("/^\d+ (<[^>]+>)/", $this->lastresulttext, $regs)) {
+ return $regs[1];
+ }
+ return false;
+ }
+
+ /** set the current article pointer
+ * @param $_msgid STRING MSGID or MSGNUM of article
+ * @return BOOLEAN true if authentication was successful, error code otherwise
+ * @see article
+ * @see body
+ */
+ protected function nntpstat($msgid)
+ {
+ if (!$this->execLine("STAT $msgid")) {
+ return false;
+ }
+ if (preg_match("/^\d+ (<[^>]+>)/", $this->lastresulttext, $regs)) {
+ return $regs[1];
+ }
+ return false;
+ }
+
+ /** filter group list
+ */
+ private function filterGroups()
+ {
+ $list = $this->fetchResult();
+
+ $groups = array();
+ foreach ($list as $result) {
+ list($group, $last, $first, $p) = explode(' ', $result, 4);
+ if (!is_null(Banana::$grp_pattern) || preg_match('@' .Banana::$grp_pattern . '@', $group)) {
+ $groups[$group] = array(intval($last), intval($first), $p);
+ }
+ }
+ return $groups;
+ }
+
+ /** gets information about all active newsgroups
+ * @return ARRAY group name => (MSGNUM of first article, MSGNUM of last article, NNTP flags)
+ * @see newgroups
+ */
+ protected function listGroups()
+ {
+ if (!$this->execLine('LIST')) {
+ return false;
+ }
+ return $this->filterGroups();
+ }
+
+ /** format date for news server
+ * @param since UNIX TIMESTAMP
+ */
+ protected function formatDate($since)
+ {
+ return gmdate("ymd His", $since) . ' GMT';
+ }
+
+ /** get information about recent newsgroups
+ * same as list, but information are limited to newgroups created after $_since
+ * @param $_since INTEGER unix timestamp
+ * @param $_distributions STRING distributions
+ * @return ARRAY same format as liste
+ * @see liste
+ */
+ protected function newgroups($since, $distributions = '')
+ {
+ if (!($since = $this->formatDate($since))) {
+ return false;
+ }
+ if (!$this->execLine("NEWGROUPS $since $distributions")) {
+ return false;
+ }
+ return $this->filterGroups();
+ }
+
+ /** gets a list of new articles
+ * @param $_since INTEGER unix timestamp
+ * @parma $_groups STRING pattern of intersting groups
+ * @return ARRAY MSGID of new articles
+ */
+ protected function newnews($groups = '*', $since = 0, $distributions = '')
+ {
+ if (!($since = $this->formatDate($since))) {
+ return false;
+ }
+ if (!$this->execLine("NEWNEWS $groups $since $distributions")) {
+ return false;
+ }
+ return $this->fetchResult();
+ }
+
+ /** Tell the remote server that I am not a user client, but probably another news server
+ * @return BOOLEAN true if sucessful
+ */
+ protected function slave()
+ {
+ return $this->execLine("SLAVE ");
+ }
+
+ /** implements IHAVE method
+ * @param $_msgid STRING MSGID of article
+ * @param $_message STRING article
+ * @return BOOLEAN
+ */
+ protected function ihave($msgid, $message = false)
+ {
+ if (!$this->execLine("IHAVE $msgid ")) {
+ return false;
+ }
+ return $this->putMessage($message);
+ }
+
+ /** closes connection to server
+ */
+ protected function quit()
+ {
+ $this->execLine('QUIT');
+ fclose($this->ns);
+ $this->ns = null;
+ $this->posting = false;
+ }
+
+# NNTP Extensions [RFC 2980]
+
+ /** Returns the date on the remote server
+ * @return INTEGER timestamp
+ */
+
+ protected function date()
+ {
+ if (!$this->execLine('DATE ', false)) {
+ return false;
+ }
+ if (preg_match("/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/", $this->lastresulttext, $r)) {
+ return gmmktime($r[4], $r[5], $r[6], $r[2], $r[3], $r[1]);
+ }
+ return false;
+ }
+
+ /** returns group descriptions
+ * @param $_pattern STRING pattern of intersting groups
+ * @return ARRAY group name => description
+ */
+
+ protected function xgtitle($pattern = '*')
+ {
+ if (!$this->execLine("XGTITLE $pattern ")) {
+ return false;
+ }
+ $array = $this->fetchResult();
+ $groups = array();
+ foreach ($array as $result) {
+ list($group, $desc) = split("[ \t]", $result, 2);
+ $groups[$group] = $desc;
+ }
+ return $groups;
+ }
+
+ /** obtain the header field $hdr for all the messages specified
+ * @param $_hdr STRING name of the header (eg: 'From')
+ * @param $_range STRING range of articles
+ * @return ARRAY MSGNUM => header value
+ */
+ protected function xhdr($hdr, $first = null, $last = null)
+ {
+ if (is_null($first) && is_null($last)) {
+ $range = "";
+ } else {
+ $range = $first . '-' . $last;
+ }
+ if (!$this->execLine("XHDR $hdr $range ")) {
+ return false;
+ }
+ $array = $this->fetchResult();
+ $headers = array();
+ foreach ($array as &$result) {
+ @list($head, $value) = explode(' ', $result, 2);
+ $headers[$head] = $value;
+ }
+ return $headers;
+ }
+
+ /** obtain the header field $_hdr matching $_pat for all the messages specified
+ * @param $_hdr STRING name of the header (eg: 'From')
+ * @param $_range STRING range of articles
+ * @param $_pat STRING pattern
+ * @return ARRAY MSGNUM => header value
+ */
+ protected function xpat($_hdr, $_range, $_pat)
+ {
+ if (!$this->execLine("XPAT $hdr $range $pat")) {
+ return false;
+ }
+ $array = $this->fetchResult();
+ $headers = array();
+ foreach ($array as &$result) {
+ list($head, $value) = explode(' ', $result, 2);
+ $headers[$head] = $result;
+ }
+ return $headers;
+ }
+}
+
+// vim:set et sw=4 sts=4 ts=4
+?>
--- /dev/null
+<?php
+/********************************************************************************
+* banana/page.inc.php : class for group lists
+* ------------------------
+*
+* This file is part of the banana distribution
+* Copyright: See COPYING files that comes with this distribution
+********************************************************************************/
+
+require_once 'smarty/libs/Smarty.class.php';
+
+class BananaPage extends Smarty
+{
+ private $error = array();
+ private $page = null;
+
+ private $pages = array();
+ private $actions = array();
+
+ public function __construct()
+ {
+ $this->Smarty();
+
+ $this->compile_check = Banana::$debug_smarty;
+ $this->template_dir = dirname(__FILE__) . '/templates/';
+ $this->compile_dir = dirname(dirname(__FILE__)) . '/spool/templates_c/';
+ $this->register_prefilter('banana_trimwhitespace');
+
+ }
+
+ public function trig($message)
+ {
+ $this->error[] = $message;
+ }
+
+ public function kill($message)
+ {
+ $this->trig($message);
+ return $this->run();
+ }
+
+ public function setPage($page)
+ {
+ $this->page = $page;
+ }
+
+ public function registerAction($action_code, array $pages = null)
+ {
+ $this->actions[] = array('text' => $action_code, 'pages' => $pages);
+ return true;
+ }
+
+ public function registerPage($name, $text, $template = null)
+ {
+ $this->pages[$name] = array('text' => $text, 'template' => $template);
+ return true;
+ }
+
+ public function run()
+ {
+ $this->registerPage('subscribe', _b_('Abonnements'), null);
+ $this->registerPage('forums', _b_('Les forums'), null);
+ if (!is_null(Banana::$group)) {
+ $this->registerPage('thread', Banana::$group, null);
+ if (!is_null(Banana::$artid)) {
+ $this->registerPage('message', _b_('Message'), null);
+ if ($this->page == 'cancel') {
+ $this->registerPage('cancel', _b_('Annulation'), null);
+ } elseif ($this->page == 'new') {
+ $this->registerPage('new', _b_('Répondre'), null);
+ }
+ } elseif ($this->page == 'new') {
+ $this->registerPage('new', _b_('Nouveau'), null);
+ }
+ }
+ foreach ($this->actions as $key=>&$action) {
+ if (!is_null($action['pages']) && !in_array($this->page, $action['pages'])) {
+ unset($this->actions[$key]);
+ }
+ }
+ $this->assign('group', Banana::$group);
+ $this->assign('artid', Banana::$artid);
+ $this->assign('part', Banana::$part);
+ $this->assign('first', Banana::$first);
+ $this->assign('action', Banana::$action);
+ $this->assign('profile', Banana::$profile);
+ $this->assign('spool', Banana::$spool);
+ $this->assign('protocole', Banana::$protocole);
+
+ $this->assign('errors', $this->error);
+ $this->assign('page', $this->page);
+ $this->assign('pages', $this->pages);
+ $this->assign('actions', $this->actions);
+
+ $this->register_function('url', array($this, 'makeUrl'));
+ $this->register_function('link', array($this, 'makeLink'));
+ $this->register_function('imglink', array($this, 'makeImgLink'));
+ $this->register_function('img', array($this, 'makeImg'));
+ if (!Banana::$debug_smarty) {
+ $error_level = error_reporting(0);
+ }
+ $text = $this->fetch('banana-base.tpl');
+ $text = banana_utf8entities($text);
+ if (!Banana::$debug_smarty) {
+ error_reporting($error_level);
+ }
+ return $text;
+ }
+
+ public function makeUrl($params, &$smarty = null)
+ {
+ if (function_exists('hook_makeLink')
+ && $res = hook_makeLink($params)) {
+ return $res;
+ }
+ $proto = empty($_SERVER['HTTPS']) ? 'http://' : 'https://';
+ $host = $_SERVER['HTTP_HOST'];
+ $file = $_SERVER['PHP_SELF'];
+
+ if (count($params) != 0) {
+ $get = '?';
+ foreach ($params as $key=>$value) {
+ if (strlen($get) != 1) {
+ $get .= '&';
+ }
+ $get .= $key . '=' . $value;
+ }
+ } else {
+ $get = '';
+ }
+ return $proto . $host . $file . $get;
+ }
+
+ public function makeLink($params, &$smarty = null)
+ {
+ $catch = array('text', 'popup', 'class', 'accesskey');
+ foreach ($catch as $key) {
+ ${$key} = isset($params[$key]) ? $params[$key] : null;
+ unset($params[$key]);
+ }
+ $link = $this->makeUrl($params, &$smarty);
+ if (is_null($text)) {
+ $text = $link;
+ }
+ if (!is_null($accesskey)) {
+ $popup .= ' (raccourci : ' . $accesskey . ')';
+ }
+ if (!is_null($popup)) {
+ $popup = ' title="' . $popup . '"';
+ }
+ if (!is_null($class)) {
+ $class = ' class="' . $class . '"';
+ }
+ $target = null;
+ if (isset($params['action']) && $params['action'] == 'view') {
+ $target = ' target="_blank"';
+ }
+ if (!is_null($accesskey)) {
+ $accesskey = ' accesskey="' . $accesskey . '"';
+ }
+ return '<a href="' . htmlentities($link) . '"'
+ . $target . $popup . $class . $accesskey
+ . '>' . $text . '</a>';
+ }
+
+ public function makeImg($params, &$smarty = null)
+ {
+ $catch = array('img', 'alt', 'height', 'width');
+ foreach ($catch as $key) {
+ ${$key} = isset($params[$key]) ? $params[$key] : null;
+ }
+ $img .= ".gif";
+ if (function_exists('hook_makeImg')
+ && $res = hook_makeImg($img, $alt, $height, $width)) {
+ return $res;
+ }
+
+ if (!is_null($width)) {
+ $width = ' width="' . $width . '"';
+ }
+ if (!is_null($height)) {
+ $height = ' height="' . $height . '"';
+ }
+
+ $proto = empty($_SERVER['HTTPS']) ? 'http://' : 'https://';
+ $host = $_SERVER['HTTP_HOST'];
+ $file = dirname($_SERVER['PHP_SELF']) . '/img/' . $img;
+ $url = $proto . $host . $file;
+
+ return '<img src="' . $url . '"' . $height . $width . ' alt="' . _b_($alt) . '" />';
+ }
+
+ public function makeImgLink($params, &$smarty = null)
+ {
+ $params['alt'] = _b_($params['alt']);
+ $params['popup'] = $params['alt'];
+ $params['text'] = $this->makeImg($params, $smarty);
+ return $this->makeLink($params, $smarty);
+ }
+
+ /** Redirect to the page with the given parameter
+ * @ref makeLink
+ */
+ public function redirect($params = array())
+ {
+ header('Location: ' . $this->makeUrl($params));
+ }
+}
+
+// {{{ function banana_trimwhitespace
+
+function banana_trimwhitespace($source, &$smarty)
+{
+ $tags = array('script', 'pre', 'textarea');
+
+ foreach ($tags as $tag) {
+ preg_match_all("!<{$tag}[^>]+>.*?</{$tag}>!is", $source, ${$tag});
+ $source = preg_replace("!<{$tag}[^>]+>.*?</{$tag}>!is", "&&&{$tag}&&&", $source);
+ }
+
+ // remove all leading spaces, tabs and carriage returns NOT
+ // preceeded by a php close tag.
+ $source = preg_replace('/((?<!\?>)\n)[\s]+/m', '\1', $source);
+
+ foreach ($tags as $tag) {
+ $source = preg_replace("!&&&{$tag}&&&!e", 'array_shift(${$tag}[0])', $source);
+ }
+
+ return $source;
+}
+
+// }}}
+
+
+// vim:set et sw=4 sts=4 ts=4:
+?>
+++ /dev/null
-<?php
-/********************************************************************************
-* include/posts.inc.php : class for posts
-* -----------------------
-*
-* This file is part of the banana distribution
-* Copyright: See COPYING files that comes with this distribution
-********************************************************************************/
-
-/** class for posts
- */
-
-class BananaPost
-{
- var $id;
- /** headers */
- var $headers;
- /** body */
- var $body;
- /** formating */
- var $messages;
- /** attachment */
- var $pj;
- /** poster name */
- var $name;
- /** test validity */
- var $valid = true;
-
- /** constructor
- * @param $_id STRING MSGNUM or MSGID (a group should be selected in this case)
- */
- function BananaPost($_id)
- {
- global $banana;
- $this->id = $_id;
- $this->pj = array();
- $this->messages = array();
- if (!$this->_header()) {
- $this->valid = false;
- return null;
- }
-
-
- if ($body = $banana->nntp->body($_id)) {
- $this->body = join("\n", $body);
- } else {
- $this->valid = false;
- return null;
- }
-
- if (isset($this->headers['content-transfer-encoding'])) {
- if (preg_match("/base64/", $this->headers['content-transfer-encoding'])) {
- $this->body = base64_decode($this->body);
- } elseif (preg_match("/quoted-printable/", $this->headers['content-transfer-encoding'])) {
- $this->body = quoted_printable_decode($this->body);
- }
- }
-
- if ($this->_split_multipart($this->headers, $this->body)) {
- $this->set_body_to_part(0);
- } else {
- if(isset($mpart_type)) {
- $this->_split_multipart($mpart_type[1], $mpart_boundary[1]);
- }
- $this->_find_uuencode();
- $this->_fix_charset();
- }
- }
-
- /** find and add uuencoded attachments
- */
- function _find_uuencode()
- {
- if (preg_match_all('@\n(begin \d+ ([^\r\n]+)\r?(?:\n(?!end)[^\n]*)*\nend)@', $this->body, $matches, PREG_SET_ORDER)) {
- foreach ($matches as $match) {
- $mime = trim(exec('echo '.escapeshellarg($match[1]).' | uudecode -o /dev/stdout | file -bi -'));
- if ($mime != 'application/x-empty') {
- $this->body = trim(str_replace($match[0], '', $this->body));
- $body = $match[1];
- $header['content-type'] = $mime.'; name="'.$match[2].'"';
- $header['content-transfer-encoding'] = 'x-uuencode';
- $header['content-disposition'] = 'attachment; filename="'.$match[2].'"';
- $this->_add_attachment(Array('headers' => $header, 'body' => $body));
- }
- }
- }
- }
-
- /** split multipart messages
- * @param $type STRING multipart type description
- * @param $boundary STRING multipart boundary identification string
- */
- function _split_multipart($headers, $body)
- {
- if (!isset($headers['content-type'])
- || !preg_match("@multipart/([^;]+);@", $headers['content-type'], $type)) {
- return false;
- }
-
- preg_match("/boundary=\"?([^ \"]+)\"?/", $headers['content-type'], $boundary);
- $boundary = $boundary[1];
- $type = $type[1];
- $parts = preg_split("@\n--$boundary(--|\n)@", $body);
- foreach ($parts as $part) {
- $part = $this->_get_part($part);
- $local_header = $part['headers'];
- $local_body = $part['body'];
- if (!$this->_split_multipart($local_header, $local_body)) {
- $is_text = isset($local_header['content-type'])
- && preg_match("@text/([^;]+);@", $local_header['content-type'])
- && (!isset($local_header['content-disposition'])
- || !preg_match('@attachment@', $local_header['content-disposition']));
-
- // alternative ==> multiple formats for messages
- if ($type == 'alternative' && $is_text) {
- array_push($this->messages, $part);
-
- // !alternative ==> une body, others are attachments
- } else if ($is_text) {
- if (count($this->messages) == 0) {
- $this->body = $local_body;
- foreach (array_keys($local_header) as $key) {
- $this->header[$key] = $local_header[$key];
- }
- array_push($this->messages, $part);
- } else {
- $this->_add_attachment($part);
- }
- } else {
- $this->_add_attachment($part);
- }
- }
- }
- return true;
- }
-
- /** extract new headers from the part
- * @param $part STRING part of a multipart message
- */
- function _get_part($part)
- {
- global $banana;
-
- $local_headers = Array();
- $lines = split("\n", $part);
- while (count($lines)) {
- $line = array_shift($lines);
- if ($line != "") {
- if (preg_match('@^[\t\r ]+@', $line) && isset($hdr)) {
- $local_headers[$hdr] .= ' '.trim($line);
- } else {
- list($hdr, $val) = split(":[ \t\r]*", $line, 2);
- $hdr = strtolower($hdr);
- if (in_array($hdr, $banana->parse_hdr)) {
- $local_headers[$hdr] = $val;
- }
- }
- } else {
- break;
- }
- }
- $local_body = join("\n", $lines);
- if (isset($local_headers['content-transfer-encoding'])
- && preg_match("/quoted-printable/", $local_headers['content-transfer-encoding'])) {
- $local_body = quoted_printable_decode($local_body);
- }
- return Array('headers' => $local_headers, 'body' => $local_body);
- }
-
- /** add an attachment
- */
- function _add_attachment($part)
- {
- $local_header = $part['headers'];
- $local_body = $part['body'];
-
- if ((isset($local_header['content-disposition']) && preg_match('/filename="?([^"]+)"?/', $local_header['content-disposition'], $filename))
- || (isset($local_header['content-type']) && preg_match('/name="?([^"]+)"?/', $local_header['content-type'], $filename))) {
- $filename = $filename[1];
- }
- if (!isset($filename)) {
- $filename = "attachment" . count($this->pj);
- }
-
- if (isset($local_header['content-type'])
- && preg_match('/^\s*([^ ;]+);/', $local_header['content-type'], $mimetype)) {
- $mimetype = $mimetype[1];
- } else {
- return false;
- }
-
- if (isset($local_header['content-id'])
- && preg_match('/^\s*<([^> ]+)>/', $local_header['content-id'], $cid)) {
- $cid = $cid[1];
- } else {
- $cid = null;
- }
-
- array_push($this->pj, Array('MIME' => $mimetype,
- 'filename' => $filename,
- 'encoding' => strtolower($local_header['content-transfer-encoding']),
- 'data' => $local_body,
- 'cid' => $cid));
- return true;
- }
-
- /** Fix body charset (convert body to utf8)
- * @return false if failed
- */
- function _fix_charset()
- {
- if (isset($this->headers['content-type'])
- && preg_match('!charset="?([^;"]*)"?\s*(;|$)?!', $this->headers['content-type'], $matches)) {
- $body = iconv($matches[1], 'utf-8//IGNORE', $this->body);
- if (strlen($body) == 0) {
- return false;
- }
- $this->body = $body;
- } else {
- $this->body = utf8_encode($this->body);
- }
- return true;
- }
-
- /** return body in plain text (useful for messages without a text/plain part)
- */
- function get_body()
- {
- preg_match("@text/([^;]+);@", $this->headers['content-type'], $format);
- if ($format[1] == 'plain') {
- return $this->body;
- }
- if ($format[1] == 'richtext') {
- return htmlToPlainText(richtextToHtml($this->body));
- } else {
- return htmlToPlainText($this->body);
- }
- }
-
- /** return local url for the given cid
- * @param cid STRING
- */
- function find_attachment($cid)
- {
- global $banana;
- $i = 0;
- foreach ($this->pj as $pj) {
- if ($pj['cid'] == $cid) {
- return htmlentities(makeLink(Array('group' => $banana->state['group'],
- 'artid' => $this->id,
- 'pj' => $i,
- 'action' => 'view')));
- }
- $i++;
- }
- return 'cid:' . $cid;;
- }
-
- /** decode an attachment
- * @param pjid INT id of the attachment to decode
- * @param action BOOL action to execute : true=view, false=download
- */
- function get_attachment($pjid, $action = false)
- {
- if ($pjid >= count($this->pj)) {
- return false;
- } else {
- $file = $this->pj[$pjid];
- header('Content-Type: '.$file['MIME'].'; name="'.$file['filename'].'"');
- if (!$action) {
- header('Content-Disposition: attachment; filename="'.$file['filename'].'"');
- } else {
- header('Content-Disposition: inline; filename="'.$file['filename'].'"');
- }
- if ($file['encoding'] == 'base64') {
- echo base64_decode($file['data']);
- } else if ($file['encoding'] == 'x-uuencode') {
- passthru('echo '.escapeshellarg($file['data']).' | uudecode -o /dev/stdout');
- } else {
- header('Content-Transfer-Encoding: '.$file['encoding']);
- echo $file['data'];
- }
- return true;
- }
- }
-
- /** set body to represent the given part
- * @param partid INT index of the part in messages
- */
- function set_body_to_part($partid)
- {
- global $banana;
-
- if (count($this->messages) == 0) {
- return false;
- }
-
- $local_header = $this->messages[$partid]['headers'];
- $this->body = $this->messages[$partid]['body'];
- foreach ($banana->parse_hdr as $hdr) {
- if (isset($local_header[$hdr])) {
- $this->headers[$hdr] = $local_header[$hdr];
- }
- }
-
- $this->_fix_charset();
- return true;
- }
-
- function _header()
- {
- global $banana;
- $hdrs = $banana->nntp->head($this->id);
- if (!$hdrs) {
- return false;
- }
-
- // parse headers
- foreach ($hdrs as $line) {
- if (preg_match("/^[\t\r ]+/", $line)) {
- $line = ($hdr=="X-Face"?"":" ").ltrim($line);
- if (in_array($hdr, $banana->parse_hdr)) {
- $this->headers[$hdr] .= $line;
- }
- } else {
- list($hdr, $val) = split(":[ \t\r]*", $line, 2);
- $hdr = strtolower($hdr);
- if (in_array($hdr, $banana->parse_hdr)) {
- $this->headers[$hdr] = $val;
- }
- }
- }
- // decode headers
- foreach ($banana->hdecode as $hdr) {
- if (isset($this->headers[$hdr])) {
- $this->headers[$hdr] = headerDecode($this->headers[$hdr]);
- }
- }
-
- $this->name = $this->headers['from'];
- $this->name = preg_replace('/<[^ ]*>/', '', $this->name);
- $this->name = trim($this->name);
- return true;
- }
-
- function checkcancel()
- {
- if (function_exists('hook_checkcancel')) {
- return hook_checkcancel($this->headers);
- }
- if (!isset($_SESSION)) {
- return false;
- }
- return ($this->headers['from'] == $_SESSION['name'] . ' <' . $_SESSION['mail']. '>');
- }
-
- /** Make some links to browse the current newsgroup
- */
- function _browser()
- {
- global $banana;
- $ret = '<div class="banana_menu">';
- $actions = Array('nextUnread' => Array('next_unread', _b_('Message non-lu suivant'), 'u'),
- 'prevPost' => Array('prev', _b_('Article précédent'), 'a'),
- 'nextPost' => Array('next', _b_('Article suivant'), 'z'),
- 'prevThread' => Array('prev_thread', _b_('Discussion précédente'), 'q'),
- 'nextThread' => Array('next_thread', _b_('Discussion suivante'), 's'));
- foreach ($actions as $method=>$params) {
- $id = $banana->spool->$method($this->id);
- if (!is_null($id)) {
- $ret .= makeImgLink(Array('group' => $banana->state['group'],
- 'artid' => $id),
- $params[0] . '.gif',
- $params[1],
- null, null, null,
- $params[2]);
- }
- }
- return $ret . '</div>';
- }
-
- /** convert message to html
- * @param partid INT id of the multipart message that must be displaid
- */
- function to_html($partid = -1)
- {
- global $banana;
-
- if (count($this->messages) > 1) {
- if ($partid != -1) {
- $this->set_body_to_part($partid);
- } else {
- // Select prefered text-format
- foreach ($banana->body_mime as $mime) {
- for ($id = 0 ; $id < count($this->messages) ; $id++) {
- if (preg_match("@$mime@", $this->messages[$id]['headers']['content-type'])) {
- $partid = $id;
- $this->set_body_to_part($partid);
- break;
- }
- }
- if ($partid != -1) {
- break;
- }
- }
- if ($partid == -1) {
- $partid = 0;
- }
- }
- } else {
- $partid = 0;
- }
-
- $res = '<table class="bicol banana_msg" cellpadding="0" cellspacing="0">';
- $res .= '<tr><th colspan="2" class="subject">'
- . $this->_browser()
- . '<div class="banana_action">'
- . makeImgLink(Array('group' => $banana->state['group'],
- 'action' => 'new'),
- 'post.gif',
- _b_('Nouveau message'), null, null, null, 'p') . ' '
- . makeImgLink(Array('group' => $banana->state['group'],
- 'artid' => $this->id,
- 'action' => 'new'),
- 'reply.gif',
- _b_('Répondre'), null, null, null, 'r');
- if ($this->checkCancel()) {
- $res .= ' '
- . makeImgLink(Array('group' => $banana->state['group'],
- 'artid' => $this->id,
- 'action' => 'cancel'),
- 'cancel.gif',
- _b_('Annuler'), null, null, null, 'c');
- }
- $res .= '</div>'
- . formatDisplayHeader('subject', $this->headers['subject'])
- . '</th></tr>'
- . '<tr class="pair"><td class="headers"><table cellpadding="0" cellspacing="0">';
-
- $xface = null;
- foreach ($banana->show_hdr as $hdr) {
- if (isset($this->headers[$hdr])) {
- $res2 = formatdisplayheader($hdr, $this->headers[$hdr]);
- if ($res2 && ($hdr != 'x-face' || !$banana->formatxface)) {
- $res .= '<tr><td class="hdr">'.header_translate($hdr)."</td><td class='val'>$res2</td></tr>\n";
- } else if ($res2) {
- $xface = $res2;
- }
- }
- }
- $res .= '</table></td><td class="xface">';
-
- if ($xface) {
- $res .= $xface;
- }
- $res .= '</td></tr>';
-
- if (count($this->messages) > 1) {
- $res .= '<tr><th colspan="2">';
- for ($i = 0 ; $i < count($this->messages) ; $i++) {
- if ($i != 0) {
- $res .= ' . ';
- }
- preg_match("@text/([^;]+);@", $this->messages[$i]['headers']['content-type'], $format);
- $format = textFormat_translate($format[1]);
- if ($i != $partid) {
- $res .= makeHREF(Array('group' => $banana->state['group'],
- 'artid' => $this->id,
- 'part' => $i),
- $format);
- } else {
- $res .= $format;
- }
- }
- $res .= '</th></tr>';
- }
-
- if (isset($this->headers['content-type'])
- && preg_match("@text/([^;]+);@", $this->headers['content-type'], $format)) {
- $format = $format[1];
- } else {
- $format = 'plain';
- }
- $res .= '<tr class="impair"><td colspan="2" class="body"';
- if ($format == 'html') {
- if (preg_match('@<body[^>]*bgcolor="?([#0-9a-f]+)"?[^>]*>@i', $this->body, $bgcolor)) {
- $res .= ' bgcolor="'.$bgcolor[1].'"';
- }
- $this->body = preg_replace('/cid:([^\'" ]+)/e', "find_attachment('\\1')", $this->body);
- $res .= '>'.formatbody($this->body, $format);
- } else {
- $res .= '><pre>'.formatbody($this->body).'</pre>';
- }
- $res .= '</td></tr>';
-
- if (count($this->pj) > 0) {
- $res .= '<tr><th colspan="2">'._b_('Pièces jointes').'</th></tr>';
- $res .= '<tr><td colspan="2">';
- $i = 0;
- foreach ($this->pj as $file) {
- $res .= makeImgLink(Array('group' => $banana->state['group'],
- 'artid' => $this->id,
- 'pj' => $i),
- 'save.gif',
- _b_('Télécharger')) . ' ';
- $res .= makeImgLink(Array('group' => $banana->state['group'],
- 'artid' => $this->id,
- 'pj' => $i,
- 'action'=> 'view'),
- 'preview.gif',
- _b_('Aperçu'));
- $res .= ' ' . $file['filename'].' ('.$file['MIME'].')';
- $res .= '<br/>';
- $i++;
- }
- $res .= '</td></tr>';
- }
-
- $ndx = $banana->spool->getndx($this->id);
- $res .= '<tr><td class="thrd" colspan="2">'
- . $banana->spool->to_html($ndx-$banana->tbefore, $ndx+$banana->tafter, $ndx)
- . '</td></tr>';
- return $res.'</table>';
- }
-}
-
-/** Wrapper for Post::find_attachment
- */
-function find_attachment($cid)
-{
- global $banana;
- return $banana->post->find_attachment($cid);
-}
-
-// vim:set et sw=4 sts=4 ts=4
-?>
--- /dev/null
+<?php
+/********************************************************************************
+* banana/protocoleinterface.inc.php : interface for box access
+* ------------------------
+*
+* This file is part of the banana distribution
+* Copyright: See COPYING files that comes with this distribution
+********************************************************************************/
+
+require_once dirname(__FILE__) . '/banana.inc.php';
+
+interface BananaProtocoleInterface
+{
+ /** Build a protocole handler plugged on the given box
+ */
+ public function __construct($box = null);
+
+ /** Indicate if the Protocole handler has been succesfully built
+ */
+ public function isValid();
+
+ /** Indicate last error n°
+ */
+ public function lastErrNo();
+
+ /** Indicate last error text
+ */
+ public function lastError();
+
+ /** Return the description of the current box
+ */
+ public function getDescription();
+
+ /** Return the list of the boxes
+ * @param mode Kind of boxes to list
+ * @param since date of last check (for new boxes and new messages)
+ * @param withstats Indicated whether msgnum and unread must be set in the result
+ * @return Array(boxname => array(desc => boxdescripton, msgnum => number of message, unread =>number of unread messages)
+ */
+ public function getBoxList($mode = Banana::BOXES_ALL, $since = 0, $withstats = false);
+
+ /** Return a message
+ * @param id Id of the emssage (can be either an Message-id or a message index)
+ * @param msg_headers Headers to process
+ * @param is_msgid If is set, $id is en Message-Id
+ * @return A BananaMessage or null if the given id can't be retreived
+ */
+ public function getMessage($id, array $msg_headers = array(), $is_msgid = false);
+
+ /** Return the indexes of the messages presents in the Box
+ * @return Array(number of messages, MSGNUM of the first message, MSGNUM of the last message)
+ */
+ public function getIndexes();
+
+ /** Return the message headers (in BananaMessage) for messages from firstid to lastid
+ * @return Array(id => array(headername => headervalue))
+ */
+ public function &getMessageHeaders($firstid, $lastid, array $msg_headers = array());
+
+ /** Update the spool to add protocole specifics data
+ * @param Array(id => message headers)
+ */
+ public function updateSpool(array &$messages);
+
+ /** Return the indexes of the new messages since the give date
+ * @return Array(MSGNUM of new messages)
+ */
+ public function getNewIndexes($since);
+
+ /** Return wether or not the protocole can be used to add new messages
+ */
+ public function canSend();
+
+ /** Return wether or not the protocole allow message deletion
+ */
+ public function canCancel();
+
+ /** Return the list of requested headers
+ * @return Array('header1', 'header2', ...) with the key 'dest' for the destination header
+ * and 'reply' for the reply header, eg:
+ * * for a mail: Array('From', 'Subject', 'dest' => 'To', 'Cc', 'Bcc', 'reply' => 'Reply-To')
+ * * for a post: Array('From', 'Subject', 'dest' => 'Newsgroups', 'reply' => 'Followup-To')
+ */
+ public function requestedHeaders();
+
+ /** Send a message
+ * @return true if it was successfull
+ */
+ public function send(BananaMessage &$message);
+
+ /** Cancel a message
+ * @return true if it was successfull
+ */
+ public function cancel(BananaMessage &$message);
+
+ /** Return the protocole name
+ */
+ public function name();
+}
+
+// vim:set et sw=4 sts=4 ts=4:
+?>
* Copyright: See COPYING files that comes with this distribution
********************************************************************************/
-if(!function_exists('file_put_contents')) {
- function file_put_contents($filename, $data)
- {
- $fp = fopen($filename, 'w');
- if(!$fp) {
- trigger_error('file_put_contents cannot write in file '.$filename, E_USER_ERROR);
- return;
- }
- fputs($fp, $data);
- fclose($fp);
- }
-}
+require_once dirname(__FILE__) . '/banana.inc.php';
-function spoolCompare($a,$b) { return ($b->date>=$a->date); }
+define('BANANA_SPOOL_VERSION', '0.3');
/** Class spoolhead
* class used in thread overviews
class BananaSpoolHead
{
/** date (timestamp) */
- var $date;
+ public $date;
/** subject */
- var $subject;
+ public $subject;
/** author */
- var $from;
+ public $from;
/** reference of parent */
- var $parent;
+ public $parent = null;
/** paren is direct */
- var $parent_direct;
+ public $parent_direct;
/** array of children */
- var $children = Array();
+ public $children = Array();
/** true if post is read */
- var $isread;
+ public $isread;
/** number of posts deeper in this branch of tree */
- var $desc;
+ public $desc;
/** same as desc, but counts only unread posts */
- var $descunread;
+ public $descunread;
+
+ /** storage data */
+ public $storage = array();
/** constructor
* @param $_date INTEGER timestamp of post
* @param $_read BOOLEAN true if read
* @param $_descunread INTEGER descunread value (0 for a new post)
*/
-
- function BananaSpoolHead($_date, $_subject, $_from, $_desc=1, $_read=true, $_descunread=0)
+ public function __construct(array &$message)
{
- $this->date = $_date;
- $this->subject = $_subject;
- $this->from = $_from;
- $this->desc = $_desc;
- $this->isread = $_read;
- $this->descunread = $_descunread;
+ $this->date = $message['date'];
+ $this->subject = stripslashes($message['subject']);
+ $this->from = $message['from'];
+ $this->desc = 1;
+ $this->isread = true;
+ $this->descunread = 0;
}
}
-/** Class spool
- * builds and updates spool
- */
-
-define("BANANA_SPOOL_VERSION", '0.2');
class BananaSpool
{
- var $version;
- /** spool */
- var $overview;
+ private $version;
+
/** group name */
- var $group;
+ public $group;
+ /** spool */
+ public $overview;
/** array msgid => msgnum */
- var $ids;
+ public $ids;
/** thread starts */
- var $roots;
- /** test validity */
- var $valid = true;
+ public $roots;
+
/** constructor
* @param $_group STRING group name
* @param $_display INTEGER 1 => all posts, 2 => only threads with new posts
* @param $_since INTEGER time stamp (used for read/unread)
*/
- function BananaSpool($_group, $_display=0, $_since="")
+ protected function __construct($group)
{
- global $banana;
- $this->group = $_group;
- $groupinfo = $banana->nntp->group($_group);
- if (!$groupinfo) {
- $this->valid = false;
- return null;
- }
-
- $this->_readFromFile();
-
- $do_save = false;
- $first = $banana->maxspool ? max($groupinfo[2] - $banana->maxspool, $groupinfo[1]) : $groupinfo[1];
- $last = $groupinfo[2];
+ $this->version = BANANA_SPOOL_VERSION;
+ $this->group = $group;
+ }
- if ($this->version == BANANA_SPOOL_VERSION && is_array($this->overview)) {
- $mids = array_keys($this->overview);
- foreach ($mids as $id) {
- if (($first <= $last && ($id < $first || $id > $last))
- || ($first > $last && $id < $first && $id > $last))
- {
- $this->delid($id, false);
- $do_save = true;
- }
- }
- if (!empty($this->overview)) {
- $first = max(array_keys($this->overview))+1;
- }
+ public static function getSpool($group, $since = 0)
+ {
+ if (!is_null(Banana::$spool) && Banana::$spool->group == $group) {
+ $spool = Banana::$spool;
} else {
- unset($this->overview, $this->ids);
- $this->version = BANANA_SPOOL_VERSION;
+ $spool = BananaSpool::readFromFile($group);
+ }
+ if (is_null($spool)) {
+ $spool = new BananaSpool($group);
}
+ Banana::$spool =& $spool;
+ $spool->build();
+ $spool->updateUnread($since);
+ return $spool;
+ }
- if ($first<=$last && $groupinfo[0]) {
- $do_save = true;
- $this->_updateSpool("$first-$last");
+ private static function spoolFilename($group)
+ {
+ $file = dirname(dirname(__FILE__));
+ $file .= '/spool/' . Banana::$protocole->name() . '/';
+ if (!is_dir($file)) {
+ mkdir($file);
}
-
- if ($do_save) { $this->_saveToFile(); }
-
- $this->_updateUnread($_since, $_display);
+ $url = parse_url(Banana::$host);
+ if (isset($url['host'])) {
+ $file .= $url['host'] . '_';
+ }
+ if (isset($url['port'])) {
+ $file .= $url['port'] . '_';
+ }
+ $file .= $group;
+ return $file;
}
- function _readFromFile()
+ private static function readFromFile($group)
{
- $file = $this->_spoolfile();
- if (file_exists($file)) {
- $temp = unserialize(file_get_contents($file));
- foreach (get_object_vars($temp) as $key=>$val) {
- $this->$key = $val;
- }
+ $file = BananaSpool::spoolFilename($group);
+ if (!file_exists($file)) {
+ return null;
+ }
+ $spool = unserialize(file_get_contents($file));
+ if ($spool->version != BANANA_SPOOL_VERSION) {
+ return null;
}
+ return $spool;
+ }
+
+ private function compare($a, $b)
+ {
+ return ($b->date >= $a->date);
}
- function _saveToFile()
+ private function saveToFile()
{
- $file = $this->_spoolfile();
- uasort($this->overview, "spoolcompare");
+ $file = BananaSpool::spoolFilename($this->group);
+ uasort($this->overview, array($this, 'compare'));
$this->roots = Array();
foreach($this->overview as $id=>$msg) {
file_put_contents($file, serialize($this));
}
- function _spoolfile()
+ private function build()
{
- global $banana;
- $url = parse_url($banana->host);
- $file = $url['host'].'_'.$url['port'].'_'.$this->group;
- return dirname(dirname(__FILE__)).'/spool/'.$file;
+ $threshold = 0;
+
+ // Compute the range of indexes
+ list($msgnum, $first, $last) = Banana::$protocole->getIndexes();
+ if ($last < $first) {
+ $threshold = $firt + $msgnum - $last;
+ $threshold = (int)(log($threshold)/log(2));
+ $threshold = (2 ^ ($threshold + 1)) - 1;
+ }
+ if (Banana::$maxspool && Banana::$maxspool < $msgnum) {
+ $first = $last - Banana::$maxspool;
+ if ($first < 0) {
+ $first += $threshold;
+ }
+ }
+ $clean = $this->clean($first, $last, $msgnum);
+ $update = $this->update($first, $last, $msgnum);
+
+ if ($clean || $update) {
+ $this->saveToFile();
+ }
+ }
+
+ private function clean(&$first, &$last, $msgnum)
+ {
+ $do_save = false;
+ if (is_array($this->overview)) {
+ $mids = array_keys($this->overview);
+ foreach ($mids as $id) {
+ if (($first <= $last && ($id < $first || $id > $last))
+ || ($first > $last && $id < $first && $id > $last))
+ {
+ $this->delid($id, false);
+ $do_save = true;
+ }
+ }
+ if (!empty($this->overview)) {
+ $first = max(array_keys($this->overview))+1;
+ }
+ }
+ return $do_save;
}
- function _updateSpool($arg)
+ private function update(&$first, &$last, $msgnum)
{
- global $banana;
- $dates = array_map('strtotime', $banana->nntp->xhdr('Date', $arg));
- $subjects = array_map('headerdecode', $banana->nntp->xhdr('Subject', $arg));
- $froms = array_map('headerdecode', $banana->nntp->xhdr('From', $arg));
- $msgids = $banana->nntp->xhdr('Message-ID', $arg);
- $refs = $banana->nntp->xhdr('References', $arg);
-
- if (is_array(@$this->ids)) {
- $this->ids = array_merge($this->ids, array_flip($msgids));
- } else {
- $this->ids = array_flip($msgids);
+ if ($first >= $last || !$msgnum) {
+ return false;
}
- foreach ($msgids as $id=>$msgid) {
- $msg = new BananaSpoolHead($dates[$id], $subjects[$id], $froms[$id]);
- $refs[$id] = str_replace('><', '> <', @$refs[$id]);
- $msgrefs = preg_split("/[ \t]/", strtr($refs[$id], $this->ids));
- $parents = preg_grep('/^\d+$/', $msgrefs);
- $msg->parent = array_pop($parents);
- $msg->parent_direct = preg_match('/^\d+$/', array_pop($msgrefs));
+ $messages =& Banana::$protocole->getMessageHeaders($first, $last,
+ array('Date', 'Subject', 'From', 'Message-ID', 'References', 'In-Reply-To'));
+
+ if (!is_array($this->ids)) {
+ $this->ids = array();
+ }
+ foreach ($messages as $id=>&$message) {
+ $this->ids[$message['message-id']] = $id;
+ }
- if (isset($this->overview[$id])) {
- $msg->desc = $this->overview[$id]->desc;
- $msg->children = $this->overview[$id]->children;
+ foreach ($messages as $id=>&$message) {
+ if (!isset($this->overview[$id])) {
+ $this->overview[$id] = new BananaSpoolHead($message);
}
- $this->overview[$id] = $msg;
+ $msg =& $this->overview[$id];
+ $msgrefs = BananaMessage::formatReferences($message);
+ $parents = preg_grep('/^\d+$/', $msgrefs);
+ $msg->parent = array_pop($parents);
+ $msg->parent_direct = preg_match('/^\d+$/', array_pop($msgrefs));
- if ($p = $msg->parent) {
+ if (!is_null($p = $msg->parent)) {
if (empty($this->overview[$p])) {
- $this->overview[$p] = new BananaSpoolHead($dates[$p], $subjects[$p], $froms[$p], 1);
+ $this->overview[$p] = new BananaSpoolHead($messages[$p]);
}
$this->overview[$p]->children[] = $id;
- while ($p) {
+ while (!is_null($p)) {
$this->overview[$p]->desc += $msg->desc;
- $p = $this->overview[$p]->parent;
+ if ($p != $this->overview[$p]->parent) {
+ $p = $this->overview[$p]->parent;
+ } else {
+ $p = null;
+ }
}
}
}
+ Banana::$protocole->updateSpool($messages);
+ return true;
}
- function _updateUnread($since, $mode)
+ private function updateUnread($since)
{
- global $banana;
- if (empty($since)) { return; }
-
- if (is_array($newpostsids = $banana->nntp->newnews($since, $this->group))) {
- if (!is_array($this->ids)) { $this->ids = array(); }
- $newpostsids = array_intersect($newpostsids, array_keys($this->ids));
- foreach ($newpostsids as $mid) {
- $this->overview[$this->ids[$mid]]->isread = false;
- $this->overview[$this->ids[$mid]]->descunread = 1;
- $parentmid = $this->ids[$mid];
- while (isset($parentmid)) {
- $this->overview[$parentmid]->descunread ++;
- $parentmid = $this->overview[$parentmid]->parent;
+ if (empty($since)) {
+ return;
+ }
+
+ $newpostsids = Banana::$protocole->getNewIndexes($since);
+
+ if (empty($newpostsids)) {
+ return;
+ }
+
+ if (!is_array($this->ids)) {
+ $this->ids = array();
+ }
+ $newpostsids = array_intersect($newpostsids, array_keys($this->ids));
+ foreach ($newpostsids as $mid) {
+ $id = $this->ids[$mid];
+ if ($this->overview[$id]->isread) {
+ $this->overview[$id]->isread = false;
+ $this->overview[$id]->descunread = 1;
+ while (isset($id)) {
+ $this->overview[$id]->descunread ++;
+ $id = $this->overview[$id]->parent;
}
}
+ }
+ }
- if (count($newpostsids)) {
- switch ($mode) {
- case 1:
- foreach ($this->roots as $k=>$i) {
- if ($this->overview[$i]->descunread==0) {
- $this->killdesc($i);
- unset($this->roots[$k]);
- }
- }
- break;
+ public function setMode($mode)
+ {
+ switch ($mode) {
+ case Banana::SPOOL_UNREAD:
+ foreach ($this->roots as $k=>$i) {
+ if ($this->overview[$i]->descunread == 0) {
+ $this->killdesc($i);
+ unset($this->roots[$k]);
}
}
+ break;
}
}
/** kill post and childrens
* @param $_id MSGNUM of post
*/
-
- function killdesc($_id)
+ private function killdesc($_id)
{
if (sizeof($this->overview[$_id]->children)) {
foreach ($this->overview[$_id]->children as $c) {
/** delete a post from overview
* @param $_id MSGNUM of post
*/
-
- function delid($_id, $write=true)
+ public function delid($_id, $write = true)
{
if (isset($this->overview[$_id])) {
if (sizeof($this->overview[$_id]->parent)) {
unset($this->ids[$msgid]);
}
- if ($write) { $this->_saveToFile(); }
+ if ($write) {
+ $this->saveToFile();
+ }
}
}
+ private function formatDate($stamp)
+ {
+ $today = intval(time() / (24*3600));
+ $dday = intval($stamp / (24*3600));
+
+ if ($today == $dday) {
+ $format = "%H:%M";
+ } elseif ($today == 1 + $dday) {
+ $format = _b_('hier')." %H:%M";
+ } elseif ($today < 7 + $dday) {
+ $format = '%a %H:%M';
+ } else {
+ $format = '%a %e %b';
+ }
+ return utf8_encode(strftime($format, $stamp));
+ }
+
/** displays children tree of a post
* @param $_id INTEGER MSGNUM of post
* @param $_index INTEGER linear number of post in the tree
* take the subject as a reference parameter, transform this subject to be displaid in the spool
* view and return a string. This string will be put after the subject.
*/
-
- function _to_html($_id, $_index, $_first=0, $_last=0, $_ref="", $_pfx_node="", $_pfx_end="", $_head=true)
+ private function _to_html($_id, $_index, $_first=0, $_last=0, $_ref="", $_pfx_node="", $_pfx_end="", $_head=true)
{
- $spfx_f = makeImg('k1.gif', 'o', 21, 9);
- $spfx_n = makeImg('k2.gif', '*', 21, 9);
- $spfx_Tnd = makeImg('T-direct.gif', '+', 21, 12);
- $spfx_Lnd = makeImg('L-direct.gif', '`', 21, 12);
- $spfx_snd = makeImg('s-direct.gif', '-', 21, 5);
- $spfx_T = makeImg('T.gif', '+', 21, 12);
- $spfx_L = makeImg('L.gif', '`', 21, 12);
- $spfx_s = makeImg('s.gif', '-', 21, 5);
- $spfx_e = makeImg('e.gif', ' ', 21, 12);
- $spfx_I = makeImg('I.gif', '|', 21, 12);
-
- if ($_index + $this->overview[$_id]->desc < $_first || $_index > $_last) {
- return;
+ static $spfx_f, $spfx_n, $spfx_Tnd, $spfx_Lnd, $spfx_snd, $spfx_T, $spfx_L, $spfx_s, $spfx_e, $spfx_I;
+ if (!isset($spfx_f)) {
+ $spfx_f = Banana::$page->makeImg(Array('img' => 'k1', 'alt' => 'o', 'height' => 21, 'width' => 9));
+ $spfx_n = Banana::$page->makeImg(Array('img' => 'k2', 'alt' => '*', 'height' => 21, 'width' => 9));
+ $spfx_Tnd = Banana::$page->makeImg(Array('img' => 'T-direct', 'alt' => '+', 'height' => 21, 'width' => 12));
+ $spfx_Lnd = Banana::$page->makeImg(Array('img' => 'L-direct', 'alt' => '`', 'height' => 21, 'width' => 12));
+ $spfx_snd = Banana::$page->makeImg(Array('img' => 's-direct', 'alt' => '-', 'height' => 21, 'width' => 5));
+ $spfx_T = Banana::$page->makeImg(Array('img' => 'T', 'alt' => '+', 'height' => 21, 'width' => 12));
+ $spfx_L = Banana::$page->makeImg(Array('img' => 'L', 'alt' => '`', 'height' => 21, 'width' => 12));
+ $spfx_s = Banana::$page->makeImg(Array('img' => 's', 'alt' => '-', 'height' => 21, 'width' => 5));
+ $spfx_e = Banana::$page->makeImg(Array('img' => 'e', 'alt' => ' ', 'height' => 21, 'width' => 12));
+ $spfx_I = Banana::$page->makeImg(Array('img' => 'I', 'alt' => '|', 'height' => 21, 'width' => 12));
}
- $res = '';
-
- if ($_index>=$_first) {
- $hc = empty($this->overview[$_id]->children);
+ $overview =& $this->overview[$_id];
+ if ($_index + $overview->desc < $_first || $_index > $_last) {
+ return '';
+ }
- $res .= '<tr class="'.($_index%2?'pair':'impair').($this->overview[$_id]->isread?'':' new')."\">\n";
- $res .= "<td class='date'>".fancyDate($this->overview[$_id]->date)." </td>\n";
- $res .= "<td class='subj'>"
- ."<div class='tree'>$_pfx_node".($hc?($_head?$spfx_f:($this->overview[$_id]->parent_direct?$spfx_s:$spfx_snd)):$spfx_n)
- ."</div>";
- $subject = $this->overview[$_id]->subject;
- if (strlen($subject) == 0) {
+ $res = '';
+ if ($_index >= $_first) {
+ $hc = empty($overview->children);
+
+ $res .= '<tr class="' . ($_index%2 ? 'pair' : 'impair') . ($overview->isread ? '' : ' new') . "\">\n";
+ $res .= '<td class="date">' . $this->formatDate($overview->date) . " </td>\n";
+ $res .= '<td class="subj' . ($_index == $_ref ? ' cur' : '') . '">'
+ . $_pfx_node .($hc ? ($_head ? $spfx_f : ($overview->parent_direct ? $spfx_s : $spfx_snd)) : $spfx_n);
+ $subject = $overview->subject;
+ if (empty($subject)) {
$subject = _b_('(pas de sujet)');
}
$link = null;
if (function_exists('hook_getSubject')) {
$link = hook_getSubject($subject);
}
- $subject = formatPlainText(htmlentities($subject));
- if ($_index == $_ref) {
- $res .= '<span class="cur">' . $subject . $link . '</span>';
- } else {
- $res .= makeHREF(Array('group' => $this->group,
- 'artid' => $_id),
- $subject,
- $subject)
- . $link;
+ $subject = banana_catchFormats($subject);
+ if ($_index != $_ref) {
+ $subject = Banana::$page->makeLink(Array('group' => $this->group, 'artid' => $_id,
+ 'text' => $subject, 'popup' => $subject));
}
- $res .= "</td>\n<td class='from'>".formatFrom($this->overview[$_id]->from)."</td>\n</tr>";
+ $res .= ' ' . $subject . $link;
+ $res .= "</td>\n<td class='from'>" . BananaMessage::formatFrom($overview->from) . "</td>\n</tr>";
- if ($hc) { return $res; }
+ if ($hc) {
+ return $res;
+ }
}
$_index ++;
-
- $children = $this->overview[$_id]->children;
+ $children = $overview->children;
while ($child = array_shift($children)) {
- if ($_index > $_last) { return $res; }
- if ($_index+$this->overview[$child]->desc >= $_first) {
+ $overview =& $this->overview[$child];
+ if ($_index > $_last) {
+ return $res;
+ }
+ if ($_index + $overview->desc >= $_first) {
if (sizeof($children)) {
$res .= $this->_to_html($child, $_index, $_first, $_last, $_ref,
- $_pfx_end.($this->overview[$child]->parent_direct?$spfx_T:$spfx_Tnd),
- $_pfx_end.$spfx_I, false);
+ $_pfx_end . ($overview->parent_direct ? $spfx_T : $spfx_Tnd),
+ $_pfx_end . $spfx_I, false);
} else {
$res .= $this->_to_html($child, $_index, $_first, $_last, $_ref,
- $_pfx_end.($this->overview[$child]->parent_direct?$spfx_L:$spfx_Lnd),
- $_pfx_end.$spfx_e, false);
+ $_pfx_end . ($overview->parent_direct ? $spfx_L : $spfx_Lnd),
+ $_pfx_end . $spfx_e, false);
}
}
- $_index += $this->overview[$child]->desc;
+ $_index += $overview->desc;
}
return $res;
* @param $_last INTEGER MSGNUM of last post
* @param $_ref STRING MSGNUM of current/selectionned post
*/
-
- function to_html($_first=0, $_last=0, $_ref = null)
+ public function toHtml($first = 0, $overview = false)
{
- $res = '<table class="bicol banana_thread" cellpadding="0" cellspacing="0">';
-
- if (is_null($_ref)) {
- $next = $this->nextUnread();
- if (!is_null($next)) {
- $next = '<div class="banana_menu">'
- . makeImgLink(Array('group' => $this->group,
- 'artid' => $next),
- 'next_unread.gif',
- _b_('Message non-lu suivant'), null, null, null, 'u')
- . '</div>';
- }
- $new = '<div class="banana_action">'
- . makeImgLink(Array('group' => $this->group,
- 'action' => 'new'),
- 'post.gif',
- _b_('Nouveau message'), null, null, null, 'p')
- . '</div>';
-
- $res .= '<tr><th>' . $next . _b_('Date') . '</th>';
- $res .= '<th>' . _b_('Sujet') . '</th>';
- $res .= '<th>' . $new . _b_('Auteur') . '</th></tr>';
+ $res = '';
+
+ if (!$overview) {
+ $_first = $first;
+ $_last = $first + Banana::$tmax - 1;
+ $_ref = null;
} else {
- $res .= '<tr><th colspan="3">' . _b_('Aperçu de ')
- . makeHREF(Array('group' => $this->group),
- $this->group)
- . '</th></tr>';
+ $_ref = $this->getNdx($first);
+ $_last = $_ref + Banana::$tafter;
+ $_first = $_ref - Banana::$tbefore;
+ if ($_first < 0) {
+ $_last -= $_first;
+ }
}
-
$index = 1;
- if (sizeof($this->overview)) {
- foreach ($this->roots as $id) {
- $res .= $this->_to_html($id, $index, $_first, $_last, $_ref);
- $index += $this->overview[$id]->desc ;
- if ($index > $_last) { break; }
+ foreach ($this->roots as $id) {
+ $res .= $this->_to_html($id, $index, $_first, $_last, $_ref);
+ $index += $this->overview[$id]->desc ;
+ if ($index > $_last) {
+ break;
}
- } else {
- $res .= '<tr><td colspan="3">'._b_('Aucun message dans ce forum').'</td></tr>';
- }
-
- global $banana;
- if (is_object($banana->groups)) {
- $res .= '<tr><td colspan="3" class="subs">'
- . $banana->groups->to_html()
- . '</td></tr>';
}
- return $res .= '</table>';
+ return $res;
}
/** computes linear post index
* @param $_id INTEGER MSGNUM of post
* @return INTEGER linear index of post
*/
-
- function getndx($_id)
+ public function getNdX($_id)
{
$ndx = 1;
$id_cur = $_id;
/** Return root message of the given thread
* @param id INTEGER id of a message
*/
- function root($id)
- {
+ public function root($id)
+ {
$id_cur = $id;
while (true) {
$id_parent = $this->overview[$id_cur]->parent;
/** Returns previous thread root index
* @param id INTEGER message number
*/
- function prevThread($id)
+ public function prevThread($id)
{
$root = $this->root($id);
$last = null;
/** Returns next thread root index
* @param id INTEGER message number
*/
- function nextThread($id)
+ public function nextThread($id)
{
$root = $this->root($id);
$ok = false;
/** Return prev post in the thread
* @param id INTEGER message number
*/
- function prevPost($id)
+ public function prevPost($id)
{
$parent = $this->overview[$id]->parent;
if (is_null($parent)) {
/** Return next post in the thread
* @param id INTEGER message number
*/
- function nextPost($id)
+ public function nextPost($id)
{
if (count($this->overview[$id]->children) != 0) {
return $this->overview[$id]->children[0];
/** Look for an unread message in the thread rooted by the message
* @param id INTEGER message number
*/
- function _nextUnread($id)
+ private function _nextUnread($id)
{
if (!$this->overview[$id]->isread) {
return $id;
/** Find next unread message
* @param id INTEGER message number
*/
- function nextUnread($id = null)
+ public function nextUnread($id = null)
{
if (!is_null($id)) {
// Look in message children
--- /dev/null
+{* *}
+{* *}
+{* *}
+
+<table class="cadre_a_onglet" style="width: 100%" cellpadding="0" cellspacing="0">
+ <tr>
+ <td>
+ <ul id="onglet">
+ {foreach from=$pages item=pg key=name}
+ {if $name eq $page}
+ <li class="actif">{$pg.text}</li>
+ {assign var=current_page value=$pg}
+ {else}
+ <li>{if $name eq 'subscribe'}{link subscribe=1 text=$pg.text}
+ {elseif $name eq 'forums'}{link text=$pg.text}
+ {elseif $name eq 'thread'}{link group=$group text=$group}
+ {elseif $name eq 'message'}{link group=$group artid=$artid text=$pg.text}
+ {else}{link page=$name text=$pg.text}
+ {/if}</li>
+ {/if}
+ {/foreach}
+ </ul>
+ </td>
+ </tr>
+ <tr>
+ <td class="conteneur_tab banana">
+ {foreach from=$errors item=error}
+ <p class="error">{$error}</p>
+ {/foreach}
+ {foreach from=$actions item=act}
+ <p class="center" style="padding: 0; margin: 0 0 1em 0">{$act.text}</p>
+ {/foreach}
+ {if $page eq 'forums'}
+ {include file="banana-boxlist.inc.tpl" grouplist=$groups withstats=true}
+ {if $newgroups|@count}
+ <p>Les nouveaux groupes suivants ont été créés depuis votre dernière visite</p>
+ {include file="banana-boxlist.inc.tpl" grouplist=$newgroups withstats=true}
+ {/if}
+ {elseif $page eq 'subscribe'}
+ {include file="banana-boxlist.inc.tpl" grouplist=$groups withsubs=true}
+ {elseif $page eq 'thread'}
+ {include file="banana-thread.inc.tpl" withtitle=true}
+ {elseif $page eq 'message'}
+ {include file="banana-message.inc.tpl"}
+ {include file="banana-thread.inc.tpl" withtitle=false}
+ {elseif $page eq 'new'}
+ {include file="banana-newmessage.inc.tpl"}
+ {elseif $page eq 'cancel'}
+ <p class="error">Voulez-vous vraiment annuler ce message ?</p>
+ <form action="{url group=$group artid=$artid action=cancel}" method="post">
+ <p class="center">
+ <input type="submit" name="cancel" value="Annuler !" />
+ </p>
+ </form>
+ {include file="banana-message.inc.tpl" noactions=true}
+ {elseif $current_page.template}
+ {include file=$current_page.template}
+ {/if}
+ </td>
+ </tr>
+</table>
+
+{* vim:set et sw=2 sts=2 ts=2: *}
--- /dev/null
+{if $groups|@count}
+{if $withsubs}
+<form action="{url subscribe=1}" method="post">
+<p style="text-align: center">
+ <input type="submit" name="validsubs" value="Valider" />
+</p>
+{/if}
+<table class="bicol">
+ <tr>
+ {if $withsubs}
+ <th></th>
+ {/if}
+ {if $withstats}
+ <th>Total</th>
+ <th>Nouveaux</th>
+ {/if}
+ <th>Nom</th>
+ <th>Description</th>
+ </tr>
+ {foreach from=$groups key=name item=grp}
+ <tr class="{cycle values="impair,pair"}">
+ {if $withsubs}
+ <td>
+ <input type="checkbox" name="subscribe[{$name}]" {if in_array($name, $profile.subscribe)}checked="checked"{/if} />
+ </td>
+ {/if}
+ {if $withstats}
+ <td style="text-align: center">{if $grp.msgnum eq 0}-{else}{$grp.msgnum}{/if}</td>
+ <td style="text-align: center">{if $grp.unread eq 0}-{else}{$grp.unread}{/if}</td>
+ {/if}
+ <td class="grp">{link group=$name text=$name}</td>
+ <td class="dsc">{$grp.desc}</td>
+ </tr>
+ {/foreach}
+</table>
+{if $withsubs}
+<p style="text-align: center">
+ <input type="submit" name="validsubs" value="Valider" />
+</p>
+</form>
+{/if}
+{/if}
+
+{* vim:set et sw=2 sts=2 ts=2: *}
--- /dev/null
+<table class="bicol message">
+ <tr>
+ <th colspan="3" class="subject">
+ {if !$noactions}
+ <div class="menu">
+ {if $spool->nextUnread($artid)}
+ {imglink group=$group artid=$spool->nextUnread($artid) img=next_unread alt="Message non-lu suivant" accesskey=u}{/if}
+ {if $spool->prevPost($artid)}
+ {imglink group=$group artid=$spool->prevPost($artid) img=prev alt="Message précédent" accesskey=a}{/if}
+ {if $spool->nextPost($artid)}
+ {imglink group=$group artid=$spool->nextPost($artid) img=next alt="Message suivant" accesskey=z}{/if}
+ {if $spool->prevThread($artid)}
+ {imglink group=$group artid=$spool->prevThread($artid) img=prev_thread alt="Discussion précédente" accesskey=q}{/if}
+ {if $spool->nextThread($artid)}
+ {imglink group=$group artid=$spool->nextThread($artid) img=next_thread alt="Discussion suivante" accesskey=s}{/if}
+ </div>
+ <div class="action">
+ {if $message->canSend()}
+ {imglink group=$group action="new" img=post alt="Nouveau messasge" accesskey=p}
+ {imglink group=$group artid=$artid action="new" img=reply alt="Répondre" accesskey=r}
+ {/if}
+ {if $message->canCancel()}
+ {imglink group=$group artid=$artid action="cancel" img=cancel alt="Annuler" accesskey=c}
+ {/if}
+ </div>
+ {/if}
+ {$message->translateHeaderValue('subject')}
+ </th>
+ </tr>
+ {foreach from=$headers name=headers item=hdr}
+ <tr class="pair">
+ <td class="hdr">{$message->translateHeaderName($hdr)}</td>
+ <td>{$message->translateHeaderValue($hdr)}</td>
+ {if $smarty.foreach.headers.first}
+ <td class="xface" rowspan="{$headers|@count}">
+ {if $message->hasXFace()}
+ <img src="{url group=$group artid=$artid part="xface"}" alt="[ X-Face ]" />
+ {/if}
+ </td>
+ {/if}
+ </tr>
+ {/foreach}
+ {assign var=files value=$message->getAttachments()}
+ {if $files|@count}
+ <tr class="pair">
+ <td class="hdr">Fichiers joints</td>
+ <td colspan="2">
+ {foreach from=$files item=file name=attachs}
+ {$file->getFilename()|htmlentities}
+ {imglink img=save alt="Enregistrer" group=$group artid=$artid part=$file->getFilename()}{if !$smarty.foreach.attachs.last}, {/if}
+ {/foreach}
+ </td>
+ </tr>
+ {/if}
+ <tr>
+ <td colspan="3" class="body">
+ {$message->getFormattedBody()}
+ </td>
+ </tr>
+</table>
+
+{* vim:set et sw=2 sts=2 ts=2: *}
--- /dev/null
+<form {if $can_attach}enctype="multipart/form-data"{/if} action="{url group=$group artid=$artid action=new}" method="post" accept-charset="utf-8">
+ <table class="bicol">
+ <tr>
+ <th colspan="2">Composer un nouveau message</th>
+ </tr>
+ {foreach from=$headers key=header item=values}
+ <tr>
+ <td>{$values.name}</td>
+ <td>
+ {if $values.fixed}
+ {$values.fixed|htmlentities}
+ {else}
+ <input type="text" name="{$header}" value="{$values.user|default:$smarty.request[$header]}" size="50" />
+ {/if}
+ </td>
+ </tr>
+ {/foreach}
+ <tr class="pair">
+ <td colspan="2" class="center">
+ <textarea name="body" cols="74" rows="16">{$body|default:$smarty.request.body}</textarea>
+ </td>
+ </tr>
+ {if $can_attach}
+ <tr class="pair">
+ <td>Fichier joint</td>
+ <td>
+ {if $maxfilesize}
+ <input type="hidden" name="MAX_FILE_SIZE" value="{$maxfilesize}" />
+ {/if}
+ <input type="file" name="attachment" size="40" />
+ </td>
+ </tr>
+ {/if}
+ <tr class="pair">
+ <td colspan="2" class="center">
+ <input type="submit" name="sendmessage" value="Envoyer le message" />
+ </td>
+ </tr>
+ </table>
+</form>
+
+{* vim:set et sts=2 ts=2 sw=2: *}
--- /dev/null
+{if $withtitle}
+<div class="pages">
+{if $spool->overview|@count > $msgbypage}
+{section name=pages loop=$spool->overview step=$msgbypage}
+ {if $first >= $smarty.section.pages.index && $first < $smarty.section.pages.index_next}
+ <strong>{$smarty.section.pages.iteration}</strong>
+ {else}
+ {link group=$group first=$smarty.section.pages.index text=$smarty.section.pages.iteration}
+ {/if}
+{/section}
+{/if}
+</div>
+{/if}
+<table class="bicol thread">
+ <tr>
+ {if $withtitle}
+ <th>
+ {if $spool->nextUnread()}
+ <div class="menu">
+ {imglink group=$group artid=$spool->nextUnread() img=next_unread alt="Message non-lu suivant" accesskey=u}
+ </div>
+ {/if}
+ Date
+ </th>
+ <th>Sujet</th>
+ <th>
+ {if $protocole->canSend()}
+ <div class="action">
+ {imglink group=$group action=new img=post alt="Nouveau message" accesskey=p}
+ </div>
+ {/if}
+ Auteur
+ </th>
+ {else}
+ <th colspan="3">
+ {link group=$group text=$group}
+ </th>
+ {/if}
+ </tr>
+ {if $spool->overview|@count}
+ {if $artid}{$spool->toHtml($artid, true)}{else}{$spool->toHtml($first)}{/if}
+ {else}
+ <tr>
+ <td colspan="3">
+ Aucun message dans ce forum
+ </td>
+ </tr>
+ {/if}
+</table>
+{include file="banana-boxlist.inc.tpl" grouplist=$groups withstats=true}
+{if $withtitle}
+<div class="pages">
+{if $spool->overview|@count > $msgbypage}
+{section name=pages loop=$spool->overview step=$msgbypage}
+ {if $first >= $smarty.section.pages.index && $first < $smarty.section.pages.index_next}
+ <strong>{$smarty.section.pages.iteration}</strong>
+ {else}
+ {link group=$group first=$smarty.section.pages.index text=$smarty.section.pages.iteration}
+ {/if}
+{/section}
+{/if}
+</div>
+{/if}
+
+{* vim:set et sw=2 sts=2 ts=2: *}
<?php\r
/********************************************************************************\r
-* banana/utf8.php : utf8 to html entities\r
+* banana/text.php : text tools\r
* ---------------\r
*\r
* This file is part of the banana distribution\r
* Copyright: See COPYING files that comes with this distribution\r
********************************************************************************/\r
\r
-function utf8entities($source)\r
+function _b_($str)\r
+{\r
+ if (!is_utf8($str)) {\r
+ $str = utf8_encode($str);\r
+ }\r
+ return dgettext('banana', $str);\r
+}\r
+\r
+if (!function_exists('is_utf8')) {\r
+ function is_utf8($s)\r
+ {\r
+ return @iconv('utf-8', 'utf-8', $s) == $s;\r
+ }\r
+}\r
+\r
+function banana_utf8entities($source)\r
{\r
// array used to figure what number to decrement from character order value \r
// according to number of characters used to map unicode to ascii by utf-8\r
* Copyright: See COPYING files that comes with this distribution
********************************************************************************/
-div.pages {
- font-size: 80%;
- text-align: center;
- padding-top: 0.1em;
- padding-bottom: 0.1em;
-}
-div.banana_action { float: right; }
-div.banana_menu { float: left; }
-
-
/** GROUP LIST **/
-
-table.banana_group { width: 100%; }
-table.banana_group td.new { text-align: center; }
-table.banana_group td.all { text-align: center; }
-table.banana_group td.grp { text-align: left; }
-table.banana_group td.dsc { text-align: left; }
+.banana .grp { text-align: left; }
+.banana .dsc { text-align: left; }
/** THREAD VIEW **/
-
-table.banana_thread { width: 100%; }
-table.banana_thread td {
+.banana table.thread td {
white-space: nowrap;
height: 20px;
vertical-align: middle;
margin-bottom: 0;
}
-table.banana_thread tr.new { font-weight: bold; }
+.banana table.thread .new { font-weight: bold; }
-table.banana_thread td.date { width: 15%; text-align: center; }
-table.banana_thread td.subj { text-align: left; overflow: hidden; }
-table.banana_thread td.from { text-align: left; }
+.banana table.thread .date { width: 15%; text-align: center; }
+.banana table.thread .subj { text-align: left; overflow: hidden; }
+.banana table.thread .from { text-align: left; }
-table.banana_thread div.tree { float: left; padding-right: 0.3em; }
-table.banana_thread span.cur { font-style: italic; }
-table.banana_thread table { border: 0px; padding: 0px; margin: 0px; width: 100%; }
-table.banana_thread td.subs { padding: 0%; }
+.banana table.thread .cur { font-style: italic; }
+.banana table.thread .subs { padding: 0%; }
/** MESSAGE VIEW **/
-
-table.banana_msg { width: 100%; }
-table.banana_msg td.headers { width: 100%; }
-table.banana_msg .hdr { width: 15%; text-align: right; font-weight: bold; padding-right: 1em; }
-table.banana_msg td.xface { text-align: right; }
-table.banana_msg th.subject { font-size: 120%; }
-table.banana_msg td.body { padding-top: 1em; padding-bottom: 0.5em; }
-
-table.banana_msg td.thrd { padding: 0px; }
-table.banana_msg table { border: 0px; padding: 0px; margin: 0px; width: 100%; }
-
-table.banana_msg blockquote {
+.banana table.message .hdr { width: 15%; text-align: right; font-weight: bold; padding-right: 1em; }
+.banana table.message .xface { text-align: right; }
+.banana table.message .subject { font-size: 120%; }
+.banana table.message .body { padding-top: 1em; padding-bottom: 0.5em; }
+.banana table.message blockquote {
color: blue;
font-style: italic;
margin-left: 0;
- padding-left: 1em;
+ padding-left: 1ex;
border-left: solid 1px;
border-color: blue;
background: inherit;
}
-table.banana_msg blockquote blockquote {
+.banana table.message blockquote blockquote {
color: green;
border-color: green;
background: inherit;
}
-table.banana_msg blockquote blockquote blockquote {
+.banana table.message blockquote blockquote blockquote {
color: #cc0000;
border-color: #cc0000;
background: inherit;
}
-table.banana_msg blockquote blockquote blockquote blockquote {
+.banana table.message blockquote blockquote blockquote blockquote {
color: #aaaa00;
border-color: #aaaa00;
background: inherit;
}
/** MISC **/
-
-div.center { text-align: center; padding: 1em; }
-p.error { color: red; background: inherit; }
-pre.error {
- color: red;
+.banana .center { text-align: center; padding: 1em; }
+.banana .error { color: red; background: inherit; }
+.banana pre.error {
margin-left: 2em;
font-size: 90%;
- background: inherit;
}
-
+.banana .pages {
+ font-size: 80%;
+ text-align: center;
+ padding-top: 0.1em;
+ padding-bottom: 0.1em;
+}
+.banana div.action { float: right; }
+.banana div.menu { float: left; }