Completed
Pull Request — master (#1536)
by
unknown
12:42
created

Account::getInbox()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 3
cp 0
rs 10
c 0
b 0
f 0
cc 2
eloc 3
nc 2
nop 0
crap 6
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
		if($message->getType() === 'text/html'){
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface OCA\Mail\Model\IMessage as the method getType() does only exist in the following implementations of said interface: OCA\Mail\Model\Message, OCA\Mail\Model\ReplyMessage.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
185
			$mail->setHtmlBody($message->getContent());
186
		}else{
187
			$mail->setBody($message->getContent());
188
		}
189
190
		// Append attachments
191
		foreach ($message->getAttachments() as $attachment) {
192
			$mail->addMimePart($attachment);
193
		}
194
195
		// Send the message
196
		$transport = $this->createTransport();
197
		$mail->send($transport, false, false);
198
199
		// Save the message in the sent folder
200
		$sentFolder = $this->getSentFolder();
201
		/** @var resource $raw */
202
		$raw = stream_get_contents($mail->getRaw());
203
		$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...
204
			Horde_Imap_Client::FLAG_SEEN
205
		]);
206
207
		// Delete draft if one exists
208
		if (!is_null($draftUID)) {
209
			$draftsFolder = $this->getDraftsFolder();
210
			$draftsFolder->setMessageFlag($draftUID, Horde_Imap_Client::FLAG_DELETED, true);
211
			$this->deleteDraft($draftUID);
212
		}
213
	}
214
215
	/**
216
	 * @param IMessage $message
217
	 * @param int|null $previousUID
218
	 * @return int
219
	 */
220 7
	public function saveDraft(IMessage $message, $previousUID) {
221
		// build mime body
222
		$from = new Horde_Mail_Rfc822_Address($message->getFrom());
223
		$from->personal = $this->getName();
224
		$headers = [
225
			'From' => $from,
226
			'To' => $message->getToList(),
227
			'Cc' => $message->getCCList(),
228
			'Bcc' => $message->getBCCList(),
229
			'Subject' => $message->getSubject(),
230 7
			'Date' => Horde_Mime_Headers_Date::create(),
231
		];
232
233
		$mail = new Horde_Mime_Mail();
234
		$mail->addHeaders($headers);
235
		$body = new Horde_Mime_Part();
236
		if($message->getType() === 'text/html'){
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface OCA\Mail\Model\IMessage as the method getType() does only exist in the following implementations of said interface: OCA\Mail\Model\Message, OCA\Mail\Model\ReplyMessage.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
237
			$body->setType('text/html');
238
			$mail->addHeader('Content-type','text/html; charset=iso-8859-1');
239
		}else{
240
			$body->setType('text/plain');
241
		}
242
		$body->setContents($message->getContent());
243
		$mail->setBasePart($body);
244
		//echo $mail->getBasePart()->getHtmlBody();
245
		// create transport and save message
246
		// save the message in the drafts folder
247
		$draftsFolder = $this->getDraftsFolder();
248
		/** @var resource $raw */
249
		$raw = $mail->getRaw();
250
		$newUid = $draftsFolder->saveDraft(stream_get_contents($raw));
251
252
		// delete old version if one exists
253
		if (!is_null($previousUID)) {
254
			$draftsFolder->setMessageFlag($previousUID, \Horde_Imap_Client::FLAG_DELETED,
255
				true);
256
			$this->deleteDraft($previousUID);
257
		}
258
259
		return $newUid;
260
	}
261
262
	/**
263
	 * @param string $mailBox
264
	 */
265 12
	public function deleteMailbox($mailBox) {
266 12
		if ($mailBox instanceof Mailbox) {
267
			$mailBox = $mailBox->getFolderId();
268
		}
269 12
		$conn = $this->getImapConnection();
270 12
		$conn->deleteMailbox($mailBox);
271 3
		$this->mailboxes = null;
272 3
	}
