Completed
Push — master ( a92383...9ab88c )
by Christoph
11s
created

Account::sendMessage()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 44
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 44
ccs 0
cts 30
cp 0
rs 8.5806
cc 4
eloc 26
nc 8
nop 2
crap 20
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 View Code Duplication
			if ($this->config->getSystemValue('app.mail.imaplog.enabled', false)) {
0 ignored issues
show
Duplication introduced by
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...
131
				$params['debug'] = $this->config->getSystemValue('datadirectory') . '/horde_imap.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
	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, false, false);
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
			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 7
	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 7
		$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 View Code Duplication
		if ($this->config->getSystemValue('app.mail.smtplog.enabled', false)) {
0 ignored issues
show
Duplication introduced by
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...
380
			$params['debug'] = $this->config->getSystemValue('datadirectory') . '/horde_smtp.log';
381
		}
382
		return new Horde_Mail_Transport_Smtphorde($params);
383
	}
384
385
	/**
386
	 * Lists special use folders for this account.
387
	 *
388
	 * The special uses returned are the "best" one for each special role,
389
	 * picked amongst the ones returned by the server, as well
390
	 * as the one guessed by our code.
391
	 *
392
	 * @param bool $base64_encode
393
	 * @return array In the form [<special use>=><folder id>, ...]
394
	 */
395 6
	public function getSpecialFoldersIds($base64_encode=true) {
396 6
		$folderRoles = ['inbox', 'sent', 'drafts', 'trash', 'archive', 'junk', 'flagged', 'all'];
397 6
		$specialFoldersIds = [];
398
399 6
		foreach ($folderRoles as $role) {
400 6
			$folders = $this->getSpecialFolder($role, true);
401 6
			$specialFoldersIds[$role] = (count($folders) === 0) ? null : $folders[0]->getFolderId();
402 6
			if ($specialFoldersIds[$role] !== null && $base64_encode === true) {
403 3
				$specialFoldersIds[$role] = base64_encode($specialFoldersIds[$role]);
404 3
			}
405 6
		}
406 6
		return $specialFoldersIds;
407
	}
408
409
	/**
410
	 * Get the "drafts" mailbox
411
	 *
412
	 * @return Mailbox The best candidate for the "drafts" inbox
413
	 */
414 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...
415
		// check for existence
416
		$draftsFolder = $this->getSpecialFolder('drafts', true);
417
		if (count($draftsFolder) === 0) {
418
			// drafts folder does not exist - let's create one
419
			$conn = $this->getImapConnection();
420
			// TODO: also search for translated drafts mailboxes
421
			$conn->createMailbox('Drafts', [
422
				'special_use' => ['drafts'],
423
			]);
424
			return $this->guessBestMailBox($this->listMailboxes('Drafts'));
425
		}
426
		return $draftsFolder[0];
427
	}
428
429
	/**
430
	 * @return IMailBox
431
	 */
432
	public function getInbox() {
433
		$folders = $this->getSpecialFolder('inbox', false);
434
		return count($folders) > 0 ? $folders[0] : null;
435
	}
436
437
	/**
438
	 * Get the "sent mail" mailbox
439
	 *
440
	 * @return Mailbox The best candidate for the "sent mail" inbox
441
	 */
442 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...
443
		//check for existence
444
		$sentFolders = $this->getSpecialFolder('sent', true);
445
		if (count($sentFolders) === 0) {
446
			//sent folder does not exist - let's create one
447
			$conn = $this->getImapConnection();
448
			//TODO: also search for translated sent mailboxes
449
			$conn->createMailbox('Sent', [
450
				'special_use' => ['sent'],
451
			]);
452
			return $this->guessBestMailBox($this->listMailboxes('Sent'));
453
		}
454
		return $sentFolders[0];
455
	}
456
457
	/**
458
	 * @param string $sourceFolderId
459
	 * @param int $messageId
460
	 */
