Completed
Pull Request — master (#1276)
by Christoph
05:09
created

Account::reconnect()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6
Metric Value
dl 0
loc 8
ccs 0
cts 8
cp 0
rs 9.4285
cc 2
eloc 6
nc 2
nop 0
crap 6
1
<?php
2
3
/**
4
 * Copyright (c) 2012 Bart Visscher <[email protected]>
5
 * Copyright (c) 2014 Thomas Müller <[email protected]>
6
 * Copyright (c) 2015 Christoph Wurst <[email protected]>
7
 *
8
 * This file is licensed under the Affero General Public License version 3 or
9
 * later.
10
 * See the COPYING-README file.
11
 */
12
13
namespace OCA\Mail;
14
15
use Horde_Imap_Client_Ids;
16
use Horde_Imap_Client_Mailbox;
17
use Horde_Imap_Client_Socket;
18
use Horde_Imap_Client;
19
use Horde_Mail_Rfc822_Address;
20
use Horde_Mail_Transport;
21
use Horde_Mail_Transport_Mail;
22
use Horde_Mail_Transport_Smtphorde;
23
use Horde_Mime_Headers_Date;
24
use Horde_Mime_Mail;
25
use Horde_Mime_Part;
26
use OCA\Mail\Cache\Cache;
27
use OCA\Mail\Db\MailAccount;
28
use OCA\Mail\Model\IMessage;
29
use OCA\Mail\Model\Message;
30
use OCA\Mail\Model\ReplyMessage;
31
use OCA\Mail\Service\AutoCompletion\AddressCollector;
32
use OCA\Mail\Service\IAccount;
33
use OCA\Mail\Service\IMailBox;
34
use OCP\IConfig;
35
use OCP\ICacheFactory;
36
use OCP\Security\ICrypto;
37
38
class Account implements IAccount {
39
40
	/** @var MailAccount */
41
	private $account;
42
43
	/** @var Mailbox[]|null */
44
	private $mailboxes;
45
46
	/** @var Horde_Imap_Client_Socket */
47
	private $client;
48
49
	/** @var ICrypto */
50
	private $crypto;
51
52
	/** @var IConfig */
53
	private $config;
54
55
	/** @var ICacheFactory */
56
	private $memcacheFactory;
57
58
	/** @var AddressCollector */
59
	private $addressCollector;
60
61
	/**
62
	 * @param MailAccount $account
63
	 */
64 3
	public function __construct(MailAccount $account) {
65 3
		$this->account = $account;
66 3
		$this->mailboxes = null;
67 3
		$this->crypto = \OC::$server->getCrypto();
68 3
		$this->config = \OC::$server->getConfig();
69 3
		$this->memcacheFactory = \OC::$server->getMemcacheFactory();
70 3
		$this->addressCollector = \OC::$server->query('OCA\Mail\Service\AutoCompletion\AddressCollector');
71 3
	}
72
73
	/**
74
	 * @return int
75
	 */
76 6
	public function getId() {
77 6
		return $this->account->getId();
78
	}
79
80
	/**
81
	 * @return string
82
	 */
83 6
	public function getName() {
84 6
		return $this->account->getName();
85
	}
86
87
	/**
88
	 * @return string
89
	 */
90 3
	public function getEMailAddress() {
91 3
		return $this->account->getEmail();
92
	}
93
94
	/**
95
	 * @return Horde_Imap_Client_Socket
96
	 */
97 13
	public function getImapConnection() {
98 13
		if (is_null($this->client)) {
99
			$host = $this->account->getInboundHost();
100
			$user = $this->account->getInboundUser();
101
			$password = $this->account->getInboundPassword();
102
			$password = $this->crypto->decrypt($password);
103
			$port = $this->account->getInboundPort();
104
			$ssl_mode = $this->convertSslMode($this->account->getInboundSslMode());
105
106
			$params = [
107
				'username' => $user,
108
				'password' => $password,
109
				'hostspec' => $host,
110
				'port' => $port,
111
				'secure' => $ssl_mode,
112
				'timeout' => 20,
113
			];
114
			if ($this->config->getSystemValue('app.mail.imaplog.enabled', false)) {
115
				$params['debug'] = $this->config->getSystemValue('datadirectory') . '/horde.log';
116
			}
117
			if ($this->config->getSystemValue('app.mail.server-side-cache.enabled', false)) {
118
				if ($this->memcacheFactory->isAvailable()) {
119
					$params['cache'] = [
120
						'backend' => new Cache(array(
121
							'cacheob' => $this->memcacheFactory
122
								->create(md5($this->getId() . $this->getEMailAddress()))
123
						))];
124
				}
125
			}
126
			$this->client = new \Horde_Imap_Client_Socket($params);
127
			$this->client->login();
128
		}
129 13
		return $this->client;
130
	}
131
132
	/**
133
	 * @param string $mailBox
134
	 * @return Mailbox
135
	 */
136 12
	public function createMailbox($mailBox) {
137 12
		$conn = $this->getImapConnection();
138 12
		$conn->createMailbox($mailBox);
139 12
		$this->mailboxes = null;
140
141 12
		return $this->getMailbox($mailBox);
142
	}
143
144
	/**
145
	 * Send a new message or reply to an existing message
146
	 *
147
	 * @param IMessage $message
148
	 * @param int|null $draftUID
149
	 */
150
	public function sendMessage(IMessage $message, $draftUID) {
151
		// build mime body
152
		$from = new Horde_Mail_Rfc822_Address($message->getFrom());
153
		$from->personal = $this->getName();
154
		$headers = [
155
			'From' => $from,
156
			'To' => $message->getToList(),
157
			'Cc' => $message->getCCList(),
158
			'Bcc' => $message->getBCCList(),
159
			'Subject' => $message->getSubject(),
160
		];
161
162
		if (!is_null($message->getRepliedMessage())) {
163
			$headers['In-Reply-To'] = $message->getRepliedMessage()->getMessageId();
164
		}
165
166
		$mail = new Horde_Mime_Mail();
167
		$mail->addHeaders($headers);
168
		$mail->setBody($message->getContent());
169
170
		// Append attachments
171
		foreach ($message->getAttachments() as $attachment) {
172
			$mail->addMimePart($attachment);
173
		}
174
175
		// Send the message
176
		$transport = $this->createTransport();
177
		$mail->send($transport);
178
179
		// Save the message in the sent folder
180
		$sentFolder = $this->getSentFolder();
181
		/** @var resource $raw */
182
		$raw = stream_get_contents($mail->getRaw());
183
		$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...
184
			Horde_Imap_Client::FLAG_SEEN
185
		]);
186
187
		// Delete draft if one exists
188
		if (!is_null($draftUID)) {
189
			$draftsFolder = $this->getDraftsFolder();
190
			$draftsFolder->setMessageFlag($draftUID, Horde_Imap_Client::FLAG_DELETED, true);
191
			$this->deleteDraft($draftUID);
192
		}
193
194
		// Collect mail addresses
195
		$addresses = array_merge($message->getToList(), $message->getCCList(), $message->getBCCList());
196
		$this->addressCollector->addAddresses($addresses);
197
	}
