Completed
Pull Request — master (#747)
by Christoph
08:09
created

Mailbox::getMessages()   C

Complexity

Conditions 8
Paths 26

Size

Total Lines 69
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 45
CRAP Score 8.156

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 69
ccs 45
cts 52
cp 0.8654
rs 6.5437
cc 8
eloc 48
nc 26
nop 3
crap 8.156

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @author Christoph Wurst <[email protected]>
4
 * @author Clement Wong <[email protected]>
5
 * @author Jan-Christoph Borchardt <[email protected]>
6
 * @author Lukas Reschke <[email protected]>
7
 * @author matiasdelellis <[email protected]>
8
 * @author Robin McCorkell <[email protected]>
9
 * @author Scrutinizer Auto-Fixer <[email protected]>
10
 * @author Thomas Imbreckx <[email protected]>
11
 * @author Thomas I <[email protected]>
12
 * @author Thomas Mueller <[email protected]>
13
 * @author Thomas Müller <[email protected]>
14
 *
15
 * ownCloud - Mail
16
 *
17
 * This code is free software: you can redistribute it and/or modify
18
 * it under the terms of the GNU Affero General Public License, version 3,
19
 * as published by the Free Software Foundation.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License, version 3,
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
28
 *
29
 */
30
31
namespace OCA\Mail;
32
33
use Horde_Imap_Client;
34
use Horde_Imap_Client_Ids;
35
use Horde_Imap_Client_Mailbox;
36
use Horde_Imap_Client_Search_Query;
37
use Horde_Imap_Client_Socket;
38
use OCA\Mail\Model\IMAPMessage;
39
use OCA\Mail\Service\IMailBox;
40
41
class Mailbox implements IMailBox {
42
43
	/**
44
	 * @var Horde_Imap_Client_Socket
45
	 */
46
	protected $conn;
47
48
	/**
49
	 * @var array
50
	 */
51
	private $attributes;
52
53
	/**
54
	 * @var string
55
	 */
56
	private $specialRole;
57
58
	/**
59
	 * @var string
60
	 */
61
	private $displayName;
62
63
	/**
64
	 * @var string
65
	 */
66
	private $delimiter;
67
68
	/**
69
	 * @var Horde_Imap_Client_Mailbox
70
	 */
71
	protected $mailBox;
72
73
	/**
74
	 * @param Horde_Imap_Client_Socket $conn
75
	 * @param Horde_Imap_Client_Mailbox $mailBox
76
	 * @param array $attributes
77
	 * @param string $delimiter
78
	 */
79 12
	public function __construct($conn, $mailBox, $attributes, $delimiter='/') {
80 12
		$this->conn = $conn;
81 12
		$this->mailBox = $mailBox;
82 12
		$this->attributes = $attributes;
83 12
		$this->delimiter = $delimiter;
84 12
		$this->getSpecialRoleFromAttributes();
85 12
		if ($this->specialRole === null) {
86 12
			$this->guessSpecialRole();
87 12
		}
88 12
		$this->makeDisplayName();
89 12
	}
90
91 6
	public function getMessages($from = 0, $count = 2, $filter = '') {
92
93 6
		if (empty($filter)) {
94 3
			$total = $this->getTotalMessages();
95 3
			$from = $total - $from;
96 3
			$to = max($from - $count + 1, 1);
97 3
			$ids = new \Horde_Imap_Client_Ids("$from:$to", true);
98 3
		} else {
99 3
			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...
100 3
				$query = $filter;
101 3
			} else {
102
				$query = new Horde_Imap_Client_Search_Query();
103
				if ($filter) {
104
					$query->text($filter, false);
105
				}
106
			}
107 3
			if ($this->getSpecialRole() !== 'trash') {
108 3
				$query->flag(Horde_Imap_Client::FLAG_DELETED, false);
109 3
			}
110 3
			$result = $this->conn->search($this->mailBox, $query, ['sort' => [Horde_Imap_Client::SORT_DATE]]);
111 3
			$ids = array_reverse($result['match']->ids);
112 3
			if ($from >= 0 && $count >= 0) {
113
				$ids = array_slice($ids, $from, $count);
114
			}
115 3
			$ids = new \Horde_Imap_Client_Ids($ids, false);
116
		}
117
118 6
		$headers = [];
119
120 6
		$fetch_query = new \Horde_Imap_Client_Fetch_Query();
121 6
		$fetch_query->envelope();
122 6
		$fetch_query->flags();
123 6
		$fetch_query->size();
124 6
		$fetch_query->uid();
125 6
		$fetch_query->imapDate();
126 6
		$fetch_query->structure();
127
128 6
		$headers = array_merge($headers, [
129 6
			'importance',
130 6
			'list-post',
131
			'x-priority'
132 6
		]);
133 6
		$headers[] = 'content-type';
134
135 6
		$fetch_query->headers('imp', $headers, [
136 6
			'cache' => true,
137
			'peek'  => true
138 6
		]);
139
140 6
		$options = ['ids' => $ids];
141
		// $list is an array of Horde_Imap_Client_Data_Fetch objects.
142 6
		$headers = $this->conn->fetch($this->mailBox, $fetch_query, $options);
143
144 6
		ob_start(); // fix for Horde warnings
145 6
		$messages = [];
146 6
		foreach ($headers->ids() as $message_id) {
147 6
			$header = $headers[$message_id];
148 6
			$message = new IMAPMessage($this->conn, $this->mailBox, $message_id, $header);
149 6
			$messages[] = $message->getListArray();
150 6
		}
151 6
		ob_get_clean();
152
153
		// sort by time
154
		usort($messages, function($a, $b) {
155
			return $a['dateInt'] < $b['dateInt'];
156 6
		});
157
158 6
		return $messages;
159
	}
160
161
	/**
162
	 * @return array
163
	 */
164 3
	public function attributes() {
165 3
		return $this->attributes;
166
	}
167
168
	/**
169
	 * @param string $messageId
170
	 * @param bool $loadHtmlMessageBody
171
	 * @return IMAPMessage
172
	 */
173
	public function getMessage($messageId, $loadHtmlMessageBody = false) {
174
		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...
175
	}
176
177
	/**
178
	 * @param int $flags
179
	 * @return array
180
	 */
181 12
	public function getStatus($flags = \Horde_Imap_Client::STATUS_ALL) {
182 12
		return $this->conn->status($this->mailBox, $flags);
183
	}
184
185
	/**
186
	 * @return int
187
	 */
188 3
	public function getTotalMessages() {
189 3
		$status = $this->getStatus(\Horde_Imap_Client::STATUS_MESSAGES);
190 3
		return (int) $status['messages'];
191
	}
192
193 12
	protected function makeDisplayName() {
194 12
		$parts = explode($this->delimiter, $this->mailBox->utf8, 2);
195
196 12
		if (count($parts) > 1) {
197
			$displayName = $parts[1];
198 12
		} elseif (strtolower($this->mailBox->utf8) === 'inbox') {
199 6
			$displayName = 'Inbox';
200 6
		} else {
201 12
			$displayName = $this->mailBox->utf8;
202
		}
203
204 12
		$this->displayName = $displayName;
205 12
	}
206
207 13
	public function getFolderId() {
208 13
		return $this->mailBox->utf8;
209
	}
210
211
	/**
212
	 * @return string
213
	 */
214 6
	public function getParent() {
215 6
		$folderId = $this->getFolderId();
216 6
		$parts = explode($this->delimiter, $folderId, 2);
217
218 6
		if (count($parts) > 1) {
219
			return $parts[0];
220
		}
221
222 6
		return null;
223
	}
224
225
	/**
226
	 * @return string
227
	 */
228 6
	public function getSpecialRole() {
229 6
		return $this->specialRole;
230
	}
231
232
	/**
233
	 * @return string
234
	 */
235 6
	public function getDisplayName() {
236 6
		return $this->displayName;
237
	}
238
239
	/**
240
	 * @param string $displayName
241
	 */
242 6
	public function setDisplayName($displayName) {
243 6
		$this->displayName = $displayName;
244 6
	}
245
246
	/**
247
	 * @param integer $accountId
248
	 * @return array
249
	 */
250 6
	public function getListArray($accountId, $status = null) {
251 6
		$displayName = $this->getDisplayName();
252
		try {
253 6
			if (is_null($status)) {
254 3
				$status = $this->getStatus();
255 3
			}
256 6
			$total = $status['messages'];
257 6
			$specialRole = $this->getSpecialRole();
258 6
			$unseen = ($specialRole === 'trash') ? 0 : $status['unseen'];
259 6
			$isEmpty = ($total === 0);
260 6
			$noSelect = in_array('\\noselect', $this->attributes);
261 6
			$parentId = $this->getParent();
262 6
			$parentId = ($parentId !== null) ? base64_encode($parentId) : null;
263
			return [
264 6
				'id' => base64_encode($this->getFolderId()),
265 6
				'parent' => $parentId,
266 6
				'name' => $displayName,
267 6
				'specialRole' => $specialRole,
268 6
				'unseen' => $unseen,
269 6
				'total' => $total,
270 6
				'isEmpty' => $isEmpty,
271 6
				'accountId' => $accountId,
272 6
				'noSelect' => $noSelect,
273 6
				'uidvalidity' => $status['uidvalidity'],
274 6
				'uidnext' => $status['uidnext'],
275 6
				'delimiter' => $this->delimiter
276 6
			];
277
		} 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...
278
			return [
279
				'id' => base64_encode($this->getFolderId()),
280
				'parent' => null,
281
				'name' => $displayName,
282
				'specialRole' => null,
283
				'unseen' => 0,
284
				'total' => 0,
285
				'error' => $e->getMessage(),
286
				'isEmpty' => true,
287
				'accountId' => $accountId,
288
				'noSelect' => true
289
			];
290
		}
291
	}