461
	public function deleteMessage($sourceFolderId, $messageId) {
462
		$mb = $this->getMailbox($sourceFolderId);
463
		$hordeSourceMailBox = $mb->getHordeMailBox();
464
		// by default we will create a 'Trash' folder if no trash is found
465
		$trashId = "Trash";
466
		$createTrash = true;
467
468
		$trashFolders = $this->getSpecialFolder('trash', true);
469
470
		if (count($trashFolders) !== 0) {
471
			$trashId = $trashFolders[0]->getFolderId();
472
			$createTrash = false;
473
		} else {
474
			// no trash -> guess
475
			$trashes = array_filter($this->getMailboxes(), function($box) {
476
				/**
477
				 * @var Mailbox $box
478
				 */
479
				return (stripos($box->getDisplayName(), 'trash') !== false);
480
			});
481
			if (!empty($trashes)) {
482
				$trashId = array_values($trashes);
483
				$trashId = $trashId[0]->getFolderId();
484
				$createTrash = false;
485
			}
486
		}
487
488
		$hordeMessageIds = new Horde_Imap_Client_Ids($messageId);
489
		$hordeTrashMailBox = new Horde_Imap_Client_Mailbox($trashId);
490
491
		if ($sourceFolderId === $trashId) {
492
			$this->getImapConnection()->expunge($hordeSourceMailBox,
493
				array('ids' => $hordeMessageIds, 'delete' => true));
494
495
			\OC::$server->getLogger()->info("Message expunged: {message} from mailbox {mailbox}",
496
				array('message' => $messageId, 'mailbox' => $sourceFolderId));
497
		} else {
498
			$this->getImapConnection()->copy($hordeSourceMailBox, $hordeTrashMailBox,
499
				array('create' => $createTrash, 'move' => true, 'ids' => $hordeMessageIds));
500
501
			\OC::$server->getLogger()->info("Message moved to trash: {message} from mailbox {mailbox}",
502
				array('message' => $messageId, 'mailbox' => $sourceFolderId, 'app' => 'mail'));
503
		}
504
	}
505
506
	/**
507
	 * 
508
	 * @param int $messageId
509
	 */
510
	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...
511
		$draftsFolder = $this->getDraftsFolder();
512
		
513
		$draftsMailBox = new \Horde_Imap_Client_Mailbox($draftsFolder->getFolderId(), false);
514
		$this->getImapConnection()->expunge($draftsMailBox);
515
	}
516
517
	/**
518
	 * Get 'best' mailbox guess
519
	 *
520
	 * For now the best candidate is the one with
521
	 * the most messages in it.
522
	 *
523
	 * @param array $folders
524
	 * @return Mailbox
525
	 */
526
	protected function guessBestMailBox(array $folders) {
527
		$maxMessages = -1;
528
		$bestGuess = null;
529
		foreach ($folders as $folder) {
530
			/** @var Mailbox $folder */
531
			if ($folder->getTotalMessages() > $maxMessages) {
532
				$maxMessages = $folder->getTotalMessages();
533
				$bestGuess = $folder;
534
			}
535
		}
536
		return $bestGuess;
537
	}
538
539
	/**
540
	 * Get mailbox(es) that have the given special use role
541
	 *
542
	 * With this method we can get a list of all mailboxes that have been
543
	 * determined to have a specific special use role. It can also return
544
	 * the best candidate for this role, for situations where we want
545
	 * one single folder.
546
	 *
547
	 * @param string $role Special role of the folder we want to get ('sent', 'inbox', etc.)
548
	 * @param bool $guessBest If set to true, return only the folder with the most messages in it
549
	 *
550
	 * @return Mailbox[] if $guessBest is false, or Mailbox if $guessBest is true. Empty [] if no match.
551
	 */
552 6
	protected function getSpecialFolder($role, $guessBest=true) {
553
554 6
		$specialFolders = [];
555 6
		foreach ($this->getMailboxes() as $mailbox) {
556 6
			if ($role === $mailbox->getSpecialRole()) {
557 6
				$specialFolders[] = $mailbox;
558 6
			}
559 6
		}
560
561 6
		if ($guessBest === true && count($specialFolders) > 1) {
562
			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...
563
		} else {
564 6
			return $specialFolders;
565
		}
566
	}