273
274
	/**
275
	 * Lists mailboxes (folders) for this account.
276
	 *
277
	 * Lists mailboxes and also queries the server for their 'special use',
278
	 * eg. inbox, sent, trash, etc
279
	 *
280
	 * @param string $pattern Pattern to match mailboxes against. All by default.
281
	 * @return Mailbox[]
282
	 */
283 6
	protected function listMailboxes($pattern = '*') {
284
		// open the imap connection
285 6
		$conn = $this->getImapConnection();
286
287
		// if successful -> get all folders of that account
288 6
		$mailBoxes = $conn->listMailboxes($pattern, Horde_Imap_Client::MBOX_ALL,
289
			[
290 6
			'delimiter' => true,
291 6
			'attributes' => true,
292 6
			'special_use' => true,
293
			'sort' => true
294 6
		]);
295
296 6
		$mailboxes = [];
297 6
		foreach ($mailBoxes as $mailbox) {
298 6
			$mailboxes[] = new Mailbox($conn, $mailbox['mailbox'],
299 6
				$mailbox['attributes'], $mailbox['delimiter']);
300 6
			if ($mailbox['mailbox']->utf8 === 'INBOX') {
301 6
				$mailboxes[] = new SearchMailbox($conn, $mailbox['mailbox'],
302 6
					$mailbox['attributes'], $mailbox['delimiter']);
303 6
			}
304 6
		}
305
306 6
		return $mailboxes;
307
	}
308
309
	/**
310
	 * @param string $folderId
311
	 * @return \OCA\Mail\Mailbox
312
	 */
313 12
	public function getMailbox($folderId) {
314 12
		$conn = $this->getImapConnection();
315 12
		$parts = explode('/', $folderId);
316 12
		if (count($parts) > 1 && $parts[1] === 'FLAGGED') {
317
			$mailbox = new Horde_Imap_Client_Mailbox($parts[0]);
318
			return new SearchMailbox($conn, $mailbox, []);
319
		}
320 12
		$mailbox = new Horde_Imap_Client_Mailbox($folderId);
321 12
		return new Mailbox($conn, $mailbox, []);
322
	}
323
324
	/**
325
	 * Get a list of all mailboxes in this account
326
	 *
327
	 * @return Mailbox[]
328
	 */
329 7
	public function getMailboxes() {
330 7
		if ($this->mailboxes === null) {
331 6
			$this->mailboxes = $this->listMailboxes();
332 6
			$this->sortMailboxes();
333 6
			$this->localizeSpecialMailboxes();
334 6
		}
335
336 7
		return $this->mailboxes;
337
	}
338
339
	/**
340
	 * @return array
341
	 */
342 3
	public function getListArray() {
343
344 3
		$folders = [];
345 3
		$mailBoxes = $this->getMailboxes();
346
		$mailBoxNames = array_map(function($mb) {
347
			/** @var Mailbox $mb */
348 3
			return $mb->getFolderId();
349
		}, array_filter($mailBoxes, function($mb) {
350
			/** @var Mailbox $mb */
351 3
			return (!$mb instanceof SearchMailbox) && (!in_array('\noselect', $mb->attributes()));
352 3
		}));
353
354 3
		$status = $this->getImapConnection()->status($mailBoxNames);
355 3
		foreach ($mailBoxes as $mailbox) {
356 3
			$s = isset($status[$mailbox->getFolderId()]) ? $status[$mailbox->getFolderId()] : null;
357 3
			$folders[] = $mailbox->getListArray($this->getId(), $s);
358 3
		}
359 3
		$delimiter = reset($folders)['delimiter'];
360
		return [
361 3
			'id' => $this->getId(),
362 3
			'email' => $this->getEMailAddress(),
363 3
			'folders' => array_values($folders),
364 3
			'specialFolders' => $this->getSpecialFoldersIds(),
365 3
			'delimiter' => $delimiter,
366 3
		];
367
	}
368
369
	/**
370
	 * @return Horde_Mail_Transport
371
	 */
