2 /********************************************************************************
3 * banana/protocoleinterface.inc.php : interface for box access
4 * ------------------------
6 * This file is part of the banana distribution
7 * Copyright: See COPYING files that comes with this distribution
8 ********************************************************************************/
10 require_once dirname(__FILE__
) . '/banana.inc.php';
11 require_once dirname(__FILE__
) . '/protocoleinterface.inc.php';
12 require_once dirname(__FILE__
) . '/message.inc.php';
14 class BananaMBox
implements BananaProtocoleInterface
16 private $inbox = null
;
18 private $filesize = null
;
19 private $current_id = null
;
20 private $at_beginning = false
;
21 private $file_cache = null
;
23 private $_lasterrno = 0;
24 private $_lasterror = null
;
26 private $count = null
;
27 private $new_messages = null
;
28 private $messages = null
;
30 /** Build a protocole handler plugged on the given box
32 public function __construct()
39 public function __destruct()
44 /** Indicate if the Protocole handler has been succesfully built
46 public function isValid()
49 //!Banana::$group || $this->file;
52 /** Indicate last error n°
54 public function lastErrNo()
56 return $this->_lasterrno
;;
59 /** Indicate last error text
61 public function lastError()
63 return $this->_lasterror
;
66 /** Return the description of the current box
68 public function getDescription()
73 /** Return the list of the boxes
74 * @param mode Kind of boxes to list
75 * @param since date of last check (for new boxes and new messages)
76 * @param withstats Indicated whether msgnum and unread must be set in the result
77 * @return Array(boxname => array(desc => boxdescripton, msgnum => number of message, unread =>number of unread messages)
79 public function getBoxList($mode = Banana
::BOXES_ALL
, $since = 0, $withstats = false
)
81 return array(Banana
::$group => array('desc' => '', 'msgnum' => 0, 'unread' => 0));
85 * @param id Id of the emssage (can be either an Message-id or a message index)
86 * @return A BananaMessage or null if the given id can't be retreived
88 public function &getMessage($id)
92 if (is_null($this->file
)) {
95 if (!is_numeric($id)) {
96 if (!Banana
::$spool) {
99 $id = Banana
::$spool->ids
[$id];
101 $messages = $this->readMessages(array($id));
102 if (!empty($messages)) {
103 $message = new BananaMessage($messages[$id]['message']);
108 /** Return the sources of the given message
110 public function getMessageSource($id)
114 if (is_null($this->file
)) {
117 if (!is_numeric($id)) {
118 if (!Banana
::$spool) {
121 $id = Banana
::$spool->ids
[$id];
123 $message = $this->readMessages(array($id));
124 return implode("\n", $message[$id]['message']);
127 /** Compute the number of messages of the box
129 private function getCount()
132 $this->count
= count(Banana
::$spool->overview
);
133 $max = @max
(array_keys(Banana
::$spool->overview
));
134 if ($max && Banana
::$spool->overview
[$max]->storage
['next'] == $this->filesize
) {
135 $this->new_messages
= 0;
137 $this->new_messages
= $this->countMessages($this->count
);
138 $this->count +
= $this->new_messages
;
142 /** Return the indexes of the messages presents in the Box
143 * @return Array(number of messages, MSGNUM of the first message, MSGNUM of the last message)
145 public function getIndexes()
148 if (is_null($this->file
)) {
149 return array(0, 0, 0);
151 if (is_null($this->count
)) {
154 return array($this->count
, 0, $this->count
- 1);
157 /** Return the message headers (in BananaMessage) for messages from firstid to lastid
158 * @return Array(id => array(headername => headervalue))
160 public function &getMessageHeaders($firstid, $lastid, array $msg_headers = array())
163 $msg_headers = array_map('strtolower', $msg_headers);
164 $messages =& $this->readMessages(range($firstid, $lastid), true
);
165 $msg_headers = array_map('strtolower', $msg_headers);
167 if (is_null($this->file
)) {
170 foreach ($msg_headers as $header) {
171 foreach ($messages as $id=>&$message) {
172 if (!isset($headers[$id])) {
173 $headers[$id] = array('beginning' => $message['beginning'], 'end' => $message['end']);
175 if ($header == 'date') {
176 $headers[$id][$header] = @strtotime
($message['message'][$header]);
178 $headers[$id][$header] = @$message['message'][$header];
182 unset($this->messages
);
187 /** Add storage data in spool overview
189 public function updateSpool(array &$messages)
191 foreach ($messages as $id=>&$data) {
192 if (isset(Banana
::$spool->overview
[$id])) {
193 Banana
::$spool->overview
[$id]->storage
['offset'] = $data['beginning'];
194 Banana
::$spool->overview
[$id]->storage
['next'] = $data['end'];
199 /** Return the indexes of the new messages since the give date
200 * @return Array(MSGNUM of new messages)
202 public function getNewIndexes($since)
205 if (is_null($this->file
)) {
208 if (is_null($this->new_messages
)) {
211 return range($this->count
- $this->new_messages
, $this->count
- 1);
214 /** Return wether or not the protocole can be used to add new messages
216 public function canSend()
221 /** Return false because we can't cancel a mail
223 public function canCancel()
228 /** Return the list of requested headers
229 * @return Array('header1', 'header2', ...) with the key 'dest' for the destination header
230 * and 'reply' for the reply header, eg:
231 * * for a mail: Array('From', 'Subject', 'dest' => 'To', 'Cc', 'Bcc', 'reply' => 'Reply-To')
232 * * for a post: Array('From', 'Subject', 'dest' => 'Newsgroups', 'reply' => 'Followup-To')
234 public function requestedHeaders()
236 return Array('From', 'Subject', 'dest' => 'To', 'Cc', 'Bcc', 'reply' => 'Reply-To');
240 * @return true if it was successfull
242 public function send(BananaMessage
&$message)
244 $headers = $message->getHeaders();
245 $to = $headers['To'];
246 $subject = $headers['Subject'];
247 unset($headers['To']);
248 unset($headers['Subject']);
250 foreach ($headers as $key=>$value) {
251 if (!empty($value)) {
252 $hdrs .= "$key: $value\r\n";
255 $body = $message->get(false
);
256 return mail($to, $subject, $body, $hdrs);
260 * @return true if it was successfull
262 public function cancel(BananaMessage
&$message)
267 /** Return the protocole name
269 public function name()
274 /** Return the spool filename
276 public function filename()
278 @list
($mail, $domain) = explode('@', Banana
::$group);
280 if (isset($domain)) {
281 $file = $domain . '_';
283 return $file . $mail;
287 # Filesystem functions
290 protected function getFileName()
292 if (is_null(Banana
::$group)) {
295 @list
($mail, $domain) = explode('@', Banana
::$group);
296 return Banana
::$mbox_path . '/' . $mail;
303 private function open()
305 if ($this->inbox
== Banana
::$group) {
308 $filename = $this->getFileName();
309 if (is_null($filename)) {
312 $this->file
= @fopen
($filename, 'r');
317 $this->filesize
= filesize($filename);
319 $this->current_id
= 0;
320 $this->at_beginning
= true
;
321 $this->inbox
= Banana
::$group;
324 private function close()
326 if (is_null($this->file
)) {
332 $this->filesize
= null
;
333 $this->current_id
= null
;
334 $this->at_beginning
= false
;
335 $this->file_cache
= null
;
337 $this->new_messages
= null
;
338 $this->messages
= null
;
341 /** Go to the given message
343 private function goTo($id)
345 if ($this->current_id
== $id && $this->at_beginning
) {
349 fseek($this->file
, 0);
350 $this->current_id
= 0;
351 $this->at_beginning
= true
;
353 } elseif (isset(Banana
::$spool->overview
[$id]) ||
isset($this->messages
[$id])) {
354 if (isset(Banana
::$spool->overview
[$id])) {
355 $pos = Banana
::$spool->overview
[$id]->storage
['offset'];
357 $pos = $this->messages
[$id]['beginning'];
359 if (fseek($this->file
, $pos) == 0) {
360 $this->current_id
= $id;
361 $this->at_beginning
= true
;
364 $this->current_id
= null
;
365 $this->_lasterrno
= 2;
366 $this->_lasterror
= _b_('Can\'t find message ') . $id;
370 $max = @max
(array_keys(Banana
::$spool->overview
));
374 if ($id <= $max && $max != 0) {
375 $this->current_id
= null
;
376 $this->_lasterrno
= 3;
377 $this->_lasterror
= _b_('Invalid message index ') . $id;
380 if (!$this->goTo($max)) {
383 if (feof($this->file
)) {
384 $this->current_id
= null
;
385 $this->_lasterrno
= 4;
386 $this->_lasterror
= _b_('Requested index does not exists or file has been truncated');
389 while ($this->readCurrentMessage(true
) && $this->current_id
< $id);
390 if ($this->current_id
== $id) {
393 $this->current_id
= null
;
394 $this->_lasterrno
= 5;
395 $this->_lasterror
= _b_('Requested index does not exists or file has been truncated');
400 private function countMessages($from = 0)
402 $this->messages
=& $this->readMessages(array($from), true
, true
);
403 return count($this->messages
);
406 /** Read the current message (identified by current_id)
407 * @param needFrom_ BOOLEAN is true if the first line *must* be a From_ line
408 * @param alignNext BOOLEAN is true if the buffer must be aligned at the beginning of the next From_ line
409 * @return message sources (without storage data)
411 private function &readCurrentMessage($stripBody = false
, $needFrom_ = true
, $alignNext = true
)
413 $file_cache =& $this->file_cache
;
414 if ($file_cache && $file_cache != ftell($this->file
)) {
420 while(!feof($this->file
)) {
421 // Process file cache
422 if ($file_cache) { // this is a From_ line
424 $this->at_beginning
= false
;
430 $line = rtrim(fgets($this->file
), "\r\n");
432 // Process From_ line
433 if ($needFrom_ ||
!$msg ||
$canFrom_) {
434 if (substr($line, 0, 5) == 'From ') { // this is a From_ line
440 $this->current_id++
; // we are finally in the next message
441 if ($alignNext) { // align the file pointer at the beginning of the new message
442 $this->at_beginning
= true
;
443 $file_cache = ftell($this->file
);
447 } elseif ($needFrom_) {
452 // Process non-From_ lines
453 if (substr($line, 0, 6) == '>From ') { // remove inline From_ quotation
454 $line = substr($line, 1);
456 if (!$stripBody ||
!$inBody) {
457 $msg[] = $line; // add the line to the message source
459 $canFrom_ = empty($line); // check if next line can be a From_ line
460 if ($canFrom_ && !$inBody && $stripBody) {
463 $this->at_beginning
= false
;
465 if (!feof($this->file
) && !$canFrom_) {
471 /** Read message with the given ids
472 * @param ids ARRAY of ids to look for
473 * @param strip BOOLEAN if true, only headers are retrieved
474 * @param from BOOLEAN if true, process all messages from max(ids) to the end of the mbox
475 * @return Array(Array('message' => message sources (or parsed message headers if $strip is true),
476 * 'beginning' => offset of message beginning,
477 * 'end' => offset of message end))
479 private function &readMessages(array $ids, $strip = false
, $from = false
)
481 if ($this->messages
) {
482 return $this->messages
;
486 while ((count($ids) ||
$from) && !feof($this->file
)) {
488 $id = array_shift($ids);
492 if ($id != $this->current_id ||
!$this->at_beginning
) {
493 if (!$this->goTo($id)) {
501 $beginning = ftell($this->file
);
502 $message =& $this->readCurrentMessage($strip, false
);
504 $message =& BananaMimePart
::parseHeaders($message);
506 $end = ftell($this->file
);
507 $messages[$id] = array('message' => $message, 'beginning' => $beginning, 'end' => $end);
513 // vim:set et sw=4 sts=4 ts=4 enc=utf-8: