Completed
Pull Request — master (#1276)
by Thomas
04:23
created

Account   D

Complexity

Total Complexity 89

Size/Duplication

Total Lines 722
Duplicated Lines 3.88 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 48.01%

Importance

Changes 7
Bugs 1 Features 1
Metric Value
wmc 89
c 7
b 1
f 1
lcom 1
cbo 7
dl 28
loc 722
ccs 181
cts 377
cp 0.4801
rs 4.4444

32 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A getId() 0 3 1
A getName() 0 3 1
A getEMailAddress() 0 3 1
B getImapConnection() 0 34 5
A createMailbox() 0 7 1
B sendMessage() 0 44 4
B saveDraft() 0 36 2
A deleteMailbox() 0 8 2
B listMailboxes() 0 25 3
A getMailbox() 0 10 3
A getMailboxes() 0 9 2
B getListArray() 0 26 4
A createTransport() 0 18 2
B getSpecialFoldersIds() 0 13 5
A getDraftsFolder() 14 14 2
A getInbox() 0 4 2
A getSentFolder() 14 14 2
B deleteMessage() 0 44 4
A deleteDraft() 0 6 1
A guessBestMailBox() 0 12 3
B getSpecialFolder() 0 15 5
B localizeSpecialMailboxes() 0 32 4
D sortMailboxes() 0 46 10
A convertSslMode() 0 7 2
C getChangedMailboxes() 0 47 9
A reconnect() 0 8 2
A getConfiguration() 0 3 1
A getEmail() 0 3 1
A testConnectivity() 0 10 2
A newMessage() 0 3 1
A newReplyMessage() 0 3 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Account often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Account, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @author Alexander Weidinger <[email protected]>
4
 * @author Christian Nöding <[email protected]>
5
 * @author Christoph Wurst <[email protected]>
6
 * @author Christoph Wurst <[email protected]>
7
 * @author Christoph Wurst <[email protected]>
8
 * @author Clement Wong <[email protected]>
9
 * @author Lukas Reschke <[email protected]>
10
 * @author Robin McCorkell <[email protected]>
11
 * @author Scrutinizer Auto-Fixer <[email protected]>
12
 * @author Thomas Imbreckx <[email protected]>
13
 * @author Thomas I <[email protected]>
14
 * @author Thomas Mueller <[email protected]>
15
 * @author Thomas Müller <[email protected]>
16
 *
17
 * ownCloud - Mail
18
 *
19
 * This code is free software: you can redistribute it and/or modify
20
 * it under the terms of the GNU Affero General Public License, version 3,
21
 * as published by the Free Software Foundation.
22
 *
23
 * This program is distributed in the hope that it will be useful,
24
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
 * GNU Affero General Public License for more details.
27
 *
28
 * You should have received a copy of the GNU Affero General Public License, version 3,
29
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
30
 *
31
 */
32
33
namespace OCA\Mail;
34
35
use Horde_Imap_Client_Ids;
36
use Horde_Imap_Client_Mailbox;
37
use Horde_Imap_Client_Socket;
38
use Horde_Imap_Client;
39
use Horde_Mail_Rfc822_Address;
40
use Horde_Mail_Transport;
41
use Horde_Mail_Transport_Mail;
42
use Horde_Mail_Transport_Smtphorde;
43
use Horde_Mime_Headers_Date;
44
use Horde_Mime_Mail;
45
use Horde_Mime_Part;
46
use OCA\Mail\Cache\Cache;
47
use OCA\Mail\Db\MailAccount;
48
use OCA\Mail\Model\IMessage;
49
use OCA\Mail\Model\Message;
50
use OCA\Mail\Model\ReplyMessage;
51
use OCA\Mail\Service\AutoCompletion\AddressCollector;
52
use OCA\Mail\Service\IAccount;
53
use OCA\Mail\Service\IMailBox;
54
use OCP\IConfig;
55
use OCP\ICacheFactory;
56
use OCP\Security\ICrypto;
57
58
class Account implements IAccount {
59
60
	/** @var MailAccount */
61
	private $account;
62
63
	/** @var Mailbox[]|null */
64
	private $mailboxes;
65
66
	/** @var Horde_Imap_Client_Socket */
67
	private $client;
68
69
	/** @var ICrypto */
70
	private $crypto;
71
72
	/** @var IConfig */
73
	private $config;
74
75
	/** @var ICacheFactory */
76
	private $memcacheFactory;
77
78
	/**
79
	 * @param MailAccount $account
80
	 */
81 9
	public function __construct(MailAccount $account) {
82 3
		$this->account = $account;
83 3
		$this->mailboxes = null;
84 9
		$this->crypto = \OC::$server->getCrypto();
85 3
		$this->config = \OC::$server->getConfig();
86 3
		$this->memcacheFactory = \OC::$server->getMemcacheFactory();
87 3
	}
88
89
	/**
90
	 * @return int
91
	 */
92 6
	public function getId() {
93 6
		return $this->account->getId();
94
	}
95
96
	/**
97
	 * @return string
98
	 */
99
	public function getName() {
100
		return $this->account->getName();
101
	}
102
103
	/**
104
	 * @return string
105
	 */
106 3
	public function getEMailAddress() {
107 3
		return $this->account->getEmail();
108
	}
109
110
	/**
111
	 * @return Horde_Imap_Client_Socket
112
	 */
113 13
	public function getImapConnection() {
114 13
		if (is_null($this->client)) {
115
			$host = $this->account->getInboundHost();
116
			$user = $this->account->getInboundUser();
117
			$password = $this->account->getInboundPassword();
118
			$password = $this->crypto->decrypt($password);
119
			$port = $this->account->getInboundPort();
120
			$ssl_mode = $this->convertSslMode($this->account->getInboundSslMode());
121
122
			$params = [
123
				'username' => $user,
124
				'password' => $password,
125
				'hostspec' => $host,
126
				'port' => $port,
127
				'secure' => $ssl_mode,
128
				'timeout' => 20,
129
			];
130
			if ($this->config->getSystemValue('app.mail.imaplog.enabled', false)) {
131
				$params['debug'] = $this->config->getSystemValue('datadirectory') . '/horde.log';
132
			}
133
			if ($this->config->getSystemValue('app.mail.server-side-cache.enabled', false)) {
134
				if ($this->memcacheFactory->isAvailable()) {
135
					$params['cache'] = [
136
						'backend' => new Cache(array(
137
							'cacheob' => $this->memcacheFactory
138
								->create(md5($this->getId() . $this->getEMailAddress()))
139
						))];
140
				}
141
			}
142
			$this->client = new \Horde_Imap_Client_Socket($params);
143
			$this->client->login();
144
		}
145 13
		return $this->client;
146
	}
147
148
	/**
149
	 * @param string $mailBox
150
	 * @return Mailbox
151
	 */
152 12
	public function createMailbox($mailBox) {
153 12
		$conn = $this->getImapConnection();
154 12
		$conn->createMailbox($mailBox);
155 12
		$this->mailboxes = null;
156
157 12
		return $this->getMailbox($mailBox);
158
	}
159
160
	/**
161
	 * Send a new message or reply to an existing message
162
	 *
163
	 * @param IMessage $message
164
	 * @param int|null $draftUID
165
	 */
166 7
	public function sendMessage(IMessage $message, $draftUID) {
167
		// build mime body
168
		$from = new Horde_Mail_Rfc822_Address($message->getFrom());
169
		$from->personal = $this->getName();
170
		$headers = [
171
			'From' => $from,
172
			'To' => $message->getToList(),
173
			'Cc' => $message->getCCList(),
174
			'Bcc' => $message->getBCCList(),
175
			'Subject' => $message->getSubject(),
176
		];
177
178
		if (!is_null($message->getRepliedMessage())) {
179
			$headers['In-Reply-To'] = $message->getRepliedMessage()->getMessageId();
180
		}
181
182
		$mail = new Horde_Mime_Mail();
183
		$mail->addHeaders($headers);
184
		$mail->setBody($message->getContent());
185
186
		// Append attachments
187
		foreach ($message->getAttachments() as $attachment) {
188
			$mail->addMimePart($attachment);
189
		}
190
191
		// Send the message
192
		$transport = $this->createTransport();
193
		$mail->send($transport);
194
195
		// Save the message in the sent folder
196
		$sentFolder = $this->getSentFolder();
197
		/** @var resource $raw */
198
		$raw = stream_get_contents($mail->getRaw());
199
		$sentFolder->saveMessage($raw, [
0 ignored issues
show
Documentation introduced by
$raw is of type resource, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
200 7
			Horde_Imap_Client::FLAG_SEEN
201
		]);
202
203
		// Delete draft if one exists
204
		if (!is_null($draftUID)) {
205
			$draftsFolder = $this->getDraftsFolder();
206
			$draftsFolder->setMessageFlag($draftUID, Horde_Imap_Client::FLAG_DELETED, true);
207
			$this->deleteDraft($draftUID);
208
		}
209
	}
210
211
	/**
212
	 * @param IMessage $message
213
	 * @param int|null $previousUID
214
	 * @return int
215
	 */
216
	public function saveDraft(IMessage $message, $previousUID) {
217
		// build mime body
218
		$from = new Horde_Mail_Rfc822_Address($message->getFrom());
219
		$from->personal = $this->getName();
220
		$headers = [
221
			'From' => $from,
222
			'To' => $message->getToList(),
223
			'Cc' => $message->getCCList(),
224
			'Bcc' => $message->getBCCList(),
225
			'Subject' => $message->getSubject(),
226
			'Date' => Horde_Mime_Headers_Date::create(),
227
		];
228
229
		$mail = new Horde_Mime_Mail();
230
		$mail->addHeaders($headers);
231
		$body = new Horde_Mime_Part();
232
		$body->setType('text/plain');
233
		$body->setContents($message->getContent());
234
		$mail->setBasePart($body);
235
236
		// create transport and save message
237
		// save the message in the drafts folder
238
		$draftsFolder = $this->getDraftsFolder();
239
		/** @var resource $raw */
240
		$raw = $mail->getRaw();
241
		$newUid = $draftsFolder->saveDraft(stream_get_contents($raw));
242
243
		// delete old version if one exists
244
		if (!is_null($previousUID)) {
245
			$draftsFolder->setMessageFlag($previousUID, \Horde_Imap_Client::FLAG_DELETED,
246
				true);
247
			$this->deleteDraft($previousUID);
248
		}
249
250
		return $newUid;
251
	}
252
253
	/**
254
	 * @param string $mailBox
255
	 */
256 12
	public function deleteMailbox($mailBox) {
257 12
		if ($mailBox instanceof Mailbox) {
258
			$mailBox = $mailBox->getFolderId();
259
		}
260 12
		$conn = $this->getImapConnection();
261 12
		$conn->deleteMailbox($mailBox);
262 3
		$this->mailboxes = null;
263 3
	}
264
265
	/**
266
	 * Lists mailboxes (folders) for this account.
267
	 *
268
	 * Lists mailboxes and also queries the server for their 'special use',
269
	 * eg. inbox, sent, trash, etc
270
	 *
271
	 * @param string $pattern Pattern to match mailboxes against. All by default.
272
	 * @return Mailbox[]
273
	 */
274 6
	protected function listMailboxes($pattern = '*') {
275
		// open the imap connection
276 6
		$conn = $this->getImapConnection();
277
278
		// if successful -> get all folders of that account
279 6
		$mailBoxes = $conn->listMailboxes($pattern, Horde_Imap_Client::MBOX_ALL,
280
			[
281 6
			'delimiter' => true,
282 6
			'attributes' => true,
283 6
			'special_use' => true,
284
			'sort' => true
285 6
		]);
286
287 6
		$mailboxes = [];
288 6
		foreach ($mailBoxes as $mailbox) {
289 6
			$mailboxes[] = new Mailbox($conn, $mailbox['mailbox'],
290 6
				$mailbox['attributes'], $mailbox['delimiter']);
291 6
			if ($mailbox['mailbox']->utf8 === 'INBOX') {
292 6
				$mailboxes[] = new SearchMailbox($conn, $mailbox['mailbox'],
293 6
					$mailbox['attributes'], $mailbox['delimiter']);
294 6
			}
295 6
		}
296
297 6
		return $mailboxes;
298
	}
299
300
	/**
301
	 * @param string $folderId
302
	 * @return \OCA\Mail\Mailbox
303
	 */
304 12
	public function getMailbox($folderId) {
305 12
		$conn = $this->getImapConnection();
306 12
		$parts = explode('/', $folderId);
307 12
		if (count($parts) > 1 && $parts[1] === 'FLAGGED') {
308
			$mailbox = new Horde_Imap_Client_Mailbox($parts[0]);
309
			return new SearchMailbox($conn, $mailbox, []);
310
		}
311 12
		$mailbox = new Horde_Imap_Client_Mailbox($folderId);
312 12
		return new Mailbox($conn, $mailbox, []);
313
	}
314
315
	/**
316
	 * Get a list of all mailboxes in this account
317
	 *
318
	 * @return Mailbox[]
319
	 */
320 7
	public function getMailboxes() {
321 7
		if ($this->mailboxes === null) {
322 6
			$this->mailboxes = $this->listMailboxes();
323 6
			$this->sortMailboxes();
324 6
			$this->localizeSpecialMailboxes();
325 6
		}
326
327 7
		return $this->mailboxes;
328
	}
329
330
	/**
331
	 * @return array
332
	 */
333 3
	public function getListArray() {
334
335 3
		$folders = [];
336 3
		$mailBoxes = $this->getMailboxes();
337
		$mailBoxNames = array_map(function($mb) {
338
			/** @var Mailbox $mb */
339 3
			return $mb->getFolderId();
340
		}, array_filter($mailBoxes, function($mb) {
341
			/** @var Mailbox $mb */
342 3
			return (!$mb instanceof SearchMailbox) && (!in_array('\noselect', $mb->attributes()));
343 3
		}));
344
345 3
		$status = $this->getImapConnection()->status($mailBoxNames);
346 3
		foreach ($mailBoxes as $mailbox) {
347 3
			$s = isset($status[$mailbox->getFolderId()]) ? $status[$mailbox->getFolderId()] : null;
348 3
			$folders[] = $mailbox->getListArray($this->getId(), $s);
349 3
		}
350 3
		$delimiter = reset($folders)['delimiter'];
351
		return [
352 3
			'id' => $this->getId(),
353 3
			'email' => $this->getEMailAddress(),
354 3
			'folders' => array_values($folders),
355 3
			'specialFolders' => $this->getSpecialFoldersIds(),
356 3
			'delimiter' => $delimiter,
357 3
		];
358
	}
359
360
	/**
361
	 * @return Horde_Mail_Transport
362
	 */
363
	public function createTransport() {
364
		$transport = $this->config->getSystemValue('app.mail.transport', 'smtp');
365
		if ($transport === 'php-mail') {
366
			return new Horde_Mail_Transport_Mail();
367
		}
368
369
		$password = $this->account->getOutboundPassword();
370
		$password = $this->crypto->decrypt($password);
371
		$params = [
372
			'host' => $this->account->getOutboundHost(),
373
			'password' => $password,
374
			'port' => $this->account->getOutboundPort(),
375
			'username' => $this->account->getOutboundUser(),
376
			'secure' => $this->convertSslMode($this->account->getOutboundSslMode()),
377
			'timeout' => 2
378
		];
379
		return new Horde_Mail_Transport_Smtphorde($params);
380
	}
381
382
	/**
383
	 * Lists special use folders for this account.
384
	 *
385
	 * The special uses returned are the "best" one for each special role,
386
	 * picked amongst the ones returned by the server, as well
387
	 * as the one guessed by our code.
388
	 *
389
	 * @param bool $base64_encode
390
	 * @return array In the form [<special use>=><folder id>, ...]
391
	 */
392 6
	public function getSpecialFoldersIds($base64_encode=true) {
393 6
		$folderRoles = ['inbox', 'sent', 'drafts', 'trash', 'archive', 'junk', 'flagged', 'all'];
394 6
		$specialFoldersIds = [];
395
396 6
		foreach ($folderRoles as $role) {
397 6
			$folders = $this->getSpecialFolder($role, true);
398 6
			$specialFoldersIds[$role] = (count($folders) === 0) ? null : $folders[0]->getFolderId();
399 6
			if ($specialFoldersIds[$role] !== null && $base64_encode === true) {
400 3
				$specialFoldersIds[$role] = base64_encode($specialFoldersIds[$role]);
401 3
			}
402 6
		}
403 6
		return $specialFoldersIds;
404
	}
405
406
	/**
407
	 * Get the "drafts" mailbox
408
	 *
409
	 * @return Mailbox The best candidate for the "drafts" inbox
410
	 */
411 View Code Duplication
	public function getDraftsFolder() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
412
		// check for existence
413
		$draftsFolder = $this->getSpecialFolder('drafts', true);
414
		if (count($draftsFolder) === 0) {
415
			// drafts folder does not exist - let's create one
416
			$conn = $this->getImapConnection();
417
			// TODO: also search for translated drafts mailboxes
418
			$conn->createMailbox('Drafts', [
419
				'special_use' => ['drafts'],
420
			]);
421
			return $this->guessBestMailBox($this->listMailboxes('Drafts'));
422
		}
423
		return $draftsFolder[0];
424
	}
425
426
	/**
427
	 * @return IMailBox
428
	 */
429
	public function getInbox() {
430
		$folders = $this->getSpecialFolder('inbox', false);
431
		return count($folders) > 0 ? $folders[0] : null;
432
	}
433
434
	/**
435
	 * Get the "sent mail" mailbox
436
	 *
437
	 * @return Mailbox The best candidate for the "sent mail" inbox
438
	 */
439 View Code Duplication
	public function getSentFolder() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
440
		//check for existence
441
		$sentFolders = $this->getSpecialFolder('sent', true);
442
		if (count($sentFolders) === 0) {
443
			//sent folder does not exist - let's create one
444
			$conn = $this->getImapConnection();
445
			//TODO: also search for translated sent mailboxes
446
			$conn->createMailbox('Sent', [
447
				'special_use' => ['sent'],
448
			]);
449
			return $this->guessBestMailBox($this->listMailboxes('Sent'));
450
		}
451
		return $sentFolders[0];
452
	}