198
199
	/**
200
	 * @param IMessage $message
201
	 * @param int|null $previousUID
202
	 * @return int
203
	 */
204
	public function saveDraft(IMessage $message, $previousUID) {
205
		// build mime body
206
		$from = new Horde_Mail_Rfc822_Address($message->getFrom());
207
		$from->personal = $this->getName();
208
		$headers = [
209
			'From' => $from,
210
			'To' => $message->getToList(),
211
			'Cc' => $message->getCCList(),
212
			'Bcc' => $message->getBCCList(),
213
			'Subject' => $message->getSubject(),
214
			'Date' => Horde_Mime_Headers_Date::create(),
215
		];
216
217
		$mail = new Horde_Mime_Mail();
218
		$mail->addHeaders($headers);
219
		$body = new Horde_Mime_Part();
220
		$body->setType('text/plain');
221
		$body->setContents($message->getContent());
222
		$mail->setBasePart($body);
223
224
		// create transport and save message
225
		// save the message in the drafts folder
226
		$draftsFolder = $this->getDraftsFolder();
227
		/** @var resource $raw */
228
		$raw = $mail->getRaw();
229
		$newUid = $draftsFolder->saveDraft(stream_get_contents($raw));
230
231
		// delete old version if one exists
232
		if (!is_null($previousUID)) {
233
			$draftsFolder->setMessageFlag($previousUID, \Horde_Imap_Client::FLAG_DELETED,
234
				true);
235
			$this->deleteDraft($previousUID);
236
		}
237
238
		return $newUid;
239
	}