567
568
	/**
569
	 *  Localizes the name of the special use folders
570
	 *
571
	 *  The display name of the best candidate folder for each special use
572
	 *  is localized to the user's language
573
	 */
574 6
	protected function localizeSpecialMailboxes() {
575
576 6
		$l = \OC::$server->getL10N('mail');
577
		$map = [
578
			// TRANSLATORS: translated mail box name
579 6
			'inbox'   => $l->t('Inbox'),
580
			// TRANSLATORS: translated mail box name
581 6
			'sent'    => $l->t('Sent'),
582
			// TRANSLATORS: translated mail box name
583 6
			'drafts'  => $l->t('Drafts'),
584
			// TRANSLATORS: translated mail box name
585 6
			'archive' => $l->t('Archive'),
586
			// TRANSLATORS: translated mail box name
587 6
			'trash'   => $l->t('Trash'),
588
			// TRANSLATORS: translated mail box name
589 6
			'junk'    => $l->t('Junk'),
590
			// TRANSLATORS: translated mail box name
591 6
			'all'     => $l->t('All'),
592
			// TRANSLATORS: translated mail box name
593 6
			'flagged' => $l->t('Favorites'),
594 6
		];
595 6
		$mailboxes = $this->getMailboxes();
596 6
		$specialIds = $this->getSpecialFoldersIds(false);
597 6
		foreach ($mailboxes as $i => $mailbox) {
598 6
			if (in_array($mailbox->getFolderId(), $specialIds) === true) {
599 6
				if (isset($map[$mailbox->getSpecialRole()])) {
600 6
					$translatedDisplayName = $map[$mailbox->getSpecialRole()];
601 6
					$mailboxes[$i]->setDisplayName((string)$translatedDisplayName);
602 6
				}
603 6
			}
604 6
		}
605 6
	}
606
607
	/**
608
	 * Sort mailboxes
609
	 *
610
	 * Sort the array of mailboxes with
611
	 *  - special use folders coming first in this order: all, inbox, flagged, drafts, sent, archive, junk, trash
612
	 *  - 'normal' folders coming after that, sorted alphabetically
613
	 */
614 6
	protected function sortMailboxes() {
615
616 6
		$mailboxes = $this->getMailboxes();
617
		usort($mailboxes, function($a, $b) {
618
			/**
619
			 * @var Mailbox $a
620
			 * @var Mailbox $b
621
			 */
622 6
			$roleA = $a->getSpecialRole();
623 6
			$roleB = $b->getSpecialRole();
624
			$specialRolesOrder = [
625 6
				'all'     => 0,
626 6
				'inbox'   => 1,
627 6
				'flagged' => 2,
628 6
				'drafts'  => 3,
629 6
				'sent'    => 4,
630 6
				'archive' => 5,
631 6
				'junk'    => 6,
632 6
				'trash'   => 7,
633 6
			];
634
			// if there is a flag unknown to us, we ignore it for sorting :
635
			// the folder will be sorted by name like any other 'normal' folder
636 6
			if (array_key_exists($roleA, $specialRolesOrder) === false) {
637 5
				$roleA = null;
638 5
			}
639 6
			if (array_key_exists($roleB, $specialRolesOrder) === false) {
640 6
				$roleB = null;
641 6
			}
642
643 6
			if ($roleA === null && $roleB !== null) {
644 5
				return 1;
645 6
			} elseif ($roleA !== null && $roleB === null) {
646 6
				return -1;
647 6
			} elseif ($roleA !== null && $roleB !== null) {
648 6
				if ($roleA === $roleB) {
649
					return strcasecmp($a->getdisplayName(), $b->getDisplayName());
650
				} else {
651 6
					return $specialRolesOrder[$roleA] - $specialRolesOrder[$roleB];
652
				}
653
			}
654
			// we get here if $roleA === null && $roleB === null
655 5
			return strcasecmp($a->getDisplayName(), $b->getDisplayName());
656 6
		});
657
658 6
		$this->mailboxes = $mailboxes;
659 6
	}