453
454
	/**
455
	 * @param string $sourceFolderId
456
	 * @param int $messageId
457
	 */
458
	public function deleteMessage($sourceFolderId, $messageId) {
459
		$mb = $this->getMailbox($sourceFolderId);
460
		$hordeSourceMailBox = $mb->getHordeMailBox();
461
		// by default we will create a 'Trash' folder if no trash is found
462
		$trashId = "Trash";
463
		$createTrash = true;
464
465
		$trashFolders = $this->getSpecialFolder('trash', true);
466
467
		if (count($trashFolders) !== 0) {
468
			$trashId = $trashFolders[0]->getFolderId();
469
			$createTrash = false;
470
		} else {
471
			// no trash -> guess
472
			$trashes = array_filter($this->getMailboxes(), function($box) {
473
				/**
474
				 * @var Mailbox $box
475
				 */
476
				return (stripos($box->getDisplayName(), 'trash') !== false);
477
			});
478
			if (!empty($trashes)) {
479
				$trashId = array_values($trashes);
480
				$trashId = $trashId[0]->getFolderId();
481
				$createTrash = false;
482
			}
483
		}
484
485
		$hordeMessageIds = new Horde_Imap_Client_Ids($messageId);
486
		$hordeTrashMailBox = new Horde_Imap_Client_Mailbox($trashId);
487
488
		if ($sourceFolderId === $trashId) {
489
			$this->getImapConnection()->expunge($hordeSourceMailBox,
490
				array('ids' => $hordeMessageIds, 'delete' => true));
491
492
			\OC::$server->getLogger()->info("Message expunged: {message} from mailbox {mailbox}",
493
				array('message' => $messageId, 'mailbox' => $sourceFolderId));
494
		} else {
495
			$this->getImapConnection()->copy($hordeSourceMailBox, $hordeTrashMailBox,
496
				array('create' => $createTrash, 'move' => true, 'ids' => $hordeMessageIds));
497
498
			\OC::$server->getLogger()->info("Message moved to trash: {message} from mailbox {mailbox}",
499
				array('message' => $messageId, 'mailbox' => $sourceFolderId, 'app' => 'mail'));
500
		}
501
	}