372
	public function createTransport() {
373
		$transport = $this->config->getSystemValue('app.mail.transport', 'smtp');
374
		if ($transport === 'php-mail') {
375
			return new Horde_Mail_Transport_Mail();
376
		}
377
378
		$password = $this->account->getOutboundPassword();
379
		$password = $this->crypto->decrypt($password);
380
		$params = [
381
			'host' => $this->account->getOutboundHost(),
382
			'password' => $password,
383
			'port' => $this->account->getOutboundPort(),
384
			'username' => $this->account->getOutboundUser(),
385
			'secure' => $this->convertSslMode($this->account->getOutboundSslMode()),
386
			'timeout' => 2
387
		];
388 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...
389
			$params['debug'] = $this->config->getSystemValue('datadirectory') . '/horde_smtp.log';
390
		}
391
		return new Horde_Mail_Transport_Smtphorde($params);
392
	}
393
394
	/**
395
	 * Lists special use folders for this account.
396
	 *
397
	 * The special uses returned are the "best" one for each special role,
398
	 * picked amongst the ones returned by the server, as well
399
	 * as the one guessed by our code.
400
	 *
401
	 * @param bool $base64_encode
402
	 * @return array In the form [<special use>=><folder id>, ...]
403
	 */
404 6
	public function getSpecialFoldersIds($base64_encode=true) {
405 6
		$folderRoles = ['inbox', 'sent', 'drafts', 'trash', 'archive', 'junk', 'flagged', 'all'];
406 6
		$specialFoldersIds = [];
407
408 6
		foreach ($folderRoles as $role) {
409 6
			$folders = $this->getSpecialFolder($role, true);
410 6
			$specialFoldersIds[$role] = (count($folders) === 0) ? null : $folders[0]->getFolderId();
411 6
			if ($specialFoldersIds[$role] !== null && $base64_encode === true) {
412 3
				$specialFoldersIds[$role] = base64_encode($specialFoldersIds[$role]);
413 3
			}
414 6
		}
415 6
		return $specialFoldersIds;
416
	}
417
418
	/**
419
	 * Get the "drafts" mailbox
420
	 *
421
	 * @return Mailbox The best candidate for the "drafts" inbox
422
	 */
423 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...
424
		// check for existence
425
		$draftsFolder = $this->getSpecialFolder('drafts', true);
426
		if (count($draftsFolder) === 0) {
427
			// drafts folder does not exist - let's create one
428
			$conn = $this->getImapConnection();
429
			// TODO: also search for translated drafts mailboxes
430
			$conn->createMailbox('Drafts', [
431
				'special_use' => ['drafts'],
432
			]);
433
			return $this->guessBestMailBox($this->listMailboxes('Drafts'));
434
		}
435
		return $draftsFolder[0];
436
	}
437
438
	/**
439
	 * @return IMailBox
440
	 */
441
	public function getInbox() {
442
		$folders = $this->getSpecialFolder('inbox', false);
443
		return count($folders) > 0 ? $folders[0] : null;
444
	}
445
446
	/**
447
	 * Get the "sent mail" mailbox
448
	 *
449
	 * @return Mailbox The best candidate for the "sent mail" inbox
450
	 */
451 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...
452
		//check for existence
453
		$sentFolders = $this->getSpecialFolder('sent', true);
454
		if (count($sentFolders) === 0) {
455
			//sent folder does not exist - let's create one
456
			$conn = $this->getImapConnection();
457
			//TODO: also search for translated sent mailboxes
458
			$conn->createMailbox('Sent', [
459
				'special_use' => ['sent'],
460
			]);
461
			return $this->guessBestMailBox($this->listMailboxes('Sent'));
462
		}
463
		return $sentFolders[0];
464
	}
465
466
	/**
467
	 * @param string $sourceFolderId
468
	 * @param int $messageId
469
	 */
