Completed
Push — master ( ac21a8...212d32 )
by Jan-Christoph
22:23
created

MessagesController::mimeTypeIcon()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 0
cts 6
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 6
nc 2
nop 1
crap 6
1
<?php
2
3
/**
4
 * @author Alexander Weidinger <[email protected]>
5
 * @author Christoph Wurst <[email protected]>
6
 * @author Christoph Wurst <[email protected]>
7
 * @author Jakob Sack <[email protected]>
8
 * @author Jan-Christoph Borchardt <[email protected]>
9
 * @author Lukas Reschke <[email protected]>
10
 * @author Thomas Imbreckx <[email protected]>
11
 * @author Thomas Müller <[email protected]>
12
 *
13
 * Mail
14
 *
15
 * This code is free software: you can redistribute it and/or modify
16
 * it under the terms of the GNU Affero General Public License, version 3,
17
 * as published by the Free Software Foundation.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
 * GNU Affero General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public License, version 3,
25
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
26
 *
27
 */
28
29
namespace OCA\Mail\Controller;
30
31
use OC;
32
use OCA\Mail\Http\AttachmentDownloadResponse;
33
use OCA\Mail\Http\HtmlResponse;
34
use OCA\Mail\Service\AccountService;
35
use OCA\Mail\Service\ContactsIntegration;
36
use OCA\Mail\Service\IAccount;
37
use OCA\Mail\Service\IMailBox;
38
use OCA\Mail\Service\Logger;
39
use OCA\Mail\Service\UnifiedAccount;
40
use OCP\AppFramework\Controller;
41
use OCP\AppFramework\Db\DoesNotExistException;
42
use OCP\AppFramework\Http;
43
use OCP\AppFramework\Http\ContentSecurityPolicy;
44
use OCP\AppFramework\Http\JSONResponse;
45
use OCP\AppFramework\Http\TemplateResponse;
46
use OCP\Files\Folder;
47
use OCP\Files\IMimeTypeDetector;
48
use OCP\IL10N;
49
use OCP\IRequest;
50
use OCP\Util;
51
52
class MessagesController extends Controller {
53
54
	/** @var AccountService */
55
	private $accountService;
56
57
	/** @var string */
58
	private $currentUserId;
59
60
	/** @var ContactsIntegration */
61
	private $contactsIntegration;
62
63
	/** @var Logger */
64
	private $logger;
65
66
	/** @var Folder */
67
	private $userFolder;
68
69
	/** @var IMimeTypeDetector */
70
	private $mimeTypeDetector;
71
72
	/** @var IL10N */
73
	private $l10n;
74
75
	/** @var IAccount[] */
76
	private $accounts = [];
77
78
	/**
79
	 * @param string $appName
80
	 * @param IRequest $request
81
	 * @param AccountService $accountService
82
	 * @param string $UserId
83
	 * @param $userFolder
84
	 * @param ContactsIntegration $contactsIntegration
85
	 * @param Logger $logger
86
	 * @param IL10N $l10n
87
	 * @param IMimeTypeDetector $mimeTypeDetector
88
	 */
89 12
	public function __construct($appName,
90
								IRequest $request,
91
								AccountService $accountService,
92
								$UserId,
93
								$userFolder,
94
								ContactsIntegration $contactsIntegration,
95
								Logger $logger,
96
								IL10N $l10n,
97
								IMimeTypeDetector $mimeTypeDetector) {
98 12
		parent::__construct($appName, $request);
99 12
		$this->accountService = $accountService;
100 12
		$this->currentUserId = $UserId;
101 12
		$this->userFolder = $userFolder;
102 12
		$this->contactsIntegration = $contactsIntegration;
103 12
		$this->logger = $logger;
104 12
		$this->l10n = $l10n;
105 12
		$this->mimeTypeDetector = $mimeTypeDetector;
106 12
	}
107
108
	/**
109
	 * @NoAdminRequired
110
	 * @NoCSRFRequired
111
	 *
112
	 * @param int $accountId
113
	 * @param string $folderId
114
	 * @param int $from
115
	 * @param int $to
116
	 * @param string $filter
117
	 * @param array $ids
118
	 * @return JSONResponse
119
	 */
120
	public function index($accountId, $folderId, $from=0, $to=20, $filter=null, $ids=null) {
121
		if (!is_null($ids)) {
122
			$ids = explode(',', $ids);
123
124
			return $this->loadMultiple($accountId, $folderId, $ids);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->loadMultip...ntId, $folderId, $ids); (array) is incompatible with the return type documented by OCA\Mail\Controller\MessagesController::index of type OCP\AppFramework\Http\JSONResponse.

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 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('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...
125
		}
126
		$mailBox = $this->getFolder($accountId, $folderId);
127
128
		$this->logger->debug("loading messages $from to $to of folder <$folderId>");
129
130
		$json = $mailBox->getMessages($from, $to-$from+1, $filter);
131
132
		$ci = $this->contactsIntegration;
133
		$json = array_map(function($j) use ($ci, $mailBox) {
134
			if ($mailBox->getSpecialRole() === 'trash') {
135
				$j['delete'] = (string)$this->l10n->t('Delete permanently');
136
			}
137
138
			if ($mailBox->getSpecialRole() === 'sent') {
139
				$j['fromEmail'] = $j['toEmail'];
140
				$j['from'] = $j['to'];
141
				if((count($j['toList']) > 1) || (count($j['ccList']) > 0)) {
142
					$j['from'] .= ' ' . $this->l10n->t('& others');
143
				}
144
			}
145
146
			$j['senderImage'] = $ci->getPhoto($j['fromEmail']);
147
			return $j;
148
		}, $json);
149
150
		return new JSONResponse($json);
151
	}
152
153
	private function loadMessage($accountId, $folderId, $id) {
154
		$account = $this->getAccount($accountId);
155
		$mailBox = $account->getMailbox(base64_decode($folderId));
156
		$m = $mailBox->getMessage($id);
157
158
		$json = $this->enhanceMessage($accountId, $folderId, $id, $m, $account, $mailBox);
159
160
		// Unified inbox hack
161
		$messageId = $id;
162
		if ($accountId === UnifiedAccount::ID) {
163
			// Add accountId, folderId for unified inbox replies
164
			list($accountId, $messageId) = json_decode(base64_decode($id));
165
			$account = $this->getAccount($accountId);
166
			$inbox = $account->getInbox();
167
			$folderId = base64_encode($inbox->getFolderId());
168
		}
169
		$json['messageId'] = $messageId;
170
		$json['accountId'] = $accountId;
171
		$json['folderId'] = $folderId;
172
		// End unified inbox hack
173
174
		return $json;
175
	}
176
177
	/**
178
	 * @NoAdminRequired
179
	 * @NoCSRFRequired
180
	 *
181
	 * @param int $accountId
182
	 * @param string $folderId
183
	 * @param mixed $id
184
	 * @return JSONResponse
185
	 */
186
	public function show($accountId, $folderId, $id) {
187
		try {
188
			$json = $this->loadMessage($accountId, $folderId, $id);
189
		} catch (DoesNotExistException $ex) {
0 ignored issues
show
Bug introduced by
The class OCP\AppFramework\Db\DoesNotExistException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
190
			return new JSONResponse([], 404);
191
		}
192
		return new JSONResponse($json);
193
	}
194
195
	/**
196
	 * @NoAdminRequired
197
	 * @NoCSRFRequired
198
	 *
199
	 * @param int $accountId
200
	 * @param string $folderId
201
	 * @param string $messageId
202
	 * @return HtmlResponse|TemplateResponse
203
	 */
204 1
	public function getHtmlBody($accountId, $folderId, $messageId) {
205
		try {
206 1
			$mailBox = $this->getFolder($accountId, $folderId);
207
208 1
			$m = $mailBox->getMessage($messageId, true);
0 ignored issues
show
Unused Code introduced by
The call to IMailBox::getMessage() has too many arguments starting with true.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
209
			$html = $m->getHtmlBody($accountId, $folderId, $messageId, function($cid) use ($m){
210
				$match = array_filter($m->attachments, function($a) use($cid){
211
					return $a['cid'] === $cid;
212
				});
213
				$match = array_shift($match);
214
				if (is_null($match)) {
215
					return null;
216
				}
217
				return $match['id'];
218 1
			});
219
220 1
			$htmlResponse = new HtmlResponse($html);
221
222
			// Harden the default security policy
223 1
			$policy = new ContentSecurityPolicy();
224 1
			$policy->allowEvalScript(false);
225 1
			$policy->disallowScriptDomain('\'self\'');
226 1
			$policy->disallowConnectDomain('\'self\'');
227 1
			$policy->disallowFontDomain('\'self\'');
228 1
			$policy->disallowMediaDomain('\'self\'');
229 1
			$htmlResponse->setContentSecurityPolicy($policy);
230
231
			// Enable caching
232 1
			$htmlResponse->cacheFor(60 * 60);
233 1
			$htmlResponse->addHeader('Pragma', 'cache');
234
235 1
			return $htmlResponse;
236
		} catch(\Exception $ex) {
237
			return new TemplateResponse($this->appName, 'error', ['message' => $ex->getMessage()], 'none');
238
		}
239
	}
240
241
	/**
242
	 * @NoAdminRequired
243
	 * @NoCSRFRequired
244
	 *
245
	 * @param int $accountId
246
	 * @param string $folderId
247
	 * @param string $messageId
248
	 * @param string $attachmentId
249
	 * @return AttachmentDownloadResponse
250
	 */
251 1
	public function downloadAttachment($accountId, $folderId, $messageId, $attachmentId) {
252 1
		$mailBox = $this->getFolder($accountId, $folderId);
253
254 1
		$attachment = $mailBox->getAttachment($messageId, $attachmentId);
255
256 1
		return new AttachmentDownloadResponse(
257 1
			$attachment->getContents(),
258 1
			$attachment->getName(),
259 1
			$attachment->getType());
260
	}
261
262
	/**
263
	 * @NoAdminRequired
264
	 * @NoCSRFRequired
265
	 *
266
	 * @param int $accountId
267
	 * @param string $folderId
268
	 * @param string $messageId
269
	 * @param int $attachmentId
270
	 * @param string $targetPath
271
	 * @return JSONResponse
272
	 */
273 2
	public function saveAttachment($accountId, $folderId, $messageId, $attachmentId, $targetPath) {
274 2
		$mailBox = $this->getFolder($accountId, $folderId);
275
276 2
		$attachmentIds = [];
0 ignored issues
show
Unused Code introduced by
$attachmentIds is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
277 2
		if($attachmentId === 0) {
278
			// Save all attachments
279 1
			$m = $mailBox->getMessage($messageId);
280
			$attachmentIds = array_map(function($a){
281 1
				return $a['id'];
282 1
			}, $m->attachments);
283 1
		} else {
284 1
			$attachmentIds = [$attachmentId];
285
		}
286
287 2
		foreach($attachmentIds as $attachmentId) {
288 2
			$attachment = $mailBox->getAttachment($messageId, $attachmentId);
289
290 2
			$fileName = $attachment->getName();
291 2
			$fileParts = pathinfo($fileName);
292 2
			$fileName = $fileParts['filename'];
293 2
			$fileExtension = $fileParts['extension'];
294 2
			$fullPath = "$targetPath/$fileName.$fileExtension";
295 2
			$counter = 2;
296 2
			while($this->userFolder->nodeExists($fullPath)) {
297
				$fullPath = "$targetPath/$fileName ($counter).$fileExtension";
298
				$counter++;
299
			}
300
301 2
			$newFile = $this->userFolder->newFile($fullPath);
302 2
			$newFile->putContent($attachment->getContents());
303 2
		}
304
305 2
		return new JSONResponse();
306
	}
307
308
	/**
309
	 * @NoAdminRequired
310
	 *
311
	 * @param int $accountId
312
	 * @param string $folderId
313
	 * @param string $messageId
314
	 * @param array $flags
315
	 * @return JSONResponse
316
	 */
317 2
	public function setFlags($accountId, $folderId, $messageId, $flags) {
318 2
		$mailBox = $this->getFolder($accountId, $folderId);
319
320 2
		foreach($flags as $flag => $value) {
321 2
			$value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
322 2
			if ($flag === 'unseen') {
323 1
				$flag = 'seen';
324 1
				$value = !$value;
325 1
			}
326 2
			$mailBox->setMessageFlag($messageId, '\\'.$flag, $value);
327 2
		}
328
329 2
		return new JSONResponse();
330
	}
331
332
	/**
333
	 * @NoAdminRequired
334
	 *
335
	 * @param int $accountId
336
	 * @param string $folderId
337
	 * @param string $id
338
	 * @return JSONResponse
339
	 */
340 3
	public function destroy($accountId, $folderId, $id) {
341 3
		$this->logger->debug("deleting message <$id> of folder <$folderId>, account <$accountId>");
342
		try {
343 3
			$account = $this->getAccount($accountId);
344 2
			$account->deleteMessage(base64_decode($folderId), $id);
345 1
			return new JSONResponse();
346
347 2
		} catch (DoesNotExistException $e) {
0 ignored issues
show
Bug introduced by
The class OCP\AppFramework\Db\DoesNotExistException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
348 2
			$this->logger->error("could not delete message <$id> of folder <$folderId>, "
349 2
				. "account <$accountId> because it does not exist");
350 2
			return new JSONResponse([], Http::STATUS_NOT_FOUND);
351
		}
352
	}
353
354
	/**
355
	 * @param int $accountId
356
	 * @return IAccount
357
	 */
358 9
	private function getAccount($accountId) {
359 9
		if (!array_key_exists($accountId, $this->accounts)) {
360 9
			$this->accounts[$accountId] = $this->accountService->find($this->currentUserId, $accountId);
361 8
		}
362 8
		return $this->accounts[$accountId];
363
	}
364
365
	/**
366
	 * @param int $accountId
367
	 * @param string $folderId
368
	 * @return IMailBox
369
	 */
370 6
	private function getFolder($accountId, $folderId) {
371 6
		$account = $this->getAccount($accountId);
372 6
		return $account->getMailbox(base64_decode($folderId));
373
	}
374
375
	/**
376
	 * @param string $messageId
377
	 * @param $accountId
378
	 * @param $folderId
379
	 * @return callable
380
	 */
381
	private function enrichDownloadUrl($accountId, $folderId, $messageId, $attachment) {
382
		$downloadUrl = Util::linkToRoute('mail.messages.downloadAttachment', [
383
			'accountId' => $accountId,
384
			'folderId' => $folderId,
385
			'messageId' => $messageId,
386
			'attachmentId' => $attachment['id'],
387
		]);
388
		$downloadUrl = OC::$server->getURLGenerator()->getAbsoluteURL($downloadUrl);
389
		$attachment['downloadUrl'] = $downloadUrl;
390
		$attachment['mimeUrl'] = $this->mimeTypeDetector->mimeTypeIcon($attachment['mime']);
391
392
		if ($this->attachmentIsImage($attachment)) {
393
			$attachment['isImage'] = true;
394
		} else if ($this->attachmentIsCalendarEvent($attachment)) {
395
			$attachment['isCalendarEvent'] = true;
396
		}
397
		return $attachment;
398
	}
399
400
	/**
401
	 * @param $attachment
402
	 *
403
	 * Determines if the content of this attachment is an image
404
	 *
405
	 * @return boolean
406
	 */
407
	private function attachmentIsImage($attachment) {
408
		return in_array(
409
			$attachment['mime'], [
410
			'image/jpeg',
411
			'image/png',
412
			'image/gif'
413
		]);
414
	}
415
416
	/**
417
	 * @param type $attachment
418
	 * @return boolean
419
	 */
420
	private function attachmentIsCalendarEvent($attachment) {
421
		return $attachment['mime'] === 'text/calendar';
422
	}
423
424
	/**
425
	 * @param string $accountId
426
	 * @param string $folderId
427
	 * @param string $messageId
428
	 * @return string
429
	 */
430
	private function buildHtmlBodyUrl($accountId, $folderId, $messageId) {
431
		$htmlBodyUrl = OC::$server->getURLGenerator()->linkToRoute('mail.messages.getHtmlBody', [
432
			'accountId' => $accountId,
433
			'folderId' => $folderId,
434
			'messageId' => $messageId,
435
		]);
436
		return OC::$server->getURLGenerator()->getAbsoluteURL($htmlBodyUrl);
437
	}
438
439
	/**
440
	 * @param integer $accountId
441
	 * @param string $folderId
442
	 */
443
	private function loadMultiple($accountId, $folderId, $ids) {
444
		$messages = array_map(function($id) use ($accountId, $folderId){
445
			try {
446
				return $this->loadMessage($accountId, $folderId, $id);
447
			} catch (DoesNotExistException $ex) {
0 ignored issues
show
Bug introduced by
The class OCP\AppFramework\Db\DoesNotExistException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
448
				return null;
449
			}
450
		}, $ids);
451
452
		return $messages;
453
	}
454
455
	/**
456
	 * @param $accountId
457
	 * @param $folderId
458
	 * @param $id
459
	 * @param $m
460
	 * @param IAccount $account
461
	 * @param IMailBox $mailBox
462
	 * @return mixed
463
	 */
464
	private function enhanceMessage($accountId, $folderId, $id, $m, IAccount $account, $mailBox) {
465
		$json = $m->getFullMessage($account->getEmail(), $mailBox->getSpecialRole());
466
		$json['senderImage'] = $this->contactsIntegration->getPhoto($m->getFromEmail());
467
		if (isset($json['hasHtmlBody'])) {
468
			$json['htmlBodyUrl'] = $this->buildHtmlBodyUrl($accountId, $folderId, $id);
469
		}
470
471
		if (isset($json['attachments'])) {
472
			$json['attachments'] = array_map(function ($a) use ($accountId, $folderId, $id) {
473
				return $this->enrichDownloadUrl($accountId, $folderId, $id, $a);
474
			}, $json['attachments']);
475
476
			// show images first
477
			usort($json['attachments'], function($a, $b) {
478
				if (isset($a['isImage']) && !isset($b['isImage'])) {
479
					return -1;
480
				} elseif (!isset($a['isImage']) && isset($b['isImage'])) {
481
					return 1;
482
				} else {
483
					Util::naturalSortCompare($a['fileName'], $b['fileName']);
484
				}
485
			});
486
			return $json;
487
		}
488
		return $json;
489
	}
490
491
}
492