502
503
	/**
504
	 * 
505
	 * @param int $messageId
506
	 */
507
	public function deleteDraft($messageId) {
0 ignored issues
show
Unused Code introduced by
The parameter $messageId is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
508
		$draftsFolder = $this->getDraftsFolder();
509
		
510
		$draftsMailBox = new \Horde_Imap_Client_Mailbox($draftsFolder->getFolderId(), false);
511
		$this->getImapConnection()->expunge($draftsMailBox);
512
	}
513
514
	/**
515
	 * Get 'best' mailbox guess
516
	 *
517
	 * For now the best candidate is the one with
518
	 * the most messages in it.
519
	 *
520
	 * @param array $folders
521
	 * @return Mailbox
522
	 */
523
	protected function guessBestMailBox(array $folders) {
524
		$maxMessages = -1;
525
		$bestGuess = null;
526
		foreach ($folders as $folder) {
527
			/** @var Mailbox $folder */
528
			if ($folder->getTotalMessages() > $maxMessages) {
529
				$maxMessages = $folder->getTotalMessages();
530
				$bestGuess = $folder;
531
			}
532
		}
533
		return $bestGuess;
534
	}
535
536
	/**
537
	 * Get mailbox(es) that have the given special use role
538
	 *
539
	 * With this method we can get a list of all mailboxes that have been
540
	 * determined to have a specific special use role. It can also return
541
	 * the best candidate for this role, for situations where we want
542
	 * one single folder.
543
	 *
544
	 * @param string $role Special role of the folder we want to get ('sent', 'inbox', etc.)
545
	 * @param bool $guessBest If set to true, return only the folder with the most messages in it
546
	 *
547
	 * @return Mailbox[] if $guessBest is false, or Mailbox if $guessBest is true. Empty [] if no match.
548
	 */
