Post messages with format=flowed instead of format=fixed
[banana.git] / banana / mbox.inc.php
1 <?php
2 /********************************************************************************
3 * banana/protocoleinterface.inc.php : interface for box access
4 * ------------------------
5 *
6 * This file is part of the banana distribution
7 * Copyright: See COPYING files that comes with this distribution
8 ********************************************************************************/
9
10 require_once dirname(__FILE__) . '/banana.inc.php';
11 require_once dirname(__FILE__) . '/protocoleinterface.inc.php';
12 require_once dirname(__FILE__) . '/message.inc.php';
13
14 class BananaMBox implements BananaProtocoleInterface
15 {
16 private $debug = false;
17 private $bt = array();
18
19 private $_lasterrno = 0;
20 private $_lasterror = null;
21
22 public function __construct()
23 {
24 $this->debug = Banana::$debug_mbox;
25 }
26
27 public function isValid()
28 {
29 return true;
30 //!Banana::$group || $this->file;
31 }
32
33 /** Indicate last error n°
34 */
35 public function lastErrNo()
36 {
37 return $this->_lasterrno;;
38 }
39
40 /** Indicate last error text
41 */
42 public function lastError()
43 {
44 return $this->_lasterror;
45 }
46
47 /** Return the description of the current box
48 */
49 public function getDescription()
50 {
51 return null;
52 }
53
54 /** Return the list of the boxes
55 * @param mode Kind of boxes to list
56 * @param since date of last check (for new boxes and new messages)
57 * @param withstats Indicated whether msgnum and unread must be set in the result
58 * @return Array(boxname => array(desc => boxdescripton, msgnum => number of message, unread =>number of unread messages)
59 */
60 public function getBoxList($mode = Banana::BOXES_ALL, $since = 0, $withstats = false)
61 {
62 return array(Banana::$group => array('desc' => '', 'msgnum' => 0, 'unread' => 0));
63 }
64
65 private function &getRawMessage($id)
66 {
67 $message = null;
68 if (!is_numeric($id)) {
69 if (!Banana::$spool) {
70 return $message;
71 }
72 $id = Banana::$spool->ids[$id];
73 }
74 $options = array ('-m ' . $id);
75 $this->getMBoxPosition($options, $id);
76 return $this->callHelper('-b', $options);
77 }
78
79 /** Return a message
80 * @param id Id of the emssage (can be either an Message-id or a message index)
81 * @return A BananaMessage or null if the given id can't be retreived
82 */
83 public function &getMessage($id)
84 {
85 $messages =& $this->getRawMessage($id);
86 if ($messages) {
87 $messages = new BananaMessage($messages);
88 }
89 return $messages;
90 }
91
92 /** Return the sources of the given message
93 */
94 public function getMessageSource($id)
95 {
96 $message =& $this->getRawMessage($id);
97 if ($message) {
98 $message = implode("\n", $message);
99 }
100 return $message;
101 }
102
103 /** Compute the number of messages of the box
104 */
105 private function getCount()
106 {
107 $options = array();
108 if (@filesize($this->getFileName()) == Banana::$spool->storage['size']) {
109 return max(Banana::$spool->ids);
110 }
111 $this->getMBoxPosition($options);
112 $val =& $this->callHelper('-c', $options);
113 if (!$val) {
114 return 0;
115 }
116 return intval(trim($val[0]));
117 }
118
119 /** Return the indexes of the messages presents in the Box
120 * @return Array(number of messages, MSGNUM of the first message, MSGNUM of the last message)
121 */
122 public function getIndexes()
123 {
124 $count = $this->getCount();
125 return array($count, 0, $count - 1);
126 }
127
128 /** Return the message headers (in BananaMessage) for messages from firstid to lastid
129 * @return Array(id => array(headername => headervalue))
130 */
131 public function &getMessageHeaders($firstid, $lastid, array $msg_headers = array())
132 {
133 $headers = null;
134 $options = array();
135 $options[] = "-m $firstid:$lastid";
136 $this->getMboxPosition($options, $firstid);
137 $lines =& $this->callHelper('-d', $options, $msg_headers);
138 if (!$lines) {
139 return $headers;
140 }
141 $headers = array();
142 while ($lines) {
143 $id = array_shift($lines);
144 if ($id === '') {
145 continue;
146 }
147 $offset = array_shift($lines);
148 if ($offset === '') {
149 continue;
150 }
151 $id = intval($id);
152 $headers[$id] = array('beginning' => intval($offset));
153 while (true) {
154 $hname = array_shift($lines);
155 if ($hname === '') {
156 break;
157 }
158 $hval = array_shift($lines);
159 if ($hname == 'date') {
160 $headers[$id][$hname] = @strtotime($hval);
161 } else {
162 $headers[$id][$hname] = $hval;
163 }
164 }
165 }
166 array_walk_recursive($headers, array('BananaMimePart', 'decodeHeader'));
167 return $headers;
168 }
169
170 /** Add storage data in spool overview
171 */
172 public function updateSpool(array &$messages)
173 {
174 foreach ($messages as $id=>&$data) {
175 if (isset(Banana::$spool->overview[$id])) {
176 Banana::$spool->overview[$id]->storage['offset'] = $data['beginning'];
177 }
178 }
179 Banana::$spool->storage['size'] = @filesize($this->getFileName());
180 }
181
182 /** Return the indexes of the new messages since the give date
183 * @return Array(MSGNUM of new messages)
184 */
185 public function getNewIndexes($since)
186 {
187 $this->open();
188 if (is_null($this->file)) {
189 return array();
190 }
191 if (is_null($this->new_messages)) {
192 $this->getCount();
193 }
194 return range($this->count - $this->new_messages, $this->count - 1);
195 }
196
197 /** Return wether or not the protocole can be used to add new messages
198 */
199 public function canSend()
200 {
201 return true;
202 }
203
204 /** Return false because we can't cancel a mail
205 */
206 public function canCancel()
207 {
208 return false;
209 }
210
211 /** Return the list of requested headers
212 * @return Array('header1', 'header2', ...) with the key 'dest' for the destination header
213 * and 'reply' for the reply header, eg:
214 * * for a mail: Array('From', 'Subject', 'dest' => 'To', 'Cc', 'Bcc', 'reply' => 'Reply-To')
215 * * for a post: Array('From', 'Subject', 'dest' => 'Newsgroups', 'reply' => 'Followup-To')
216 */
217 public function requestedHeaders()
218 {
219 return Array('From', 'Subject', 'dest' => 'To', 'Cc', 'Bcc', 'reply' => 'Reply-To');
220 }
221
222 /** Send a message
223 * @return true if it was successfull
224 */
225 public function send(BananaMessage &$message)
226 {
227 $headers = $message->getHeaders();
228 $to = $headers['To'];
229 $subject = $headers['Subject'];
230 unset($headers['To']);
231 unset($headers['Subject']);
232 $hdrs = '';
233 foreach ($headers as $key=>$value) {
234 if (!empty($value)) {
235 $hdrs .= "$key: $value\r\n";
236 }
237 }
238 $body = $message->get(false);
239 return mail($to, $subject, $body, $hdrs);
240 }
241
242 /** Cancel a message
243 * @return true if it was successfull
244 */
245 public function cancel(BananaMessage &$message)
246 {
247 return false;
248 }
249
250 /** Return the protocole name
251 */
252 public function name()
253 {
254 return 'MBOX';
255 }
256
257 /** Return the spool filename
258 */
259 public function filename()
260 {
261 @list($mail, $domain) = explode('@', Banana::$group);
262 $file = "";
263 if (isset($domain)) {
264 $file = $domain . '_';
265 }
266 return $file . $mail;
267 }
268
269 /** Return the execution backtrace
270 */
271 public function backtrace()
272 {
273 if ($this->debug) {
274 return $this->bt;
275 } else {
276 return null;
277 }
278 }
279
280 #######
281 # Filesystem functions
282 #######
283
284 protected function getFileName()
285 {
286 if (is_null(Banana::$group)) {
287 return null;
288 }
289 @list($mail, $domain) = explode('@', Banana::$group);
290 return Banana::$mbox_path . '/' . $mail;
291 }
292
293 #######
294 # MBox parser
295 #######
296
297 /** Add the '-p' optioin for callHelper
298 */
299 private function getMBoxPosition(array &$options, $id = null)
300 {
301 if (Banana::$spool->overview) {
302 if (!is_null($id) && isset(Banana::$spool->overview[$id])) {
303 $key = $id;
304 } else {
305 $key = max(Banana::$spool->ids);
306 if (!is_null($id) && $key >= $id) {
307 return;
308 }
309 }
310 if (isset(Banana::$spool->overview[$key]->storage['offset'])) {
311 $options[] = '-p ' . $key . ':' . Banana::$spool->overview[$key]->storage['offset'];
312 }
313 }
314 }
315
316 private function &callHelper($action, array $options = array(), array $headers = array())
317 {
318 $action .= ' -f ' . $this->getFileName();
319 $cmd = Banana::$mbox_helper . " $action " . implode(' ', $options) . ' ' . implode(' ', $headers);
320 if ($this->debug) {
321 $start = microtime(true);
322 }
323 exec($cmd, $out, $return);
324 if ($this->debug) {
325 $this->bt[] = array('action' => $cmd, 'time' => (microtime(true) - $start),
326 'code' => $return, 'response' => count($out), 'error' => $return ? "Helper failed" : null);
327 }
328 if ($return != 0) {
329 $this->_lasterrorno = 1;
330 $this->_lasterrorcode = "Helper failed";
331 $out = null;
332 }
333 return $out;
334 }
335 }
336
337 // vim:set et sw=4 sts=4 ts=4 enc=utf-8:
338 ?>