From: x2003bruneau Date: Tue, 9 Jan 2007 13:55:03 +0000 (+0000) Subject: Rework all sources : X-Git-Tag: 1.8~152 X-Git-Url: http://git.polytechnique.org/?a=commitdiff_plain;h=7027794fb616f65d8910305c9fed9037a751b875;p=banana.git Rework all sources : * add protocole abstraction and a mbox parser * better message parser * use Smarty for html generation * remove php4 compatibility git-svn-id: svn+ssh://murphy/home/svn/banana/trunk@147 9869982d-c50d-0410-be91-f2a2ec7c7c7b --- diff --git a/banana/NetNNTP.inc.php b/banana/NetNNTP.inc.php deleted file mode 100644 index 3934cd5..0000000 --- a/banana/NetNNTP.inc.php +++ /dev/null @@ -1,512 +0,0 @@ -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 -?> diff --git a/banana/banana.inc.php.in b/banana/banana.inc.php.in index 86df250..fadd3a7 100644 --- a/banana/banana.inc.php.in +++ b/banana/banana.inc.php.in @@ -1,6 +1,6 @@ '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 ', '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 ', '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('

'._b_('Impossible de contacter le serveur').'

'); - } + // 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('

' - . $group . _b_(' : ce newsgroup n\'existe pas ou vous n\'avez pas l\'autorisation d\'y accéder') - . '

'); + // 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 = '

'._b_('Voulez-vous vraiment annuler ce message ?').'

' - . '

' - . '' - . '' - . '

'; - 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 .= '

'._b_('Les forums suivants ont été créés depuis ton dernier passage :').'

'; - $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') . '
' + . 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 '

'._b_('Impossible charger la liste des messages de ') . $group . '

'; - } - - 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 '

'._b_('Impossible charger la liste des messages de ') . $group . '

'; + 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 '

'._b_('Impossible d\'accéder au message. Le message a peut-être été annulé').'

'; - } - - $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 '

'._b_('Impossible charger la liste des messages').'

'; - } - - if (!$this->_newPost($id)) { - if ($this->nntp->lasterrorcode == "423") { - $this->spool->delid($id); - } - $this->nntp->quit(); - return '

'._b_('Impossible d\'accéder au message. Le message a peut-être été annulé').'

'; - } - - $this->nntp->quit(); - if ($this->post->get_attachment($pjid, $action)) { - return ""; - } else { - return '

'._b_('Impossible d\'accéder à la pièce jointe.').'

'; - } + 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 '

'._b_('Impossible charger la liste des messages').'

'; - } - - if (!$this->_newPost($id)) { - return '

'._b_('Impossible de trouver le message à annuler').'

'; - } - $mid = array_search($id, $this->spool->ids); - - if (!$this->post->checkcancel()) { - return '

'._b_('Vous n\'avez pas les permissions pour annuler ce message').'

'; - } - $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 '

'._b_('Impossible d\'annuler le message').'

'; + 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 = '
' - . '' - . '' - . '' - . '' - . '' - . '' - . '' - . '' - . '' - . '' - . '' - . '' - . '' - . ''; - if ($this->can_attach) { - $html .= '' - . ''; - } - $html .= '' - . '
' . _b_('En-têtes') . '
' . _b_('Nom') . '' . htmlentities($this->profile['name']) . '
' . _b_('Sujet') . '
' . _b_('Forums') . '
' . _b_('Suivi à') . '
' . _b_('Organisation') . '' . $this->profile['org'] . '
' . _b_('Corps') . '
' . _b_('Pièce jointe') . '
' - . '' - . '
'; - if ($id != -1) { - $html .= ''; + exit; } - $html .= '' - . '
'; - - 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 '

'._b_('Impossible charger la liste des messages').'

'; - } - - $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 '

'._b_('Impossible charger le message d\'origine').'

'; + 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 '

'._b_('Fichier trop gros pour être envoyé : ') - .$uploaded['name'].'

'.$this->action_showThread($group, $artid); - - case UPLOAD_ERR_PARTIAL: - return '

'._b_('Erreur lors de l\'upload de ') - .$uploaded['name'].'

'.$this->action_showThread($group, $artid); - - case UPLOAD_ERR_NO_FILE: - return '

'._b_('Le fichier spécifié n\'existe pas : ') - .$uploaded['name'].'

'.$this->action_showThread($group, $artid); - - case UPLOAD_ERR_NO_TMP_DIR: - return '