549 6
	protected function getSpecialFolder($role, $guessBest=true) {
550
551 6
		$specialFolders = [];
552 6
		foreach ($this->getMailboxes() as $mailbox) {
553 6
			if ($role === $mailbox->getSpecialRole()) {
554 6
				$specialFolders[] = $mailbox;
555 6
			}
556 6
		}
557
558 6
		if ($guessBest === true && count($specialFolders) > 1) {
559
			return [$this->guessBestMailBox($specialFolders)];
0 ignored issues
show
Best Practice introduced by
The expression return array($this->gues...lBox($specialFolders)); seems to be an array, but some of its elements' types (null) are incompatible with the return type documented by OCA\Mail\Account::getSpecialFolder of type OCA\Mail\Mailbox[].

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 new Author('Johannes');
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return '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...
560
		} else {
561 6
			return $specialFolders;
562
		}
563
	}
564
565
	/**
566
	 *  Localizes the name of the special use folders
567
	 *
568
	 *  The display name of the best candidate folder for each special use
569
	 *  is localized to the user's language
570
	 */
571 6
	protected function localizeSpecialMailboxes() {
572
573 6
		$l = \OC::$server->getL10N('mail');
574
		$map = [
575
			// TRANSLATORS: translated mail box name
576 6
			'inbox'   => $l->t('Inbox'),
577
			// TRANSLATORS: translated mail box name
578 6
			'sent'    => $l->t('Sent'),
579
			// TRANSLATORS: translated mail box name
580 6
			'drafts'  => $l->t('Drafts'),
581
			// TRANSLATORS: translated mail box name
582 6
			'archive' => $l->t('Archive'),
583
			// TRANSLATORS: translated mail box name
584 6
			'trash'   => $l->t('Trash'),
585
			// TRANSLATORS: translated mail box name
586 6
			'junk'    => $l->t('Junk'),
587
			// TRANSLATORS: translated mail box name
588 6
			'all'     => $l->t('All'),
589
			// TRANSLATORS: translated mail box name
590 6
			'flagged' => $l->t('Favorites'),
591 6
		];
592 6
		$mailboxes = $this->getMailboxes();
593 6
		$specialIds = $this->getSpecialFoldersIds(false);
594 6
		foreach ($mailboxes as $i => $mailbox) {
595 6
			if (in_array($mailbox->getFolderId(), $specialIds) === true) {
596 6
				if (isset($map[$mailbox->getSpecialRole()])) {
597 6
					$translatedDisplayName = $map[$mailbox->getSpecialRole()];
598 6
					$mailboxes[$i]->setDisplayName((string)$translatedDisplayName);
599 6
				}
600 6
			}
601 6
		}
602 6
	}