240
241
	/**
242
	 * @param string $mailBox
243
	 */
244 12
	public function deleteMailbox($mailBox) {
245 12
		if ($mailBox instanceof Mailbox) {
246
			$mailBox = $mailBox->getFolderId();
247
		}
248 12
		$conn = $this->getImapConnection();
249 12
		$conn->deleteMailbox($mailBox);
250 3
		$this->mailboxes = null;
251 3
	}
252
253
	/**
254
	 * Lists mailboxes (folders) for this account.
255
	 *
256
	 * Lists mailboxes and also queries the server for their 'special use',
257
	 * eg. inbox, sent, trash, etc
258
	 *
259
	 * @param string $pattern Pattern to match mailboxes against. All by default.
260
	 * @return Mailbox[]
261
	 */
262 6
	protected function listMailboxes($pattern = '*') {
263
		// open the imap connection
264 6
		$conn = $this->getImapConnection();
265
266
		// if successful -> get all folders of that account
267 6
		$mailBoxes = $conn->listMailboxes($pattern, Horde_Imap_Client::MBOX_ALL,
268
			[
269 6
			'delimiter' => true,
270 6
			'attributes' => true,
271 6
			'special_use' => true,
272
			'sort' => true
273 6
		]);
274
275 6
		$mailboxes = [];
276 6
		foreach ($mailBoxes as $mailbox) {
277 6
			$mailboxes[] = new Mailbox($conn, $mailbox['mailbox'],
278 6
				$mailbox['attributes'], $mailbox['delimiter']);
279 6
			if ($mailbox['mailbox']->utf8 === 'INBOX') {
280 6
				$mailboxes[] = new SearchMailbox($conn, $mailbox['mailbox'],
281 6
					$mailbox['attributes'], $mailbox['delimiter']);
282 6
			}
283 6
		}
284
285 6
		return $mailboxes;
286
	}
287
288
	/**
289
	 * @param string $folderId
290
	 * @return \OCA\Mail\Mailbox
291
	 */
292 12
	public function getMailbox($folderId) {
293 12
		$conn = $this->getImapConnection();
294 12
		$parts = explode('/', $folderId);
295 12
		if (count($parts) > 1 && $parts[1] === 'FLAGGED') {
296
			$mailbox = new Horde_Imap_Client_Mailbox($parts[0]);
297
			return new SearchMailbox($conn, $mailbox, []);
298
		}
299 12
		$mailbox = new Horde_Imap_Client_Mailbox($folderId);
300 12
		return new Mailbox($conn, $mailbox, []);
301
	}
302
303
	/**
304
	 * Get a list of all mailboxes in this account
305
	 *
306
	 * @return Mailbox[]
307
	 */
308 7
	public function getMailboxes() {
309 7
		if ($this->mailboxes === null) {
310 6
			$this->mailboxes = $this->listMailboxes();
311 6
			$this->sortMailboxes();
312 6
			$this->localizeSpecialMailboxes();
313 6
		}
314
315 7
		return $this->mailboxes;
316
	}
317
318
	/**
319
	 * @return array
320
	 */
321 3
	public function getListArray() {
322
323 3
		$folders = [];
324 3
		$mailBoxes = $this->getMailboxes();
325
		$mailBoxNames = array_map(function($mb) {
326
			/** @var Mailbox $mb */
327 3
			return $mb->getFolderId();
328
		}, array_filter($mailBoxes, function($mb) {
329
			/** @var Mailbox $mb */
330 3
			return (!$mb instanceof SearchMailbox) && (!in_array('\noselect', $mb->attributes()));
331 3
		}));
332
333 3
		$status = $this->getImapConnection()->status($mailBoxNames);
334 3
		foreach ($mailBoxes as $mailbox) {
335 3
			$s = isset($status[$mailbox->getFolderId()]) ? $status[$mailbox->getFolderId()] : null;
336 3
			$folders[] = $mailbox->getListArray($this->getId(), $s);
337 3
		}
338 3
		$delimiter = reset($folders)['delimiter'];
339
		return [
340 3
			'id' => $this->getId(),
341 3
			'email' => $this->getEMailAddress(),
342 3
			'folders' => array_values($folders),
343 3
			'specialFolders' => $this->getSpecialFoldersIds(),
344 3
			'delimiter' => $delimiter,
345 3
		];
346
	}