470
	public function deleteMessage($sourceFolderId, $messageId) {
471
		$mb = $this->getMailbox($sourceFolderId);
472
		$hordeSourceMailBox = $mb->getHordeMailBox();
473
		// by default we will create a 'Trash' folder if no trash is found
474
		$trashId = "Trash";
475
		$createTrash = true;
476
477
		$trashFolders = $this->getSpecialFolder('trash', true);
478
479
		if (count($trashFolders) !== 0) {
480
			$trashId = $trashFolders[0]->getFolderId();
481
			$createTrash = false;
482
		} else {
483
			// no trash -> guess
484
			$trashes = array_filter($this->getMailboxes(), function($box) {
485
				/**
486
				 * @var Mailbox $box
487
				 */
488
				return (stripos($box->getDisplayName(), 'trash') !== false);
489
			});
490
			if (!empty($trashes)) {
491
				$trashId = array_values($trashes);
492
				$trashId = $trashId[0]->getFolderId();
493
				$createTrash = false;
494
			}
495
		}
496
497
		$hordeMessageIds = new Horde_Imap_Client_Ids($messageId);
498
		$hordeTrashMailBox = new Horde_Imap_Client_Mailbox($trashId);
499
500
		if ($sourceFolderId === $trashId) {
501
			$this->getImapConnection()->expunge($hordeSourceMailBox,
502
				array('ids' => $hordeMessageIds, 'delete' => true));
503
504
			\OC::$server->getLogger()->info("Message expunged: {message} from mailbox {mailbox}",
505
				array('message' => $messageId, 'mailbox' => $sourceFolderId));
506
		} else {
507
			$this->getImapConnection()->copy($hordeSourceMailBox, $hordeTrashMailBox,
508
				array('create' => $createTrash, 'move' => true, 'ids' => $hordeMessageIds));
509
510
			\OC::$server->getLogger()->info("Message moved to trash: {message} from mailbox {mailbox}",
511
				array('message' => $messageId, 'mailbox' => $sourceFolderId, 'app' => 'mail'));
512
		}
513
	}
514
515
	/**
516
	 *
517
	 * @param int $messageId
518
	 */
519
	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...
520
		$draftsFolder = $this->getDraftsFolder();
521
522
		$draftsMailBox = new \Horde_Imap_Client_Mailbox($draftsFolder->getFolderId(), false);
523
		$this->getImapConnection()->expunge($draftsMailBox);
524
	}
525
526
	/**
527
	 * Get 'best' mailbox guess
528
	 *
529
	 * For now the best candidate is the one with
530
	 * the most messages in it.
531
	 *
532
	 * @param array $folders
533
	 * @return Mailbox
534
	 */
535
	protected function guessBestMailBox(array $folders) {
536
		$maxMessages = -1;
537
		$bestGuess = null;
538
		foreach ($folders as $folder) {
539
			/** @var Mailbox $folder */
540
			if ($folder->getTotalMessages() > $maxMessages) {
541
				$maxMessages = $folder->getTotalMessages();
542
				$bestGuess = $folder;
543
			}
544
		}
545
		return $bestGuess;
546
	}
547
548
	/**
549
	 * Get mailbox(es) that have the given special use role
550
	 *
551
	 * With this method we can get a list of all mailboxes that have been
552
	 * determined to have a specific special use role. It can also return
553
	 * the best candidate for this role, for situations where we want
554
	 * one single folder.
555
	 *
556
	 * @param string $role Special role of the folder we want to get ('sent', 'inbox', etc.)
557
	 * @param bool $guessBest If set to true, return only the folder with the most messages in it
558
	 *
559
	 * @return Mailbox[] if $guessBest is false, or Mailbox if $guessBest is true. Empty [] if no match.
560
	 */
561 6
	protected function getSpecialFolder($role, $guessBest=true) {
562
563 6
		$specialFolders = [];
564 6
		foreach ($this->getMailboxes() as $mailbox) {
565 6
			if ($role === $mailbox->getSpecialRole()) {
566 6
				$specialFolders[] = $mailbox;
567 6
			}
568 6
		}
569
570 6
		if ($guessBest === true && count($specialFolders) > 1) {
571
			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...
572
		} else {
573 6
			return $specialFolders;
574
		}
575
	}