603
604
	/**
605
	 * Sort mailboxes
606
	 *
607
	 * Sort the array of mailboxes with
608
	 *  - special use folders coming first in this order: all, inbox, flagged, drafts, sent, archive, junk, trash
609
	 *  - 'normal' folders coming after that, sorted alphabetically
610
	 */
611 6
	protected function sortMailboxes() {
612
613 6
		$mailboxes = $this->getMailboxes();
614
		usort($mailboxes, function($a, $b) {
615
			/**
616
			 * @var Mailbox $a
617
			 * @var Mailbox $b
618
			 */
619 6
			$roleA = $a->getSpecialRole();
620 6
			$roleB = $b->getSpecialRole();
621
			$specialRolesOrder = [
622 6
				'all'     => 0,
623 6
				'inbox'   => 1,
624 6
				'flagged' => 2,
625 6
				'drafts'  => 3,
626 6
				'sent'    => 4,
627 6
				'archive' => 5,
628 6
				'junk'    => 6,
629 6
				'trash'   => 7,
630 6
			];
631
			// if there is a flag unknown to us, we ignore it for sorting :
632
			// the folder will be sorted by name like any other 'normal' folder
633 6
			if (array_key_exists($roleA, $specialRolesOrder) === false) {
634 6
				$roleA = null;
635 6
			}
636 6
			if (array_key_exists($roleB, $specialRolesOrder) === false) {
637 6
				$roleB = null;
638 6
			}
639
640 6
			if ($roleA === null && $roleB !== null) {
641 6
				return 1;
642 6
			} elseif ($roleA !== null && $roleB === null) {
643 6
				return -1;
644 6
			} elseif ($roleA !== null && $roleB !== null) {
645 6
				if ($roleA === $roleB) {
646
					return strcasecmp($a->getdisplayName(), $b->getDisplayName());
647
				} else {
648 6
					return $specialRolesOrder[$roleA] - $specialRolesOrder[$roleB];
649
				}
650
			}
651
			// we get here if $roleA === null && $roleB === null
652 6
			return strcasecmp($a->getDisplayName(), $b->getDisplayName());
653 6
		});
654
655 6
		$this->mailboxes = $mailboxes;
656 6
	}
