Completed
Push — master ( 1f89ca...fd92ed )
by Lukas
12s
created

lib/account.php (8 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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