660
661
	/**
662
	 * Convert special security mode values into Horde parameters
663
	 *
664
	 * @param string $sslMode
665
	 * @return false|string
666
	 */
667
	protected function convertSslMode($sslMode) {
668
		switch ($sslMode) {
669
			case 'none':
670
				return false;
671
		}
672
		return $sslMode;
673
	}
674
675
	/**
676
	 * @param $query
677
	 * @return array
678
	 */
679 4
	public function getChangedMailboxes($query) {
680 4
		$imp = $this->getImapConnection();
681 4
		$allBoxes = $this->getMailboxes();
682 4
		$allBoxesMap = [];
683 4
		foreach ($allBoxes as $mb) {
684 4
			$allBoxesMap[$mb->getFolderId()] = $mb;
685 4
		}
686
687
		// filter non existing mailboxes
688
		$mailBoxNames = array_filter(array_keys($query), function($folderId) use ($allBoxesMap) {
689 4
			return isset($allBoxesMap[$folderId]);
690 4
		});
691
692 4
		$status = $imp->status($mailBoxNames);
693
694
		// filter for changed mailboxes
695 4
		$changedBoxes = [];
696 4
		foreach ($status as $folderId => $s) {
697 3
			$uidValidity = $query[$folderId]['uidvalidity'];
698 3
			$uidNext = $query[$folderId]['uidnext'];
699
700 3
			if ($uidNext === $s['uidnext'] &&
701 3
				$uidValidity === $s['uidvalidity']) {
702 3
				continue;
703
			}
704
			// get unread messages
705 3
			if (isset($allBoxesMap[$folderId])) {
706
				/** @var Mailbox $m */
707 3
				$m = $allBoxesMap[$folderId];
708 3
				$role = $m->getSpecialRole();
709 3
				if (is_null($role) || $role === 'inbox') {
710 3
					$newMessages = $m->getMessagesSince($uidNext, $s['uidnext']);
711
					// only trigger updates in case new messages are actually available
712 3
					if (!empty($newMessages)) {
713 3
						$changedBoxes[$folderId] = $m->getListArray($this->getId(), $s);
714 3
						$changedBoxes[$folderId]['messages'] = $newMessages;
715 3
						$newUnreadMessages = array_filter($newMessages, function($m) {
716 3
							return $m['flags']['unseen'];
717 3
						});
718 3
						$changedBoxes[$folderId]['newUnReadCounter'] = count($newUnreadMessages);
719 3
					}
720 3
				}
721 3
			}
722 4
		}
723
724 4
		return $changedBoxes;
725
	}
726
727
	/**
728
	 * @throws \Horde_Imap_Client_Exception
729
	 */
730
	public function reconnect() {
731
		$this->mailboxes = null;
732
		if ($this->client) {
733
			$this->client->close();
734
			$this->client = null;
735
		}
736
		$this->getImapConnection();
737
	}
738
739
	/**
740
	 * @return array
741
	 */
742
	public function getConfiguration() {
743
		return $this->account->toJson();
744
	}
745
746
	/**
747
	 * @return string|Horde_Mail_Rfc822_List
748
	 */
749
	public function getEmail() {
750
		return $this->account->getEmail();
751
	}
752
753
	public function testConnectivity() {
754
		// connect to imap
755
		$this->getImapConnection();
756
757
		// connect to smtp
758
		$smtp = $this->createTransport();
759
		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...
760
			$smtp->getSMTPObject();
761
		}
762
	}
763
764
	/**
765
	 * Factory method for creating new messages
766
	 *
767
	 * @return IMessage
768
	 */
769
	public function newMessage() {
770
		return new Message();
771
	}
772
773
	/**
774
	 * Factory method for creating new reply messages
775
	 *
776
	 * @return ReplyMessage
777
	 */
778
	public function newReplyMessage() {
779
		return new ReplyMessage();
780
	}
781
782
}
783