347
348
	/**
349
	 * @return Horde_Mail_Transport
350
	 */
351
	public function createTransport() {
352
		$transport = $this->config->getSystemValue('app.mail.transport', 'smtp');
353
		if ($transport === 'php-mail') {
354
			return new Horde_Mail_Transport_Mail();
355
		}
356
357
		$password = $this->account->getOutboundPassword();
358
		$password = $this->crypto->decrypt($password);
359
		$params = [
360
			'host' => $this->account->getOutboundHost(),
361
			'password' => $password,
362
			'port' => $this->account->getOutboundPort(),
363
			'username' => $this->account->getOutboundUser(),
364
			'secure' => $this->convertSslMode($this->account->getOutboundSslMode()),
365
			'timeout' => 2
366
		];
367
		return new Horde_Mail_Transport_Smtphorde($params);
368
	}
369
370
	/**
371
	 * Lists special use folders for this account.
372
	 *
373
	 * The special uses returned are the "best" one for each special role,
374
	 * picked amongst the ones returned by the server, as well
375
	 * as the one guessed by our code.
376
	 *
377
	 * @param bool $base64_encode
378
	 * @return array In the form [<special use>=><folder id>, ...]
379
	 */
380 6
	public function getSpecialFoldersIds($base64_encode=true) {
381 6
		$folderRoles = ['inbox', 'sent', 'drafts', 'trash', 'archive', 'junk', 'flagged', 'all'];
382 6
		$specialFoldersIds = [];
383
384 6
		foreach ($folderRoles as $role) {
385 6
			$folders = $this->getSpecialFolder($role, true);
386 6
			$specialFoldersIds[$role] = (count($folders) === 0) ? null : $folders[0]->getFolderId();
387 6
			if ($specialFoldersIds[$role] !== null && $base64_encode === true) {
388 3
				$specialFoldersIds[$role] = base64_encode($specialFoldersIds[$role]);
389 3
			}
390 6
		}
391 6
		return $specialFoldersIds;
392
	}
393
394
	/**
395
	 * Get the "drafts" mailbox
396
	 *
397
	 * @return Mailbox The best candidate for the "drafts" inbox
398
	 */
399 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...
400
		// check for existence
401
		$draftsFolder = $this->getSpecialFolder('drafts', true);
402
		if (count($draftsFolder) === 0) {
403
			// drafts folder does not exist - let's create one
404
			$conn = $this->getImapConnection();
405
			// TODO: also search for translated drafts mailboxes
406
			$conn->createMailbox('Drafts', [
407
				'special_use' => ['drafts'],
408
			]);
409
			return $this->guessBestMailBox($this->listMailboxes('Drafts'));
410
		}
411
		return $draftsFolder[0];
412
	}
413
414
	/**
415
	 * @return IMailBox
416
	 */
417
	public function getInbox() {
418
		$folders = $this->getSpecialFolder('inbox', false);
419
		return count($folders) > 0 ? $folders[0] : null;
420
	}
421
422
	/**
423
	 * Get the "sent mail" mailbox
424
	 *
425
	 * @return Mailbox The best candidate for the "sent mail" inbox
426
	 */
427 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...
428
		//check for existence
429
		$sentFolders = $this->getSpecialFolder('sent', true);
430
		if (count($sentFolders) === 0) {
431
			//sent folder does not exist - let's create one
432
			$conn = $this->getImapConnection();
433
			//TODO: also search for translated sent mailboxes
434
			$conn->createMailbox('Sent', [
435
				'special_use' => ['sent'],
436
			]);
437
			return $this->guessBestMailBox($this->listMailboxes('Sent'));
438
		}