292
	/**
293
	 * Get the special use role of the mailbox
294
	 *
295
	 * This method reads the attributes sent by the server
296
	 *
297
	 */
298 12
	protected function getSpecialRoleFromAttributes() {
299
		/*
300
		 * @todo: support multiple attributes on same folder
301
		 * "any given server or  message store may support
302
		 *  any combination of the attributes"
303
		 *  https://tools.ietf.org/html/rfc6154
304
		 */
305 12
		$result = null;
306 12
		if (is_array($this->attributes)) {
307
			/* Convert attributes to lowercase, because gmail
308
			 * returns them as lowercase (eg. \trash and not \Trash)
309
			 */
310
			$specialUseAttributes = [
311 12
				strtolower(Horde_Imap_Client::SPECIALUSE_ALL),
312 12
				strtolower(Horde_Imap_Client::SPECIALUSE_ARCHIVE),
313 12
				strtolower(Horde_Imap_Client::SPECIALUSE_DRAFTS),
314 12
				strtolower(Horde_Imap_Client::SPECIALUSE_FLAGGED),
315 12
				strtolower(Horde_Imap_Client::SPECIALUSE_JUNK),
316 12
				strtolower(Horde_Imap_Client::SPECIALUSE_SENT),
317 12
				strtolower(Horde_Imap_Client::SPECIALUSE_TRASH)
318 12
			];
319
320 12
			$attributes = array_map(function($n) {
321 6
				return strtolower($n);
322 12
			}, $this->attributes);
323
324 12
			foreach ($specialUseAttributes as $attr)  {
325 12
				if (in_array($attr, $attributes)) {
326 6
					$result = ltrim($attr, '\\');
327 6
					break;
328
				}
329 12
			}
330
331 12
		}
332
333 12
		$this->specialRole = $result;
334 12
	}
