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