439
		return $sentFolders[0];
440
	}
441
442
	/**
443
	 * @param string $sourceFolderId
444
	 * @param int $messageId
445
	 */
446
	public function deleteMessage($sourceFolderId, $messageId) {
447
		$mb = $this->getMailbox($sourceFolderId);
448
		$hordeSourceMailBox = $mb->getHordeMailBox();
449
		// by default we will create a 'Trash' folder if no trash is found
450
		$trashId = "Trash";
451
		$createTrash = true;
452
453
		$trashFolders = $this->getSpecialFolder('trash', true);
454
455
		if (count($trashFolders) !== 0) {
456
			$trashId = $trashFolders[0]->getFolderId();
457
			$createTrash = false;
458
		} else {
459
			// no trash -> guess
460
			$trashes = array_filter($this->getMailboxes(), function($box) {
461
				/**
462
				 * @var Mailbox $box
463
				 */
464
				return (stripos($box->getDisplayName(), 'trash') !== false);
465
			});
466
			if (!empty($trashes)) {
467
				$trashId = array_values($trashes);
468
				$trashId = $trashId[0]->getFolderId();
469
				$createTrash = false;
470
			}
471
		}
472
473
		$hordeMessageIds = new Horde_Imap_Client_Ids($messageId);
474
		$hordeTrashMailBox = new Horde_Imap_Client_Mailbox($trashId);
475
476
		if ($sourceFolderId === $trashId) {
477
			$this->getImapConnection()->expunge($hordeSourceMailBox,
478
				array('ids' => $hordeMessageIds, 'delete' => true));
479
480
			\OC::$server->getLogger()->info("Message expunged: {message} from mailbox {mailbox}",
481
				array('message' => $messageId, 'mailbox' => $sourceFolderId));
482
		} else {
483
			$this->getImapConnection()->copy($hordeSourceMailBox, $hordeTrashMailBox,
484
				array('create' => $createTrash, 'move' => true, 'ids' => $hordeMessageIds));
485
486
			\OC::$server->getLogger()->info("Message moved to trash: {message} from mailbox {mailbox}",
487
				array('message' => $messageId, 'mailbox' => $sourceFolderId, 'app' => 'mail'));
488
		}
489
	}
490
491
	/**
492
	 * 
493
	 * @param int $messageId
494
	 */
495
	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...
496
		$draftsFolder = $this->getDraftsFolder();
497
		
498
		$draftsMailBox = new \Horde_Imap_Client_Mailbox($draftsFolder->getFolderId(), false);
499
		$this->getImapConnection()->expunge($draftsMailBox);
500
	}
501
502
	/**
503
	 * Get 'best' mailbox guess
504
	 *
505
	 * For now the best candidate is the one with
506
	 * the most messages in it.
507
	 *
508
	 * @param array $folders
509
	 * @return Mailbox
510
	 */
511
	protected function guessBestMailBox(array $folders) {
512
		$maxMessages = -1;
513
		$bestGuess = null;
514
		foreach ($folders as $folder) {
515
			/** @var Mailbox $folder */
516
			if ($folder->getTotalMessages() > $maxMessages) {
517
				$maxMessages = $folder->getTotalMessages();
518
				$bestGuess = $folder;
519
			}
520
		}
521
		return $bestGuess;
522
	}
523
524
	/**
525
	 * Get mailbox(es) that have the given special use role
526
	 *
527
	 * With this method we can get a list of all mailboxes that have been
528
	 * determined to have a specific special use role. It can also return
529
	 * the best candidate for this role, for situations where we want
530
	 * one single folder.
531
	 *
532
	 * @param string $role Special role of the folder we want to get ('sent', 'inbox', etc.)
533
	 * @param bool $guessBest If set to true, return only the folder with the most messages in it
534
	 *
535
	 * @return Mailbox[] if $guessBest is false, or Mailbox if $guessBest is true. Empty [] if no match.
536
	 */