657
658
	/**
659
	 * Convert special security mode values into Horde parameters
660
	 *
661
	 * @param string $sslMode
662
	 * @return false|string
663
	 */
664
	protected function convertSslMode($sslMode) {
665
		switch ($sslMode) {
666
			case 'none':
667
				return false;
668
		}
669
		return $sslMode;
670
	}
671
672
	/**
673
	 * @param $query
674
	 * @return array
675
	 */
676 4
	public function getChangedMailboxes($query) {
677 4
		$imp = $this->getImapConnection();
678 4
		$allBoxes = $this->getMailboxes();
679 4
		$allBoxesMap = [];
680 4
		foreach ($allBoxes as $mb) {
681 4
			$allBoxesMap[$mb->getFolderId()] = $mb;
682 4
		}
683
684
		// filter non existing mailboxes
685
		$mailBoxNames = array_filter(array_keys($query), function($folderId) use ($allBoxesMap) {
686 4
			return isset($allBoxesMap[$folderId]);
687 4
		});
688
689 4
		$status = $imp->status($mailBoxNames);
690
691
		// filter for changed mailboxes
692 4
		$changedBoxes = [];
693 4
		foreach ($status as $folderId => $s) {
694 3
			$uidValidity = $query[$folderId]['uidvalidity'];
695 3
			$uidNext = $query[$folderId]['uidnext'];
696
697 3
			if ($uidNext === $s['uidnext'] &&
698 3
				$uidValidity === $s['uidvalidity']) {
699 3
				continue;
700
			}
701
			// get unread messages
702 3
			if (isset($allBoxesMap[$folderId])) {
703
				/** @var Mailbox $m */
704 3
				$m = $allBoxesMap[$folderId];
705 3
				$role = $m->getSpecialRole();
706 3
				if (is_null($role) || $role === 'inbox') {
707 3
					$newMessages = $m->getMessagesSince($uidNext, $s['uidnext']);
708
					// only trigger updates in case new messages are actually available
709 3
					if (!empty($newMessages)) {
710 3
						$changedBoxes[$folderId] = $m->getListArray($this->getId(), $s);
711 3
						$changedBoxes[$folderId]['messages'] = $newMessages;
712 3
						$newUnreadMessages = array_filter($newMessages, function($m) {
713 3
							return $m['flags']['unseen'];
714 3
						});
715 3
						$changedBoxes[$folderId]['newUnReadCounter'] = count($newUnreadMessages);
716 3
					}
717 3
				}
718 3
			}
719 4
		}
720
721 4
		return $changedBoxes;
722
	}
