Issues (78)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

lib/account.php (1 issue)

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)) {
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, [
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() {
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() {
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) {
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)];
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) {
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