335
336
	/**
337
	 * Assign a special role to this mailbox based on its name
338
	 */
339 12
	protected function guessSpecialRole() {
340
341
		$specialFoldersDict = [
342 12
			'inbox'   => ['inbox'],
343 12
			'sent'    => ['sent', 'sent items', 'sent messages', 'sent-mail', 'sentmail'],
344 12
			'drafts'  => ['draft', 'drafts'],
345 12
			'archive' => ['archive', 'archives'],
346 12
			'trash'   => ['deleted messages', 'trash'],
347 12
			'junk'    => ['junk', 'spam', 'bulk mail'],
348 12
		];
349
350 12
		$lowercaseExplode = explode($this->delimiter, $this->getFolderId(), 2);
351 12
		$lowercaseId = strtolower(array_pop($lowercaseExplode));
352 12
		$result = null;
353 12
		foreach ($specialFoldersDict as $specialRole => $specialNames) {
354 12
			if (in_array($lowercaseId, $specialNames)) {
355 6
				$result = $specialRole;
356 6
				break;
357
			}
358 12
		}
359
360 12
		$this->specialRole = $result;
361 12
	}
362
363
	/**
364
	 * @param int $messageId
365
	 * @param string $attachmentId
366
	 * @return Attachment
367
	 */
368
	public function getAttachment($messageId, $attachmentId) {
369
		return new Attachment($this->conn, $this->mailBox, $messageId, $attachmentId);
370
	}
371
372
	/**
373
	 * @param string $rawBody
374
	 * @param array $flags
375
	 */
376 6
	public function saveMessage($rawBody, $flags = []) {
377
378 6
		$this->conn->append($this->mailBox, [
379
			[
380 6
				'data' => $rawBody,
381
				'flags' => $flags
382 6
			]
383 6
		]);
384 6
	}
385
386
	/**
387
	 * Save draft
388
	 *
389
	 * @param string $rawBody
390
	 * @return int UID of the saved draft
391
	 */
392
	public function saveDraft($rawBody) {
393
394
		$uids = $this->conn->append($this->mailBox, [
395
			[
396
				'data' => $rawBody,
397
				'flags' => [
398
					Horde_Imap_Client::FLAG_DRAFT,
399
					Horde_Imap_Client::FLAG_SEEN
400
				]
401
			]
402
		]);
403
		return $uids->current();
404
	}
405
406
	/**
407
	 * @param int $uid
408
	 * @param string $flag
409
	 * @param boolean $add
410
	 */
411
	public function setMessageFlag($uid, $flag, $add) {
412
		$options = [
413
			'ids' => new Horde_Imap_Client_Ids($uid)
414
		];
415
		if ($add) {
416
			$options['add'] = [$flag];
417
		} else {
418
			$options['remove'] = [$flag];
419
		}
420
		$this->conn->store($this->mailBox, $options);
421
	}
422
423
	/**
424
	 * @param $fromUid
425
	 * @param $toUid
426
	 * @return array
427
	 */
428 3
	public function getMessagesSince($fromUid, $toUid) {
429 3
		$query = new Horde_Imap_Client_Search_Query();
430 3
		$query->ids(new Horde_Imap_Client_Ids("$fromUid:$toUid"));
431 3
		return $this->getMessages(-1, -1, $query);
432
	}
433
434
	/**
435
	 * @return Horde_Imap_Client_Mailbox
436
	 */
437
	public function getHordeMailBox() {
438
		return $this->mailBox;
439
	}
440
441
}
442