Completed
Pull Request — master (#1292)
by Christoph
09:56
created

Mailbox::getMessage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2
Metric Value
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 2
crap 2
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 6
		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...
93 3
			$query = $filter;
94 4
		} else {
95 3
			$query = new Horde_Imap_Client_Search_Query();
96 3
			if ($filter) {
97
				$query->text($filter, false);
98
			}
99
		}
100 6
		if ($this->getSpecialRole() !== 'trash') {
101 6
			$query->flag(Horde_Imap_Client::FLAG_DELETED, false);
102 6
		}
103 6
		$result = $this->conn->search($this->mailBox, $query, ['sort' => [Horde_Imap_Client::SORT_DATE]]);
104 6
		$ids = array_reverse($result['match']->ids);
105 6
		if ($from >= 0 && $count >= 0) {
106 3
			$ids = array_slice($ids, $from, $count);
107 3
		}
108 6
		$ids = new \Horde_Imap_Client_Ids($ids, false);
109
110 6
		$headers = [];
111
112 6
		$fetch_query = new \Horde_Imap_Client_Fetch_Query();
113 6
		$fetch_query->envelope();
114 6
		$fetch_query->flags();
115 6
		$fetch_query->size();
116 6
		$fetch_query->uid();
117 6
		$fetch_query->imapDate();
118 6
		$fetch_query->structure();
119
120 6
		$headers = array_merge($headers, [
121 6
			'importance',
122 6
			'list-post',
123
			'x-priority'
124 6
		]);
125 6
		$headers[] = 'content-type';
126
127 6
		$fetch_query->headers('imp', $headers, [
128 6
			'cache' => true,
129
			'peek'  => true
130 6
		]);
131
132 6
		$options = ['ids' => $ids];
133
		// $list is an array of Horde_Imap_Client_Data_Fetch objects.
134 6
		$headers = $this->conn->fetch($this->mailBox, $fetch_query, $options);
135
136 6
		ob_start(); // fix for Horde warnings
137 6
		$messages = [];
138 6
		foreach ($headers->ids() as $message_id) {
139 6
			$header = $headers[$message_id];
140 6
			$message = new IMAPMessage($this->conn, $this->mailBox, $message_id, $header);
141 6
			$messages[] = $message->getListArray();
142 6
		}
143 6
		ob_get_clean();
144
145
		// sort by time
146
		usort($messages, function($a, $b) {
147
			return $a['dateInt'] < $b['dateInt'];
148 6
		});
149
150 6
		return $messages;
151
	}
152
153
	/**
154
	 * @return array
155
	 */
156 3
	public function attributes() {
157 3
		return $this->attributes;
158
	}
159
160
	/**
161
	 * @param string $messageId
162
	 * @param bool $loadHtmlMessageBody
163
	 * @return IMAPMessage
164
	 */
165
	public function getMessage($messageId, $loadHtmlMessageBody = false) {
166
		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...
167
	}
168
169
	/**
170
	 * @param int $flags
171
	 * @return array
172
	 */
173 12
	public function getStatus($flags = \Horde_Imap_Client::STATUS_ALL) {
174 12
		return $this->conn->status($this->mailBox, $flags);
175
	}
176
177
	/**
178
	 * @return int
179
	 */
180 3
	public function getTotalMessages() {
181 3
		$status = $this->getStatus(\Horde_Imap_Client::STATUS_MESSAGES);
182 3
		return (int) $status['messages'];
183
	}
184
185 12
	protected function makeDisplayName() {
186 12
		$parts = explode($this->delimiter, $this->mailBox->utf8, 2);
187
188 12
		if (count($parts) > 1) {
189
			$displayName = $parts[1];
190 12
		} elseif (strtolower($this->mailBox->utf8) === 'inbox') {
191 6
			$displayName = 'Inbox';
192 6
		} else {
193 12
			$displayName = $this->mailBox->utf8;
194
		}
195
196 12
		$this->displayName = $displayName;
197 12
	}
