Completed
Pull Request — master (#1192)
by Christoph
06:02
created

Mailbox::getStatus()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 * Copyright (c) 2012 Bart Visscher <[email protected]>
4
 * This file is licensed under the Affero General Public License version 3 or
5
 * later.
6
 * See the COPYING-README file.
7
 */
8
9
namespace OCA\Mail;
10
11
use Horde_Imap_Client;
12
use Horde_Imap_Client_Ids;
13
use Horde_Imap_Client_Mailbox;
14
use Horde_Imap_Client_Search_Query;
15
use Horde_Imap_Client_Socket;
16
use OCA\Mail\Model\IMAPMessage;
17
use OCA\Mail\Service\IMailBox;
18
19
class Mailbox implements IMailBox {
20
21
	/**
22
	 * @var Horde_Imap_Client_Socket
23
	 */
24
	protected $conn;
25
26
	/**
27
	 * @var array
28
	 */
29
	private $attributes;
30
31
	/**
32
	 * @var string
33
	 */
34
	private $specialRole;
35
36
	/**
37
	 * @var string
38
	 */
39
	private $displayName;
40
41
	/**
42
	 * @var string
43
	 */
44
	private $delimiter;
45
46
	/**
47
	 * @var Horde_Imap_Client_Mailbox
48
	 */
49
	protected $mailBox;
50
51
	/**
52
	 * @param Horde_Imap_Client_Socket $conn
53
	 * @param Horde_Imap_Client_Mailbox $mailBox
54
	 * @param array $attributes
55
	 * @param string $delimiter
56
	 */
57 5
	public function __construct($conn, $mailBox, $attributes, $delimiter='/') {
58 5
		$this->conn = $conn;
59 5
		$this->mailBox = $mailBox;
60 5
		$this->attributes = $attributes;
61 5
		$this->delimiter = $delimiter;
62 5
		$this->getSpecialRoleFromAttributes();
63 5
		if ($this->specialRole === null) {
64 5
			$this->guessSpecialRole();
65 5
		}
66 5
		$this->makeDisplayName();
67 5
	}
68
69
	public function getMessages($from = 0, $count = 2, $filter = '') {
70
		if ($filter instanceof Horde_Imap_Client_Search_Query) {
0 ignored issues
show
Bug introduced by
The class Horde_Imap_Client_Search_Query does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
71
			$query = $filter;
72
		} else {
73
			$query = new Horde_Imap_Client_Search_Query();
74
			if ($filter) {
75
				$query->text($filter, false);
76
			}
77
		}
78
		if ($this->getSpecialRole() !== 'trash') {
79
			$query->flag(Horde_Imap_Client::FLAG_DELETED, false);
80
		}
81
		$result = $this->conn->search($this->mailBox, $query, ['sort' => [Horde_Imap_Client::SORT_DATE]]);
82
		$ids = array_reverse($result['match']->ids);
83
		if ($from >= 0 && $count >= 0) {
84
			$ids = array_slice($ids, $from, $count);
85
		}
86
		$ids = new \Horde_Imap_Client_Ids($ids, false);
87
88
		$headers = [];
89
90
		$fetch_query = new \Horde_Imap_Client_Fetch_Query();
91
		$fetch_query->envelope();
92
		$fetch_query->flags();
93
		$fetch_query->size();
94
		$fetch_query->uid();
95
		$fetch_query->imapDate();
96
		$fetch_query->structure();
97
98
		$headers = array_merge($headers, [
99
			'importance',
100
			'list-post',
101
			'x-priority'
102
		]);
103
		$headers[] = 'content-type';
104
105
		$fetch_query->headers('imp', $headers, [
106
			'cache' => true,
107
			'peek'  => true
108
		]);
109
110
		$options = ['ids' => $ids];
111
		// $list is an array of Horde_Imap_Client_Data_Fetch objects.
112
		$headers = $this->conn->fetch($this->mailBox, $fetch_query, $options);
113
114
		ob_start(); // fix for Horde warnings
115
		$messages = [];
116
		foreach ($headers->ids() as $message_id) {
117
			$header = $headers[$message_id];
118
			$message = new IMAPMessage($this->conn, $this->mailBox, $message_id, $header);
119
			$messages[] = $message->getListArray();
120
		}
121
		ob_get_clean();
122
123
		// sort by time
124
		usort($messages, function($a, $b) {
125
			return $a['dateInt'] < $b['dateInt'];
126
		});
127
128
		return $messages;
129
	}
130
131
	/**
132
	 * @return array
133
	 */
134 3
	public function attributes() {
135 3
		return $this->attributes;
136
	}
137
138
	/**
139
	 * @param string $messageId
140
	 * @param bool $loadHtmlMessageBody
141
	 * @return IMAPMessage
142
	 */
143
	public function getMessage($messageId, $loadHtmlMessageBody = false) {
144
		return new IMAPMessage($this->conn, $this->mailBox, $messageId, null, $loadHtmlMessageBody);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \OCA\Mail\Mod... $loadHtmlMessageBody); (OCA\Mail\Model\IMAPMessage) is incompatible with the return type declared by the interface OCA\Mail\Service\IMailBox::getMessage of type OCA\Mail\Message.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
145
	}
146
147
	/**
148
	 * @param int $flags
149
	 * @return array
150
	 */
151 12
	public function getStatus($flags = \Horde_Imap_Client::STATUS_ALL) {
152 12
		return $this->conn->status($this->mailBox, $flags);
153
	}
154
155
	/**
156
	 * @return int
157
	 */
158 3
	public function getTotalMessages() {
159 3
		$status = $this->getStatus(\Horde_Imap_Client::STATUS_MESSAGES);
160
		return (int) $status['messages'];
161
	}
162
163 5
	protected function makeDisplayName() {
164 5
		$parts = explode($this->delimiter, $this->mailBox->utf8, 2);
165
166 5
		if (count($parts) > 1) {
167 4
			$displayName = $parts[1];
168 5
		} elseif (strtolower($this->mailBox->utf8) === 'inbox') {
169 4
			$displayName = 'Inbox';
170 4
		} else {
171 5
			$displayName = $this->mailBox->utf8;
172
		}
173
174 5
		$this->displayName = $displayName;
175 5
	}
176
177 5
	public function getFolderId() {
178 5
		return $this->mailBox->utf8;
179
	}
180
181
	/**
182
	 * @return string
183
	 */
184 3
	public function getParent() {
185 3
		$folderId = $this->getFolderId();
186 3
		$parts = explode($this->delimiter, $folderId, 2);
187
188 3
		if (count($parts) > 1) {
189 3
			return $parts[0];
190
		}
191
192 3
		return null;
193
	}
194
195
	/**
196
	 * @return string
197
	 */
198 4
	public function getSpecialRole() {
199 4
		return $this->specialRole;
200
	}
201
202
	/**
203
	 * @return string
204
	 */
205 4
	public function getDisplayName() {
206 4
		return $this->displayName;
207
	}
208
209
	/**
210
	 * @param string $displayName
211
	 */
212 4
	public function setDisplayName($displayName) {
213 4
		$this->displayName = $displayName;
214 4
	}
215
216
	/**
217
	 * @param integer $accountId
218
	 * @return array
219
	 */
220 3
	public function getListArray($accountId, $status = null) {
221 3
		$displayName = $this->getDisplayName();
222
		try {
223 3
			if (is_null($status)) {
224 3
				$status = $this->getStatus();
225 3
			}
226 3
			$total = $status['messages'];
227 3
			$specialRole = $this->getSpecialRole();
228 3
			$unseen = ($specialRole === 'trash') ? 0 : $status['unseen'];
229 3
			$isEmpty = ($total === 0);
230 3
			$noSelect = in_array('\\noselect', $this->attributes);
231 3
			$parentId = $this->getParent();
232 3
			$parentId = ($parentId !== null) ? base64_encode($parentId) : null;
233
			return [
234 3
				'id' => base64_encode($this->getFolderId()),
235 3
				'parent' => $parentId,
236 3
				'name' => $displayName,
237 3
				'specialRole' => $specialRole,
238 3
				'unseen' => $unseen,
239 3
				'total' => $total,
240 3
				'isEmpty' => $isEmpty,
241 3
				'accountId' => $accountId,
242 3
				'noSelect' => $noSelect,
243 3
				'uidvalidity' => $status['uidvalidity'],
244 3
				'uidnext' => $status['uidnext'],
245 3
				'delimiter' => $this->delimiter
246 3
			];
247 3
		} catch (\Horde_Imap_Client_Exception $e) {
0 ignored issues
show
Bug introduced by
The class Horde_Imap_Client_Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
248
			return [
249 3
				'id' => base64_encode($this->getFolderId()),
250 3
				'parent' => null,
251 3
				'name' => $displayName,
252 3
				'specialRole' => null,
253 3
				'unseen' => 0,
254 3
				'total' => 0,
255 3
				'error' => $e->getMessage(),
256 3
				'isEmpty' => true,
257 3
				'accountId' => $accountId,
258
				'noSelect' => true
259 3
			];
260
		}
261
	}
262
	/**
263
	 * Get the special use role of the mailbox
264
	 *
265
	 * This method reads the attributes sent by the server
266
	 *
267
	 */
268 5
	protected function getSpecialRoleFromAttributes() {
269
		/*
270
		 * @todo: support multiple attributes on same folder
271
		 * "any given server or  message store may support
272
		 *  any combination of the attributes"
273
		 *  https://tools.ietf.org/html/rfc6154
274
		 */
275 5
		$result = null;
276 5
		if (is_array($this->attributes)) {
277
			/* Convert attributes to lowercase, because gmail
278
			 * returns them as lowercase (eg. \trash and not \Trash)
279
			 */
280
			$specialUseAttributes = [
281 5
				strtolower(Horde_Imap_Client::SPECIALUSE_ALL),
282 5
				strtolower(Horde_Imap_Client::SPECIALUSE_ARCHIVE),
283 5
				strtolower(Horde_Imap_Client::SPECIALUSE_DRAFTS),
284 5
				strtolower(Horde_Imap_Client::SPECIALUSE_FLAGGED),
285 5
				strtolower(Horde_Imap_Client::SPECIALUSE_JUNK),
286 5
				strtolower(Horde_Imap_Client::SPECIALUSE_SENT),
287 5
				strtolower(Horde_Imap_Client::SPECIALUSE_TRASH)
288 5
			];
289
290 5
			$attributes = array_map(function($n) {
291 4
				return strtolower($n);
292 5
			}, $this->attributes);
293
294 5
			foreach ($specialUseAttributes as $attr)  {
295 5
				if (in_array($attr, $attributes)) {
296 4
					$result = ltrim($attr, '\\');
297 4
					break;
298
				}
299 5
			}
300
301 5
		}
302
303 5
		$this->specialRole = $result;
304 5
	}
305
306
	/**
307
	 * Assign a special role to this mailbox based on its name
308
	 */
309 5
	protected function guessSpecialRole() {
310
311
		$specialFoldersDict = [
312 5
			'inbox'   => ['inbox'],
313 5
			'sent'    => ['sent', 'sent items', 'sent messages', 'sent-mail', 'sentmail'],
314 5
			'drafts'  => ['draft', 'drafts'],
315 5
			'archive' => ['archive', 'archives'],
316 5
			'trash'   => ['deleted messages', 'trash'],
317 5
			'junk'    => ['junk', 'spam', 'bulk mail'],
318 5
		];
319
320 5
		$lowercaseExplode = explode($this->delimiter, $this->getFolderId(), 2);
321 5
		$lowercaseId = strtolower(array_pop($lowercaseExplode));
322 5
		$result = null;
323 5
		foreach ($specialFoldersDict as $specialRole => $specialNames) {
324 5
			if (in_array($lowercaseId, $specialNames)) {
325 4
				$result = $specialRole;
326 4
				break;
327
			}
328 5
		}
329
330 5
		$this->specialRole = $result;
331 5
	}
332
333
	/**
334
	 * @param int $messageId
335
	 * @param string $attachmentId
336
	 * @return Attachment
337
	 */
338
	public function getAttachment($messageId, $attachmentId) {
339
		return new Attachment($this->conn, $this->mailBox, $messageId, $attachmentId);
340
	}
341
342
	/**
343
	 * @param string $rawBody
344
	 * @param array $flags
345
	 */
346
	public function saveMessage($rawBody, $flags = []) {
347
348
		$this->conn->append($this->mailBox, [
349
			[
350
				'data' => $rawBody,
351
				'flags' => $flags
352
			]
353
		]);
354
	}
355
356
	/**
357
	 * Save draft
358
	 *
359
	 * @param string $rawBody
360
	 * @return int UID of the saved draft
361
	 */
362
	public function saveDraft($rawBody) {
363
364
		$uids = $this->conn->append($this->mailBox, [
365
			[
366
				'data' => $rawBody,
367
				'flags' => [
368
					Horde_Imap_Client::FLAG_DRAFT,
369
					Horde_Imap_Client::FLAG_SEEN
370
				]
371
			]
372
		]);
373
		return $uids->current();
374
	}
375
376
	/**
377
	 * @param int $uid
378
	 * @param string $flag
379
	 * @param boolean $add
380
	 */
381
	public function setMessageFlag($uid, $flag, $add) {
382
		$options = [
383
			'ids' => new Horde_Imap_Client_Ids($uid)
384
		];
385
		if ($add) {
386
			$options['add'] = [$flag];
387
		} else {
388
			$options['remove'] = [$flag];
389
		}
390
		$this->conn->store($this->mailBox, $options);
391
	}
392
393
	/**
394
	 * @param $fromUid
395
	 * @param $toUid
396
	 * @return array
397
	 */
398
	public function getMessagesSince($fromUid, $toUid) {
399
		$query = new Horde_Imap_Client_Search_Query();
400
		$query->ids(new Horde_Imap_Client_Ids("$fromUid:$toUid"));
401
		return $this->getMessages(-1, -1, $query);
402
	}
403
404
	/**
405
	 * @return Horde_Imap_Client_Mailbox
406
	 */
407
	public function getHordeMailBox() {
408
		return $this->mailBox;
409
	}
410
411
}
412