537 6
	protected function getSpecialFolder($role, $guessBest=true) {
538
539 6
		$specialFolders = [];
540 6
		foreach ($this->getMailboxes() as $mailbox) {
541 6
			if ($role === $mailbox->getSpecialRole()) {
542 6
				$specialFolders[] = $mailbox;
543 6
			}
544 6
		}
545
546 6
		if ($guessBest === true && count($specialFolders) > 1) {
547
			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...
548
		} else {
549 6
			return $specialFolders;
550
		}
551
	}
552
553
	/**
554
	 *  Localizes the name of the special use folders
555
	 *
556
	 *  The display name of the best candidate folder for each special use
557
	 *  is localized to the user's language
558
	 */
559 6
	protected function localizeSpecialMailboxes() {
560
561 6
		$l = \OC::$server->getL10N('mail');
562
		$map = [
563
			// TRANSLATORS: translated mail box name
564 6
			'inbox'   => $l->t('Inbox'),
565
			// TRANSLATORS: translated mail box name
566 6
			'sent'    => $l->t('Sent'),
567
			// TRANSLATORS: translated mail box name
568 6
			'drafts'  => $l->t('Drafts'),
569
			// TRANSLATORS: translated mail box name
570 6
			'archive' => $l->t('Archive'),
571
			// TRANSLATORS: translated mail box name
572 6
			'trash'   => $l->t('Trash'),
573
			// TRANSLATORS: translated mail box name
574 6
			'junk'    => $l->t('Junk'),
575
			// TRANSLATORS: translated mail box name
576 6
			'all'     => $l->t('All'),
577
			// TRANSLATORS: translated mail box name
578 6
			'flagged' => $l->t('Favorites'),
579 6
		];
580 6
		$mailboxes = $this->getMailboxes();
581 6
		$specialIds = $this->getSpecialFoldersIds(false);
582 6
		foreach ($mailboxes as $i => $mailbox) {
583 6
			if (in_array($mailbox->getFolderId(), $specialIds) === true) {
584 6
				if (isset($map[$mailbox->getSpecialRole()])) {
585 6
					$translatedDisplayName = $map[$mailbox->getSpecialRole()];
586 6
					$mailboxes[$i]->setDisplayName((string)$translatedDisplayName);
587 6
				}
588 6
			}
589 6
		}
590 6
	}
591
592
	/**
593
	 * Sort mailboxes
594
	 *
595
	 * Sort the array of mailboxes with
596
	 *  - special use folders coming first in this order: all, inbox, flagged, drafts, sent, archive, junk, trash
597
	 *  - 'normal' folders coming after that, sorted alphabetically
598
	 */
599 6
	protected function sortMailboxes() {
600
601 6
		$mailboxes = $this->getMailboxes();
602
		usort($mailboxes, function($a, $b) {
603
			/**
604
			 * @var Mailbox $a
605
			 * @var Mailbox $b
606
			 */
607 6
			$roleA = $a->getSpecialRole();
608 6
			$roleB = $b->getSpecialRole();
609
			$specialRolesOrder = [
610 6
				'all'     => 0,
611 6
				'inbox'   => 1,
612 6
				'flagged' => 2,
613 6
				'drafts'  => 3,
614 6
				'sent'    => 4,
615 6
				'archive' => 5,
616 6
				'junk'    => 6,
617 6
				'trash'   => 7,
618 6
			];
619
			// if there is a flag unknown to us, we ignore it for sorting :
620
			// the folder will be sorted by name like any other 'normal' folder
621 6
			if (array_key_exists($roleA, $specialRolesOrder) === false) {
622 6
				$roleA = null;
623 6
			}
624 6
			if (array_key_exists($roleB, $specialRolesOrder) === false) {
625 6
				$roleB = null;
626 6
			}
627
628 6
			if ($roleA === null && $roleB !== null) {
629 6
				return 1;
630 6
			} elseif ($roleA !== null && $roleB === null) {
631 6
				return -1;
632 6
			} elseif ($roleA !== null && $roleB !== null) {
633 6
				if ($roleA === $roleB) {
634
					return strcasecmp($a->getdisplayName(), $b->getDisplayName());
635
				} else {
636 6
					return $specialRolesOrder[$roleA] - $specialRolesOrder[$roleB];
637
				}
638
			}
639
			// we get here if $roleA === null && $roleB === null
640 6
			return strcasecmp($a->getDisplayName(), $b->getDisplayName());
641 6
		});
642
643 6
		$this->mailboxes = $mailboxes;
644 6
	}