198
199 13
	public function getFolderId() {
200 13
		return $this->mailBox->utf8;
201
	}
202
203
	/**
204
	 * @return string
205
	 */
206 6
	public function getParent() {
207 6
		$folderId = $this->getFolderId();
208 6
		$parts = explode($this->delimiter, $folderId, 2);
209
210 6
		if (count($parts) > 1) {
211
			return $parts[0];
212
		}
213
214 6
		return null;
215
	}
216
217
	/**
218
	 * @return string
219
	 */
220 9
	public function getSpecialRole() {
221 9
		return $this->specialRole;
222
	}
223
224
	/**
225
	 * @return string
226
	 */
227 6
	public function getDisplayName() {
228 6
		return $this->displayName;
229
	}
230
231
	/**
232
	 * @param string $displayName
233
	 */
234 6
	public function setDisplayName($displayName) {
235 6
		$this->displayName = $displayName;
236 6
	}
237
238
	/**
239
	 * @param integer $accountId
240
	 * @return array
241
	 */
242 6
	public function getListArray($accountId, $status = null) {
243 6
		$displayName = $this->getDisplayName();
244
		try {
245 6
			if (is_null($status)) {
246 3
				$status = $this->getStatus();
247 3
			}
248 6
			$total = $status['messages'];
249 6
			$specialRole = $this->getSpecialRole();
250 6
			$unseen = ($specialRole === 'trash') ? 0 : $status['unseen'];
251 6
			$isEmpty = ($total === 0);
252 6
			$noSelect = in_array('\\noselect', $this->attributes);
253 6
			$parentId = $this->getParent();
254 6
			$parentId = ($parentId !== null) ? base64_encode($parentId) : null;
255
			return [
256 6
				'id' => base64_encode($this->getFolderId()),
257 6
				'parent' => $parentId,
258 6
				'name' => $displayName,
259 6
				'specialRole' => $specialRole,
260 6
				'unseen' => $unseen,
261 6
				'total' => $total,
262 6
				'isEmpty' => $isEmpty,
263 6
				'accountId' => $accountId,
264 6
				'noSelect' => $noSelect,
265 6
				'uidvalidity' => $status['uidvalidity'],
266 6
				'uidnext' => $status['uidnext'],
267 6
				'delimiter' => $this->delimiter
268 6
			];
269
		} 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...
270
			return [
271
				'id' => base64_encode($this->getFolderId()),
272
				'parent' => null,
273
				'name' => $displayName,
274
				'specialRole' => null,
275
				'unseen' => 0,
276
				'total' => 0,
277
				'error' => $e->getMessage(),
278
				'isEmpty' => true,
279
				'accountId' => $accountId,
280
				'noSelect' => true
281
			];
282
		}
283
	}
284
	/**
285
	 * Get the special use role of the mailbox
286
	 *
287
	 * This method reads the attributes sent by the server
288
	 *
289
	 */
290 12
	protected function getSpecialRoleFromAttributes() {
291
		/*
292
		 * @todo: support multiple attributes on same folder
293
		 * "any given server or  message store may support
294
		 *  any combination of the attributes"
295
		 *  https://tools.ietf.org/html/rfc6154
296
		 */
297 12
		$result = null;
298 12
		if (is_array($this->attributes)) {
299
			/* Convert attributes to lowercase, because gmail
300
			 * returns them as lowercase (eg. \trash and not \Trash)
301
			 */
302
			$specialUseAttributes = [
303 12
				strtolower(Horde_Imap_Client::SPECIALUSE_ALL),
304 12
				strtolower(Horde_Imap_Client::SPECIALUSE_ARCHIVE),
305 12
				strtolower(Horde_Imap_Client::SPECIALUSE_DRAFTS),
306 12
				strtolower(Horde_Imap_Client::SPECIALUSE_FLAGGED),
307 12
				strtolower(Horde_Imap_Client::SPECIALUSE_JUNK),
308 12
				strtolower(Horde_Imap_Client::SPECIALUSE_SENT),
309 12
				strtolower(Horde_Imap_Client::SPECIALUSE_TRASH)
310 12
			];
311
312 12
			$attributes = array_map(function($n) {
313 6
				return strtolower($n);
314 12
			}, $this->attributes);
315
316 12
			foreach ($specialUseAttributes as $attr)  {
317 12
				if (in_array($attr, $attributes)) {
318 6
					$result = ltrim($attr, '\\');
319 6
					break;
320
				}
321 12
			}
322
323 12
		}
324
325 12
		$this->specialRole = $result;
326 12
	}