723
724
	/**
725
	 * @throws \Horde_Imap_Client_Exception
726
	 */
727
	public function reconnect() {
728
		$this->mailboxes = null;
729
		if ($this->client) {
730
			$this->client->close();
731
			$this->client = null;
732
		}
733
		$this->getImapConnection();
734
	}
735
736
	/**
737
	 * @return array
738
	 */
739
	public function getConfiguration() {
740
		return $this->account->toJson();
741
	}
742
743
	/**
744
	 * @return string|Horde_Mail_Rfc822_List
745
	 */
746
	public function getEmail() {
747
		return $this->account->getEmail();
748
	}
749
750
	public function testConnectivity() {
751
		// connect to imap
752
		$this->getImapConnection();
753
754
		// connect to smtp
755
		$smtp = $this->createTransport();
756
		if ($smtp instanceof Horde_Mail_Transport_Smtphorde) {
0 ignored issues
show
Bug introduced by
The class Horde_Mail_Transport_Smtphorde 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...
757
			$smtp->getSMTPObject();
758
		}
759
	}
760
761
	/**
762
	 * Factory method for creating new messages
763
	 *
764
	 * @return IMessage
765
	 */
766
	public function newMessage() {
767
		return new Message();
768
	}
769
770
	/**
771
	 * Factory method for creating new reply messages
772
	 *
773
	 * @return ReplyMessage
774
	 */
775
	public function newReplyMessage() {
776
		return new ReplyMessage();
777
	}
778
779
}
780