Fix erroneous unread count in some specific cases.
[banana.git] / banana / nntp.inc.php
1 <?php
2 /********************************************************************************
3 * banana/nntp.inc.php : NNTP protocole handler
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__) . '/message.inc.php';
12 require_once dirname(__FILE__) . '/nntpcore.inc.php';
13 require_once dirname(__FILE__) . '/protocoleinterface.inc.php';
14
15 class BananaNNTP extends BananaNNTPCore implements BananaProtocoleInterface
16 {
17 private $ingroup = null;
18
19 private $mode = null;
20 private $boxes = null;
21
22 /** Build a protocole handler plugged on the given box
23 */
24 public function __construct()
25 {
26 $url = parse_url(Banana::$nntp_host);
27 if ($url['scheme'] == 'nntps' || $url['scheme'] == 'snntp') {
28 $url['host'] = 'ssl://' . $url['host'];
29 if (!isset($url['port'])) {
30 $url['port'] = 563;
31 }
32 } else if (!isset($url['port'])) {
33 $url['port'] = 119;
34 }
35 parent::__construct($url['host'], $url['port']);
36 if (isset($url['user'])) {
37 $this->authinfo($url['user'], $url['pass']);
38 }
39 }
40
41 /** Return the descript;ion of the current box
42 */
43 public function getDescription()
44 {
45 $descs = $this->xgtitle(Banana::$group);
46 if (isset($descs[Banana::$group])) {
47 return trim(utf8_encode($descs[Banana::$group]));
48 }
49 return null;
50 }
51
52 /** Return the list of the boxes
53 * @param mode Kind of boxes to list
54 * @param since date of last check (for new boxes and new messages)
55 * @return Array(boxname => array(desc => boxdescripton, msgnum => number of message, unread =>number of unread messages)
56 */
57 public function getBoxList($mode = Banana::BOXES_ALL, $since = 0, $withstats = false)
58 {
59 if (!is_array($this->boxes) || $this->mode != $mode) {
60 $descs = $this->xgtitle();
61 if ($mode == Banana::BOXES_NEW && $since) {
62 $list = $this->newgroups($since);
63 } else {
64 $list = $this->listGroups();
65 if ($mode == Banana::BOXES_SUB) {
66 if (is_array(Banana::$profile['subscribe'])) {
67 $sub = array_flip(Banana::$profile['subscribe']);
68 } else {
69 $sub = array();
70 }
71 $list = array_intersect_key($list, $sub);
72 }
73 }
74 $this->boxes = array();
75 foreach ($list as $group=>&$infos) {
76 if (isset($descs[$group])) {
77 $desc = $descs[$group];
78 if (!is_utf8($desc)) {
79 $desc = utf8_encode($desc);
80 }
81 $this->boxes[$group] = array('desc' => $desc);
82 } else {
83 $this->boxes[$group] = array('desc' => null);
84 }
85 }
86 ksort($this->boxes);
87 }
88 if ($withstats) {
89 foreach ($this->boxes as $group=>&$desc) {
90 list($msgnum, $first, $last, $groupname) = $this->group($group);
91 $this->ingroup = $group;
92 $new = $this->newnews($group, $since);
93 if (!is_array($new)) {
94 $new = 0;
95 } else {
96 $new = count($new);
97 }
98 $desc['msgnum'] = $msgnum;
99 $desc['unread'] = $new;
100 }
101 }
102 return $this->boxes;
103 }
104
105 /** Return a message
106 * @param id Id of the emssage (can be either an Message-id or a message index)
107 * @return A BananaMessage or null if the given id can't be retreived
108 */
109 public function &getMessage($id)
110 {
111 $message = null;
112 if (is_numeric($id) && Banana::$group != $this->ingroup) {
113 if (is_null(Banana::$spool)) {
114 $this->group(Banana::$group);
115 $this->ingroup = Banana::$group;
116 } else {
117 $id = array_search($id, Banana::$spool->ids);
118 }
119 }
120 $data = $this->article($id);
121 if ($data !== false) {
122 $message = new BananaMessage($data);
123 }
124 return $message;
125 }
126
127 /** Return the sources of the message
128 */
129 public function getMessageSource($id)
130 {
131 if (is_numeric($id) && Banana::$group != $this->ingroup) {
132 if (is_null(Banana::$spool)) {
133 $this->group(Banana::$group);
134 $this->ingroup = Banana::$group;
135 } else {
136 $id = array_search($id, Banana::$spool->ids);
137 }
138 }
139 $data = $this->article($id);
140 if ($data !== false) {
141 return implode("\n", $data);
142 }
143 $data = null;
144 return $data;
145 }
146
147 /** Return the indexes of the messages presents in the Box
148 * @return Array(number of messages, MSGNUM of the first message, MSGNUM of the last message)
149 */
150 public function getIndexes()
151 {
152 list($msgnum, $first, $last, $groupname) = $this->group(Banana::$group);
153 $this->ingroup = Banana::$group;
154 return array($msgnum, $first, $last);
155 }
156
157 /** Return the message headers (in BananaMessage) for messages from firstid to lastid
158 * @return Array(id => array(headername => headervalue))
159 */
160 public function &getMessageHeaders($firstid, $lastid, array $msg_headers = array())
161 {
162 $messages = array();
163 foreach ($msg_headers as $header) {
164 $headers = $this->xhdr($header, $firstid, $lastid);
165 $header = strtolower($header);
166 if ($header == 'date') {
167 $headers = array_map('strtotime', $headers);
168 } else {
169 array_walk($headers, array('BananaMimePart', 'decodeHeader'));
170 }
171 foreach ($headers as $id=>&$value) {
172 if (!isset($messages[$id])) {
173 $messages[$id] = array();
174 }
175 $messages[$id][$header] =& $value;
176 }
177 }
178 return $messages;
179 }
180
181 /** Add protocole specific data in the spool
182 */
183 public function updateSpool(array &$messages)
184 {
185 return true;
186 }
187
188 /** Return the indexes of the new messages since the give date
189 * @return Array(MSGNUM of new messages)
190 */
191 public function getNewIndexes($since)
192 {
193 return $this->newnews(Banana::$group, $since);
194 }
195
196 /** Return true if can post
197 */
198 public function canSend()
199 {
200 return $this->isValid();
201 }
202
203 /** Return true if can cancel
204 */
205 public function canCancel()
206 {
207 return $this->isValid();
208 }
209
210 /** Return the list of requested header for a new post
211 */
212 public function requestedHeaders()
213 {
214 return Array('From', 'Subject', 'dest' => 'Newsgroups', 'reply' => 'Followup-To', 'Organization');
215 }
216
217 /** Send the message
218 */
219 public function send(BananaMessage $message)
220 {
221 $sources = $message->get(true);
222 return $this->post($sources);
223 }
224
225 /** Cancel the message
226 */
227 public function cancel(BananaMessage $message)
228 {
229 $headers = Array('From' => Banana::$profile['From'],
230 'Newsgroups' => Banana::$group,
231 'Subject' => 'cmsg ' . $message->getHeaderValue('message-id'),
232 'Control' => 'cancel ' . $message->getHeaderValue('message-id'));
233 $headers = array_merge($headers, Banana::$msgedit_headers);
234 $body = 'Message canceled with Banana';
235 $msg = BananaMessage::newMessage($headers, $body);
236 return $this->send($msg);
237 }
238
239 /** Return the protocole name
240 */
241 public function name()
242 {
243 return 'NNTP';
244 }
245
246 /** Return the filename for the spool
247 */
248 public function filename()
249 {
250 $url = parse_url(Banana::$nntp_host);
251 $file = '';
252 if (isset($url['host'])) {
253 $file .= $url['host'] . '_';
254 }
255 if (isset($url['port'])) {
256 $file .= $url['port'] . '_';
257 }
258 $file .= Banana::$group;
259 return $file;
260 }
261 }
262
263 // vim:set et sw=4 sts=4 ts=4 enc=utf-8:
264 ?>