327
328
	/**
329
	 * Assign a special role to this mailbox based on its name
330
	 */
331 12
	protected function guessSpecialRole() {
332
333
		$specialFoldersDict = [
334 12
			'inbox'   => ['inbox'],
335 12
			'sent'    => ['sent', 'sent items', 'sent messages', 'sent-mail', 'sentmail'],
336 12
			'drafts'  => ['draft', 'drafts'],
337 12
			'archive' => ['archive', 'archives'],
338 12
			'trash'   => ['deleted messages', 'trash'],
339 12
			'junk'    => ['junk', 'spam', 'bulk mail'],
340 12
		];
341
342 12
		$lowercaseExplode = explode($this->delimiter, $this->getFolderId(), 2);
343 12
		$lowercaseId = strtolower(array_pop($lowercaseExplode));
344 12
		$result = null;
345 12
		foreach ($specialFoldersDict as $specialRole => $specialNames) {
346 12
			if (in_array($lowercaseId, $specialNames)) {
347 6
				$result = $specialRole;
348 6
				break;
349
			}
350 12
		}
351
352 12
		$this->specialRole = $result;
353 12
	}
354
355
	/**
356
	 * @param int $messageId
357
	 * @param string $attachmentId
358
	 * @return Attachment
359
	 */
360
	public function getAttachment($messageId, $attachmentId) {
361
		return new Attachment($this->conn, $this->mailBox, $messageId, $attachmentId);
362
	}
363
364
	/**
365
	 * @param string $rawBody
366
	 * @param array $flags
367
	 */
368 6
	public function saveMessage($rawBody, $flags = []) {
369
370 6
		$this->conn->append($this->mailBox, [
371
			[
372 6
				'data' => $rawBody,
373
				'flags' => $flags
374 6
			]
375 6
		]);
376 6
	}
377
378
	/**
379
	 * Save draft
380
	 *
381
	 * @param string $rawBody
382
	 * @return int UID of the saved draft
383
	 */
384
	public function saveDraft($rawBody) {
385
386
		$uids = $this->conn->append($this->mailBox, [
387
			[
388
				'data' => $rawBody,
389
				'flags' => [
390
					Horde_Imap_Client::FLAG_DRAFT,
391
					Horde_Imap_Client::FLAG_SEEN
392
				]
393
			]
394
		]);
395
		return $uids->current();
396
	}
397
398
	/**
399
	 * @param int $uid
400
	 * @param string $flag
401
	 * @param boolean $add
402
	 */
403
	public function setMessageFlag($uid, $flag, $add) {
404
		$options = [
405
			'ids' => new Horde_Imap_Client_Ids($uid)
406
		];
407
		if ($add) {
408
			$options['add'] = [$flag];
409
		} else {
410
			$options['remove'] = [$flag];
411
		}
412
		$this->conn->store($this->mailBox, $options);
413
	}
414
415
	/**
416
	 * @param $fromUid
417
	 * @param $toUid
418
	 * @return array
419
	 */
420 3
	public function getMessagesSince($fromUid, $toUid) {
421 3
		$query = new Horde_Imap_Client_Search_Query();
422 3
		$query->ids(new Horde_Imap_Client_Ids("$fromUid:$toUid"));
423 3
		return $this->getMessages(-1, -1, $query);
424
	}
425
426
	/**
427
	 * @return Horde_Imap_Client_Mailbox
428
	 */
429
	public function getHordeMailBox() {
430
		return $this->mailBox;
431
	}
432
433
}
434