576
577
	/**
578
	 *  Localizes the name of the special use folders
579
	 *
580
	 *  The display name of the best candidate folder for each special use
581
	 *  is localized to the user's language
582
	 */
583 6
	protected function localizeSpecialMailboxes() {
584
585 6
		$l = \OC::$server->getL10N('mail');
586
		$map = [
587
			// TRANSLATORS: translated mail box name
588 6
			'inbox'   => $l->t('Inbox'),
589
			// TRANSLATORS: translated mail box name
590 6
			'sent'    => $l->t('Sent'),
591
			// TRANSLATORS: translated mail box name
592 6
			'drafts'  => $l->t('Drafts'),
593
			// TRANSLATORS: translated mail box name
594 6
			'archive' => $l->t('Archive'),
595
			// TRANSLATORS: translated mail box name
596 6
			'trash'   => $l->t('Trash'),
597
			// TRANSLATORS: translated mail box name
598 6
			'junk'    => $l->t('Junk'),
599
			// TRANSLATORS: translated mail box name
600 6
			'all'     => $l->t('All'),
601
			// TRANSLATORS: translated mail box name
602 6
			'flagged' => $l->t('Favorites'),
603 6
		];
604 6
		$mailboxes = $this->getMailboxes();
605 6
		$specialIds = $this->getSpecialFoldersIds(false);
606 6
		foreach ($mailboxes as $i => $mailbox) {
607 6
			if (in_array($mailbox->getFolderId(), $specialIds) === true) {
608 6
				if (isset($map[$mailbox->getSpecialRole()])) {
609 6
					$translatedDisplayName = $map[$mailbox->getSpecialRole()];
610 6
					$mailboxes[$i]->setDisplayName((string)$translatedDisplayName);
611 6
				}
612 6
			}
613 6
		}
614 6
	}
615
616
	/**
617
	 * Sort mailboxes
618
	 *
619
	 * Sort the array of mailboxes with
620
	 *  - special use folders coming first in this order: all, inbox, flagged, drafts, sent, archive, junk, trash
621
	 *  - 'normal' folders coming after that, sorted alphabetically
622
	 */
623 6
	protected function sortMailboxes() {
624
625 6
		$mailboxes = $this->getMailboxes();
626
		usort($mailboxes, function($a, $b) {
627
			/**
628
			 * @var Mailbox $a
629
			 * @var Mailbox $b
630
			 */
631 6
			$roleA = $a->getSpecialRole();
632 6
			$roleB = $b->getSpecialRole();
633
			$specialRolesOrder = [
634 6
				'all'     => 0,
635 6
				'inbox'   => 1,
636 6
				'flagged' => 2,
637 6
				'drafts'  => 3,
638 6
				'sent'    => 4,
639 6
				'archive' => 5,
640 6
				'junk'    => 6,
641 6
				'trash'   => 7,
642 6
			];
643
			// if there is a flag unknown to us, we ignore it for sorting :
644
			// the folder will be sorted by name like any other 'normal' folder
645 6
			if (array_key_exists($roleA, $specialRolesOrder) === false) {
646 5
				$roleA = null;
647 5
			}
648 6
			if (array_key_exists($roleB, $specialRolesOrder) === false) {
649 6
				$roleB = null;
650 6
			}
651
652 6
			if ($roleA === null && $roleB !== null) {
653 5
				return 1;
654 6
			} elseif ($roleA !== null && $roleB === null) {
655 6
				return -1;
656 6
			} elseif ($roleA !== null && $roleB !== null) {
657 6
				if ($roleA === $roleB) {
658
					return strcasecmp($a->getdisplayName(), $b->getDisplayName());
659
				} else {
660 6
					return $specialRolesOrder[$roleA] - $specialRolesOrder[$roleB];
661
				}
662
			}
663
			// we get here if $roleA === null && $roleB === null
664 5
			return strcasecmp($a->getDisplayName(), $b->getDisplayName());
665 6
		});
666
667 6
		$this->mailboxes = $mailboxes;
668 6
	}