'._b_('Une erreur est survenue sur le serveur lors de l\'upload de ') - .$uploaded['name'].'

'.$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 :') . '
' + . Banana::$protocole->lastError()); } - redirectInBanana($dir); } else { - return '

' . _b_('Impossible de poster le message. Le serveur a retourné l\'erreur :') . '

' - . '
' . utf8_encode($this->nntp->lasterrortext) .'
' - . $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 :') . '
' + . 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; + } } } diff --git a/banana/groups.inc.php b/banana/groups.inc.php deleted file mode 100644 index e7cd952..0000000 --- a/banana/groups.inc.php +++ /dev/null @@ -1,137 +0,0 @@ -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 = ''."\n"; - $html .= ''."\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 .= ''."\n"; - $html .= ""; - if ($show_form) { - $html .= ''; - } elseif ($this->type == BANANA_GROUP_SUB) { - $html .= ''; - } - $html .= ''; - } - - $html .= '
'._b_('Total').''; - if ($show_form) { - $html .= _b_('Abo.').''; - } elseif ($this->type == BANANA_GROUP_SUB) { - $html .= _b_('Nouveaux').''; - } - $html .= _b_('Nom').''._b_('Description').'
{$ginfo[0]}profile['subscribe'])) { - $html .= ' checked="checked"'; - } - $html .= ' />'.($new ? $new : '-').'' . makeHREF(Array('group' => $g), $g) . '' . $d[0] . '
'; - - if ($show_form) { - return '
' - . '
' - . $html . '
'; - } - - return $html; - } -} - -// vim:set et sw=4 sts=4 ts=4 -?> diff --git a/banana/mbox.inc.php b/banana/mbox.inc.php new file mode 100644 index 0000000..c36bb5c --- /dev/null +++ b/banana/mbox.inc.php @@ -0,0 +1,431 @@ +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: +?> diff --git a/banana/message.func.inc.php b/banana/message.func.inc.php new file mode 100644 index 0000000..04ad7cb --- /dev/null +++ b/banana/message.func.inc.php @@ -0,0 +1,415 @@ +') { + $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", $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 '' . banana__cutlink($url) . ''; +} + +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 '' . $email . ''; +} + +// }}} + +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("@(^|
|\n)$regexp@i", '\1', $text));
+}
+
+// }}}
+
+function banana_catchQuotes($res, $strict = true)
+{
+    if ($strict) {
+        $regexp = ">";
+    } else {
+        $regexp = "> *";
+    }
+    while (preg_match("/(^|
|\n)$regexp/i", $res)) {
+        $res  = preg_replace("/(^|
|\n)(($regexp.*(?:\n|$))+)/ie",
+            "'\\1
'"
+            ." . banana__replaceQuotes('\\2', '$regexp')"
+            ." . '
'",
+            $res);
+    }
+    return $res;
+}
+
+function banana_catchSignature($res)
+{
+    $res = preg_replace("@
-- ?\n@", "
\n-- \n", $res);
+    $parts = preg_split("/\n-- ?\n/", $res);
+    $sign  = '

';
+    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('
' . $text . '
'); +} + +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 = '



  • ' + . '

    \n"; - $res .= "\n"; - $res .= "\n"; + $res .= '\n"; + $res .= '\n\n"; + $res .= ' ' . $subject . $link; + $res .= "\n\n"; - 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; @@ -392,64 +449,38 @@ class BananaSpool * @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 = '
    '; + $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("@(

    )\n?-- ?\n?(]*>|]*>)@", "\\1
    -- \\2", $res); + $res = preg_replace("@]*>\n?-- ?\n?(]*>)@", "
    --
    \\2", $res); + $res = preg_replace("@(]*>)\n?-- ?\n@", "
    --
    \\1", $res); + $parts = preg_split("@(:?]*>\n?-- ?\n?

    |]*>\n?-- ?\n?]*>)@", $res); + $sign = '
    '; + 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("/]*?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("!!is", $format, $res); + } + return $res; +} + +function banana__convertQuotes($res) +{ + return preg_replace('!([^<]*)!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, '

    ')); + $res = preg_replace("@@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 = '<'.join('><', array_keys($tags)).'>'; + $source = strip_tags($source, $protectedTags); + + // convert richtext tags to html + foreach (array_keys($tags) as $tag) { + $source = preg_replace('@(]*>)@i', '\1'.$tags[$tag].'\2', $source); + } + + // some special cases + $source = preg_replace('@@i', '
    --
    ', $source); + $source = preg_replace('@
    @i', '', $source); + $source = preg_replace('@@i', '<', $source); + $source = preg_replace('@]*>((?:[^<]|<(?!/comment>))*)
    @i', '', $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: +?> diff --git a/banana/message.inc.php b/banana/message.inc.php new file mode 100644 index 0000000..c83d17b --- /dev/null +++ b/banana/message.inc.php @@ -0,0 +1,285 @@ +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: +# From: mark@cbosgd.ATT.COM (Mark Horton) +# From: Mark Horton + $mailto = '' . banana_htmlentities($regs[1]) . ''; + } + if (preg_match("/^<(.+@.+)>$/", $text, $regs)) { + $result = $mailto . $regs[1] . '">' . banana_htmlentities($regs[1]) . ''; + } + if (preg_match("/^([^ ]+@[^ ]+) \((.*)\)$/", $text, $regs)) { + $result = $mailto . $regs[1] . '">' . banana_htmlentities($regs[2]) . ''; + } + if (preg_match("/^\"?([^<>\"]+)\"? +<(.+@.+)>$/", $text, $regs)) { + $nom = preg_replace("/^'(.*)'$/", '\1', $regs[1]); + $nom = stripslashes($nom); + $result = $mailto . $regs[2] . '">' . banana_htmlentities($nom) . ''; + } + 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: +?> diff --git a/banana/mimepart.inc.php b/banana/mimepart.inc.php new file mode 100644 index 0000000..c486add --- /dev/null +++ b/banana/mimepart.inc.php @@ -0,0 +1,514 @@ +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: +?> diff --git a/banana/misc.inc.php b/banana/misc.inc.php deleted file mode 100644 index 48c8081..0000000 --- a/banana/misc.inc.php +++ /dev/null @@ -1,670 +0,0 @@ - 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 '' . $text . ''; -} - -/** 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 '' . $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 = '



    • '; - $source = preg_replace('|

    |i', '
    ', $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, '

    '))); - $res = preg_replace("@]*>@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\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 = '<'.join('><', array_keys($tags)).'>'; - $source = strip_tags($source, $protectedTags); - - // convert richtext tags to html - foreach (array_keys($tags) as $tag) { - $source = preg_replace('@(]*>)@i', '\1'.$tags[$tag].'\2', $source); - } - - // some special cases - $source = preg_replace('@@i', '
    --
    ', $source); - $source = preg_replace('@
    @i', '', $source); - $source = preg_replace('@@i', '<', $source); - $source = preg_replace('@]*>((?:[^<]|<(?!/comment>))*)
    @i', '', $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 '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 - $mailto = '
    ".htmlentities($regs[1]."@".$regs[2]).""; - } - if (preg_match("/^([^ ]+)@([^ ]+) \((.*)\)$/",$text,$regs)) { - $result="$mailto{$regs[1]}@{$regs[2]}\">".htmlentities($regs[3]).""; - } - if (preg_match("/^\"?([^<>\"]+)\"? +<(.+)@(.+)>$/",$text,$regs)) { - $result="$mailto{$regs[2]}@{$regs[3]}\">".htmlentities($regs[1]).""; - } - 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 = '

      '; - foreach ($res as $name=>$onglet) { - if ($name != @$page) { - $ret .= '
    • ' . $onglet[0] . '
    • '; - } else { - $ret .= '
    • ' . $onglet[1] . '
    • '; - } - } - $ret .= '
    '; - 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 .= '
    '; - $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 .= '' . $i . ' '; - } else { - $res .= makeHREF(Array('group' => $group, - 'first' => $ndx), - $i, - $ndx . '-' . min($ndx+$banana->tmax-1,sizeof($banana->spool->overview))) - . ' '; - } - $i++; - } - $res .= '
    '; - } - return $res; -} - -function makeTable($text) -{ - $links = null; - if (function_exists('hook_browsingAction')) { - $links = hook_browsingAction(); - } - - return '' - . '' - . '' - . '
    ' - . displayTabs() - . '
    ' - . $links - . $text - . '
    '; -} - -/******************************************************************************** - * 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 ''.cutlink($url).''; -} - -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 '' . $email . ''; -} - -/** Remove quotation marks - */ -function replaceQuotes($text) -{ - return stripslashes(preg_replace("@(^|
    |\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("@(

    )\n?-- ?\n?(]*>|]*>)@", "\\1
    -- \\2", $res); - $res = preg_replace("@]*>\n?-- ?\n?(]*>)@", "
    --
    \\2", $res); - $res = preg_replace("@(]*>)\n?-- ?\n@", "
    --
    \\1", $res); - $parts = preg_split("@(:?]*>\n?-- ?\n?

    |]*>\n?-- ?\n?]*>)@", $res); - $sign = '
    '; - } else { - while (preg_match("@(^|
    |\n)>@i", $res)) {
    -            $res  = preg_replace("@(^|
    |\n)((>[^\n]*(?:\n|$))+)@ie",
    -                "'\\1
    '"
    -                ." . replaceQuotes('\\2')"
    -                ." . '
    '",
    -                $res);
    -        }
    -        $res = preg_replace("@
    -- ?\n@", "
    \n-- \n", $res);
    -        $parts = preg_split("/\n-- ?\n/", $res);
    -        $sign  = '

    ';
    -    }
    -
    -    return join($sign, $parts);
    -}
    -
    -// vim:set et sw=4 sts=4 ts=4
    -?>
    diff --git a/banana/nntp.inc.php b/banana/nntp.inc.php
    new file mode 100644
    index 0000000..f1dde9d
    --- /dev/null
    +++ b/banana/nntp.inc.php
    @@ -0,0 +1,247 @@
    +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 '
    +        
    +        
    +
    '; +//echo $msg->getFormattedBody('plain'); +echo $msg->getFormattedBody(); +echo '
    ', "\n"; +$end = microtime(true); +echo ($end - $time) . "s\n"; +*/ + +// vim:set et sw=4 sts=4 ts=4: +?> diff --git a/banana/nntpcore.inc.php b/banana/nntpcore.inc.php new file mode 100644 index 0000000..1ab299e --- /dev/null +++ b/banana/nntpcore.inc.php @@ -0,0 +1,511 @@ +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 +?> diff --git a/banana/page.inc.php b/banana/page.inc.php new file mode 100644 index 0000000..b897146 --- /dev/null +++ b/banana/page.inc.php @@ -0,0 +1,236 @@ +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 '' . $text . ''; + } + + 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 '' . _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}[^>]+>.*?!is", $source, ${$tag}); + $source = preg_replace("!<{$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: +?> diff --git a/banana/post.inc.php b/banana/post.inc.php deleted file mode 100644 index c3e3402..0000000 --- a/banana/post.inc.php +++ /dev/null @@ -1,536 +0,0 @@ -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 = '
    '; - $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 . '
    '; - } - - /** 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 = ''; - $res .= '' - . ''; - - if (count($this->messages) > 1) { - $res .= ''; - } - - if (isset($this->headers['content-type']) - && preg_match("@text/([^;]+);@", $this->headers['content-type'], $format)) { - $format = $format[1]; - } else { - $format = 'plain'; - } - $res .= ''; - - if (count($this->pj) > 0) { - $res .= ''; - $res .= ''; - } - - $ndx = $banana->spool->getndx($this->id); - $res .= ''; - return $res.'
    ' - . $this->_browser() - . '
    ' - . 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 .= '
    ' - . formatDisplayHeader('subject', $this->headers['subject']) - . '
    '; - - $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 .= '\n"; - } else if ($res2) { - $xface = $res2; - } - } - } - $res .= '
    '.header_translate($hdr)."$res2
    '; - - if ($xface) { - $res .= $xface; - } - $res .= '
    '; - 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 .= '
    ]*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 .= '>
    '.formatbody($this->body).'
    '; - } - $res .= '
    '._b_('Pièces jointes').'
    '; - $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 .= '
    '; - $i++; - } - $res .= '
    ' - . $banana->spool->to_html($ndx-$banana->tbefore, $ndx+$banana->tafter, $ndx) - . '
    '; - } -} - -/** 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 -?> diff --git a/banana/protocoleinterface.inc.php b/banana/protocoleinterface.inc.php new file mode 100644 index 0000000..5962e5a --- /dev/null +++ b/banana/protocoleinterface.inc.php @@ -0,0 +1,102 @@ + 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: +?> diff --git a/banana/spool.inc.php b/banana/spool.inc.php index de4e598..610d192 100644 --- a/banana/spool.inc.php +++ b/banana/spool.inc.php @@ -7,20 +7,9 @@ * 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 @@ -28,23 +17,26 @@ function spoolCompare($a,$b) { return ($b->date>=$a->date); } 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 @@ -54,102 +46,99 @@ class BananaSpoolHead * @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) { @@ -161,95 +150,144 @@ class BananaSpool 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) { @@ -265,8 +303,7 @@ class BananaSpool /** 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)) { @@ -296,10 +333,29 @@ class BananaSpool 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 @@ -314,74 +370,75 @@ class BananaSpool * 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 .= '
    ".fancyDate($this->overview[$_id]->date)." " - ."
    $_pfx_node".($hc?($_head?$spfx_f:($this->overview[$_id]->parent_direct?$spfx_s:$spfx_snd)):$spfx_n) - ."
    "; - $subject = $this->overview[$_id]->subject; - if (strlen($subject) == 0) { + $res = ''; + if ($_index >= $_first) { + $hc = empty($overview->children); + + $res .= '
    ' . $this->formatDate($overview->date) . " ' + . $_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 .= '' . $subject . $link . ''; - } 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 .= "".formatFrom($this->overview[$_id]->from)."
    " . BananaMessage::formatFrom($overview->from) . "
    '; - - if (is_null($_ref)) { - $next = $this->nextUnread(); - if (!is_null($next)) { - $next = '
    ' - . makeImgLink(Array('group' => $this->group, - 'artid' => $next), - 'next_unread.gif', - _b_('Message non-lu suivant'), null, null, null, 'u') - . '
    '; - } - $new = '
    ' - . makeImgLink(Array('group' => $this->group, - 'action' => 'new'), - 'post.gif', - _b_('Nouveau message'), null, null, null, 'p') - . '
    '; - - $res .= ''; - $res .= ''; - $res .= ''; + $res = ''; + + if (!$overview) { + $_first = $first; + $_last = $first + Banana::$tmax - 1; + $_ref = null; } else { - $res .= ''; + $_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 .= ''; - } - - global $banana; - if (is_object($banana->groups)) { - $res .= ''; } - return $res .= '
    ' . $next . _b_('Date') . '' . _b_('Sujet') . '' . $new . _b_('Auteur') . '
    ' . _b_('Aperçu de ') - . makeHREF(Array('group' => $this->group), - $this->group) - . '
    '._b_('Aucun message dans ce forum').'
    ' - . $banana->groups->to_html() - . '
    '; + 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; @@ -478,8 +509,8 @@ class BananaSpool /** 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; @@ -492,7 +523,7 @@ class BananaSpool /** Returns previous thread root index * @param id INTEGER message number */ - function prevThread($id) + public function prevThread($id) { $root = $this->root($id); $last = null; @@ -508,7 +539,7 @@ class BananaSpool /** Returns next thread root index * @param id INTEGER message number */ - function nextThread($id) + public function nextThread($id) { $root = $this->root($id); $ok = false; @@ -526,7 +557,7 @@ class BananaSpool /** 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)) { @@ -545,7 +576,7 @@ class BananaSpool /** 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]; @@ -574,7 +605,7 @@ class BananaSpool /** 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; @@ -591,7 +622,7 @@ class BananaSpool /** 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 diff --git a/banana/templates/banana-base.tpl b/banana/templates/banana-base.tpl new file mode 100644 index 0000000..3db7416 --- /dev/null +++ b/banana/templates/banana-base.tpl @@ -0,0 +1,63 @@ +{* *} +{* *} +{* *} + + + + + + + + +
    +
      + {foreach from=$pages item=pg key=name} + {if $name eq $page} +
    • {$pg.text}
    • + {assign var=current_page value=$pg} + {else} +
    • {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}
    • + {/if} + {/foreach} +
    +
    + {foreach from=$errors item=error} +

    {$error}

    + {/foreach} + {foreach from=$actions item=act} +

    {$act.text}

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

    Les nouveaux groupes suivants ont été créés depuis votre dernière visite

    + {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'} +

    Voulez-vous vraiment annuler ce message ?

    +
    +

    + +

    +
    + {include file="banana-message.inc.tpl" noactions=true} + {elseif $current_page.template} + {include file=$current_page.template} + {/if} +
    + +{* vim:set et sw=2 sts=2 ts=2: *} diff --git a/banana/templates/banana-boxlist.inc.tpl b/banana/templates/banana-boxlist.inc.tpl new file mode 100644 index 0000000..0331838 --- /dev/null +++ b/banana/templates/banana-boxlist.inc.tpl @@ -0,0 +1,44 @@ +{if $groups|@count} +{if $withsubs} +

    +

    + +

    +{/if} + + + {if $withsubs} + + {/if} + {if $withstats} + + + {/if} + + + + {foreach from=$groups key=name item=grp} + + {if $withsubs} + + {/if} + {if $withstats} + + + {/if} + + + + {/foreach} +
    TotalNouveauxNomDescription
    + + {if $grp.msgnum eq 0}-{else}{$grp.msgnum}{/if}{if $grp.unread eq 0}-{else}{$grp.unread}{/if}{link group=$name text=$name}{$grp.desc}
    +{if $withsubs} +

    + +

    + +{/if} +{/if} + +{* vim:set et sw=2 sts=2 ts=2: *} diff --git a/banana/templates/banana-message.inc.tpl b/banana/templates/banana-message.inc.tpl new file mode 100644 index 0000000..5dbc3f4 --- /dev/null +++ b/banana/templates/banana-message.inc.tpl @@ -0,0 +1,62 @@ + + + + + {foreach from=$headers name=headers item=hdr} + + + + {if $smarty.foreach.headers.first} + + {/if} + + {/foreach} + {assign var=files value=$message->getAttachments()} + {if $files|@count} + + + + + {/if} + + + +
    + {if !$noactions} + +
    + {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} +
    + {/if} + {$message->translateHeaderValue('subject')} +
    {$message->translateHeaderName($hdr)}{$message->translateHeaderValue($hdr)} + {if $message->hasXFace()} + [ X-Face ] + {/if} +
    Fichiers joints + {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} +
    + {$message->getFormattedBody()} +
    + +{* vim:set et sw=2 sts=2 ts=2: *} diff --git a/banana/templates/banana-newmessage.inc.tpl b/banana/templates/banana-newmessage.inc.tpl new file mode 100644 index 0000000..dd15111 --- /dev/null +++ b/banana/templates/banana-newmessage.inc.tpl @@ -0,0 +1,42 @@ +
    + + + + + {foreach from=$headers key=header item=values} + + + + + {/foreach} + + + + {if $can_attach} + + + + + {/if} + + + +
    Composer un nouveau message
    {$values.name} + {if $values.fixed} + {$values.fixed|htmlentities} + {else} + + {/if} +
    + +
    Fichier joint + {if $maxfilesize} + + {/if} + +
    + +
    +
    + +{* vim:set et sts=2 ts=2 sw=2: *} diff --git a/banana/templates/banana-thread.inc.tpl b/banana/templates/banana-thread.inc.tpl new file mode 100644 index 0000000..60630fa --- /dev/null +++ b/banana/templates/banana-thread.inc.tpl @@ -0,0 +1,65 @@ +{if $withtitle} +
    +{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} + {$smarty.section.pages.iteration} + {else} + {link group=$group first=$smarty.section.pages.index text=$smarty.section.pages.iteration} + {/if} +{/section} +{/if} +
    +{/if} + + + {if $withtitle} + + + + {else} + + {/if} + + {if $spool->overview|@count} + {if $artid}{$spool->toHtml($artid, true)}{else}{$spool->toHtml($first)}{/if} + {else} + + + + {/if} +
    + {if $spool->nextUnread()} + + {/if} + Date + Sujet + {if $protocole->canSend()} +
    + {imglink group=$group action=new img=post alt="Nouveau message" accesskey=p} +
    + {/if} + Auteur +
    + {link group=$group text=$group} +
    + Aucun message dans ce forum +
    +{include file="banana-boxlist.inc.tpl" grouplist=$groups withstats=true} +{if $withtitle} +
    +{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} + {$smarty.section.pages.iteration} + {else} + {link group=$group first=$smarty.section.pages.index text=$smarty.section.pages.iteration} + {/if} +{/section} +{/if} +
    +{/if} + +{* vim:set et sw=2 sts=2 ts=2: *} diff --git a/banana/utf8.php b/banana/text.func.inc.php similarity index 85% rename from banana/utf8.php rename to banana/text.func.inc.php index 586bc31..00d0385 100644 --- a/banana/utf8.php +++ b/banana/text.func.inc.php @@ -1,13 +1,28 @@