645
646
	/**
647
	 * Convert special security mode values into Horde parameters
648
	 *
649
	 * @param string $sslMode
650
	 * @return false|string
651
	 */
652
	protected function convertSslMode($sslMode) {
653
		switch ($sslMode) {
654
			case 'none':
655
				return false;
656
		}
657
		return $sslMode;
658
	}
659
660
	/**
661
	 * @param $query
662
	 * @return array
663
	 */
664 4
	public function getChangedMailboxes($query) {
665 4
		$imp = $this->getImapConnection();
666 4
		$allBoxes = $this->getMailboxes();
667 4
		$allBoxesMap = [];
668 4
		foreach ($allBoxes as $mb) {
669 4
			$allBoxesMap[$mb->getFolderId()] = $mb;
670 4
		}
671
672
		// filter non existing mailboxes
673
		$mailBoxNames = array_filter(array_keys($query), function($folderId) use ($allBoxesMap) {
674 4
			return isset($allBoxesMap[$folderId]);
675 4
		});
676
677 4
		$status = $imp->status($mailBoxNames);
678
679
		// filter for changed mailboxes
680 4
		$changedBoxes = [];
681 4
		foreach ($status as $folderId => $s) {
682 3
			$uidValidity = $query[$folderId]['uidvalidity'];
683 3
			$uidNext = $query[$folderId]['uidnext'];
684
685 3
			if ($uidNext === $s['uidnext'] &&
686 3
				$uidValidity === $s['uidvalidity']) {
687 3
				continue;
688
			}
689
			// get unread messages
690 3
			if (isset($allBoxesMap[$folderId])) {
691
				/** @var Mailbox $m */
692 3
				$m = $allBoxesMap[$folderId];
693 3
				$role = $m->getSpecialRole();
694 3
				if (is_null($role) || $role === 'inbox') {
695 3
					$newMessages = $m->getMessagesSince($uidNext, $s['uidnext']);
696
					// only trigger updates in case new messages are actually available
697 3
					if (!empty($newMessages)) {
698 3
						$changedBoxes[$folderId] = $m->getListArray($this->getId(), $s);
699 3
						$changedBoxes[$folderId]['messages'] = $newMessages;
700 3
						$newUnreadMessages = array_filter($newMessages, function($m) {
701 3
							return $m['flags']['unseen'];
702 3
						});
703 3
						$changedBoxes[$folderId]['newUnReadCounter'] = count($newUnreadMessages);
704 3
					}
705 3
				}
706 3
			}
707 4
		}
708
709 4
		return $changedBoxes;
710
	}
711
712
	/**
713
	 * @throws \Horde_Imap_Client_Exception
714
	 */
715
	public function reconnect() {
716
		$this->mailboxes = null;
717
		if ($this->client) {
718
			$this->client->close();
719
			$this->client = null;
720
		}
721
		$this->getImapConnection();
722
	}
723
724
	/**
725
	 * @return array
726
	 */
727
	public function getConfiguration() {
728
		return $this->account->toJson();
729
	}
730
731
	/**
732
	 * @return string|Horde_Mail_Rfc822_List
733
	 */
734
	public function getEmail() {
735
		return $this->account->getEmail();
736
	}
737
738
	public function testConnectivity() {
739
		// connect to imap
740
		$this->getImapConnection();
741
742
		// connect to smtp
743
		$smtp = $this->createTransport();
744
		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...
745
			$smtp->getSMTPObject();
746
		}
747
	}
748
749
	/**
750
	 * Factory method for creating new messages
751
	 *
752
	 * @return OCA\Mail\Model\IMessage
753
	 */
754
	public function newMessage() {
755
		return new Message();
756
	}
757
758
	/**
759
	 * Factory method for creating new reply messages
760
	 *
761
	 * @return OCA\Mail\Model\ReplyMessage
762
	 */
763
	public function newReplyMessage() {
764
		return new ReplyMessage();
765
	}
766
767
}
768