669
670
	/**
671
	 * Convert special security mode values into Horde parameters
672
	 *
673
	 * @param string $sslMode
674
	 * @return false|string
675
	 */
676
	protected function convertSslMode($sslMode) {
677
		switch ($sslMode) {
678
			case 'none':
679
				return false;
680
		}
681
		return $sslMode;
682
	}
683
684
	/**
685
	 * @param $query
686
	 * @return array
687
	 */
688 4
	public function getChangedMailboxes($query) {
689 4
		$imp = $this->getImapConnection();
690 4
		$allBoxes = $this->getMailboxes();
691 4
		$allBoxesMap = [];
692 4
		foreach ($allBoxes as $mb) {
693 4
			$allBoxesMap[$mb->getFolderId()] = $mb;
694 4
		}
695
696
		// filter non existing mailboxes
697
		$mailBoxNames = array_filter(array_keys($query), function($folderId) use ($allBoxesMap) {
698 4
			return isset($allBoxesMap[$folderId]);
699 4
		});
700
701 4
		$status = $imp->status($mailBoxNames);
702
703
		// filter for changed mailboxes
704 4
		$changedBoxes = [];
705 4
		foreach ($status as $folderId => $s) {
706 3
			$uidValidity = $query[$folderId]['uidvalidity'];
707 3
			$uidNext = $query[$folderId]['uidnext'];
708
709 3
			if ($uidNext === $s['uidnext'] &&
710 3
				$uidValidity === $s['uidvalidity']) {
711 3
				continue;
712
			}
713
			// get unread messages
714 3
			if (isset($allBoxesMap[$folderId])) {
715
				/** @var Mailbox $m */
716 3
				$m = $allBoxesMap[$folderId];
717 3
				$role = $m->getSpecialRole();
718 3
				if (is_null($role) || $role === 'inbox') {
719 3
					$newMessages = $m->getMessagesSince($uidNext, $s['uidnext']);
720
					// only trigger updates in case new messages are actually available
721 3
					if (!empty($newMessages)) {
722 3
						$changedBoxes[$folderId] = $m->getListArray($this->getId(), $s);
723 3
						$changedBoxes[$folderId]['messages'] = $newMessages;
724 3
						$newUnreadMessages = array_filter($newMessages, function($m) {
725 3
							return $m['flags']['unseen'];
726 3
						});
727 3
						$changedBoxes[$folderId]['newUnReadCounter'] = count($newUnreadMessages);
728 3
					}
729 3
				}
730 3
			}
731 4
		}
732
733 4
		return $changedBoxes;
734
	}
735
736
	/**
737
	 * @throws \Horde_Imap_Client_Exception
738
	 */
739
	public function reconnect() {
740
		$this->mailboxes = null;
741
		if ($this->client) {
742
			$this->client->close();
743
			$this->client = null;
744
		}
745
		$this->getImapConnection();
746
	}
747
748
	/**
749
	 * @return array
750
	 */
751
	public function getConfiguration() {
752
		return $this->account->toJson();
753
	}
754
755
	/**
756
	 * @return string|Horde_Mail_Rfc822_List
757
	 */
758
	public function getEmail() {
759
		return $this->account->getEmail();
760
	}
761
762
	public function testConnectivity() {
763
		// connect to imap
764
		$this->getImapConnection();
765
766
		// connect to smtp
767
		$smtp = $this->createTransport();
768
		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...
769
			$smtp->getSMTPObject();
770
		}
771
	}
772
773
	/**
774
	 * Factory method for creating new messages
775
	 *
776
	 * @return IMessage
777
	 */
778
	public function newMessage() {
779
		return new Message();
780
	}
781
782
	/**
783
	 * Factory method for creating new reply messages
784
	 *
785
	 * @return ReplyMessage
786
	 */
787
	public function newReplyMessage() {
788
		return new ReplyMessage();
789
	}
790
791
}
792