Completed
Push — Readme-gmail-change ( 92e7ac )
by
unknown
02:28
created

MessagesController::attachmentIsImage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
ccs 0
cts 4
cp 0
rs 9.4285
cc 1
eloc 4
nc 1
nop 1
crap 2
1
<?php
2
/**
3
 * ownCloud - Mail app
4
 *
5
 * @author Thomas Müller
6
 * @copyright 2013-2014 Thomas Müller [email protected]
7
 *
8
 * You should have received a copy of the GNU Lesser General Public
9
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
10
 *
11
 */
12
13
namespace OCA\Mail\Controller;
14
15
use OCA\Mail\Http\AttachmentDownloadResponse;
16
use OCA\Mail\Http\HtmlResponse;
17
use OCA\Mail\Service\AccountService;
18
use OCA\Mail\Service\ContactsIntegration;
19
use OCA\Mail\Service\IAccount;
20
use OCA\Mail\Service\IMailBox;
21
use OCA\Mail\Service\UnifiedAccount;
22
use OCP\AppFramework\Controller;
23
use OCP\AppFramework\Db\DoesNotExistException;
24
use OCP\AppFramework\Http;
25
use OCP\AppFramework\Http\ContentSecurityPolicy;
26
use OCP\AppFramework\Http\JSONResponse;
27
use OCP\AppFramework\Http\TemplateResponse;
28
use OCP\Files\IMimeTypeDetector;
29
use OCP\IL10N;
30
use OCP\Util;
31
32
class MessagesController extends Controller {
33
34
	/** @var AccountService */
35
	private $accountService;
36
37
	/**
38
	 * @var string
39
	 */
40
	private $currentUserId;
41
42
	/**
43
	 * @var ContactsIntegration
44
	 */
45
	private $contactsIntegration;
46
47
	/**
48
	 * @var \OCA\Mail\Service\Logger
49
	 */
50
	private $logger;
51
52
	/**
53
	 * @var \OCP\Files\Folder
54
	 */
55
	private $userFolder;
56
57
	/**
58
	 * @var IL10N
59
	 */
60
	private $l10n;
61
62
	/**
63
	 * @var IMimeTypeDetector
64
	 */
65
	private $mimeTypeDetector;
66
67
	/**
68
	 * @var IAccount[]
69
	 */
70
	private $accounts = [];
71
72
	/**
73
	 * @param string $appName
74
	 * @param \OCP\IRequest $request
75
	 * @param AccountService $accountService
76
	 * @param $currentUserId
77
	 * @param $userFolder
78
	 * @param $contactsIntegration
79
	 * @param $logger
80
	 * @param $l10n
81
	 * @param IMimeTypeDetector $mimeTypeDetector
82
	 */
83 12 View Code Duplication
	public function __construct($appName,
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...
84
								$request,
85
								AccountService $accountService,
86
								$currentUserId,
87
								$userFolder,
88
								$contactsIntegration,
89
								$logger,
90
								$l10n,
91
								IMimeTypeDetector $mimeTypeDetector) {
92 12
		parent::__construct($appName, $request);
93 12
		$this->accountService = $accountService;
94 12
		$this->currentUserId = $currentUserId;
95 12
		$this->userFolder = $userFolder;
96 12
		$this->contactsIntegration = $contactsIntegration;
97 12
		$this->logger = $logger;
98 12
		$this->l10n = $l10n;
99 12
		$this->mimeTypeDetector = $mimeTypeDetector;
100 12
	}
101
102
	/**
103
	 * @NoAdminRequired
104
	 * @NoCSRFRequired
105
	 *
106
	 * @param int $accountId
107
	 * @param string $folderId
108
	 * @param int $from
109
	 * @param int $to
110
	 * @param string $filter
111
	 * @param array $ids
112
	 * @return JSONResponse
113
	 */
114
	public function index($accountId, $folderId, $from=0, $to=20, $filter=null, $ids=null) {
115
		if (!is_null($ids)) {
116
			$ids = explode(',', $ids);
117
118
			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...
119
		}
120
		$mailBox = $this->getFolder($accountId, $folderId);
121
122
		$this->logger->debug("loading messages $from to $to of folder <$folderId>");
123
124
		$json = $mailBox->getMessages($from, $to-$from+1, $filter);
125
126
		$ci = $this->contactsIntegration;
127
		$json = array_map(function($j) use ($ci, $mailBox) {
128
			if ($mailBox->getSpecialRole() === 'trash') {
129
				$j['delete'] = (string)$this->l10n->t('Delete permanently');
130
			}
131
132
			if ($mailBox->getSpecialRole() === 'sent') {
133
				$j['fromEmail'] = $j['toEmail'];
134
				$j['from'] = $j['to'];
135
				if((count($j['toList']) > 1) || (count($j['ccList']) > 0)) {
136
					$j['from'] .= ' ' . $this->l10n->t('& others');
137
				}
138
			}
139
140
			$j['senderImage'] = $ci->getPhoto($j['fromEmail']);
141
			return $j;
142
		}, $json);
143
144
		return new JSONResponse($json);
145
	}
146
147
	private function loadMessage($accountId, $folderId, $id) {
148
		$account = $this->getAccount($accountId);
149
		$mailBox = $account->getMailbox(base64_decode($folderId));
150
		$m = $mailBox->getMessage($id);
151
152
		$json = $this->enhanceMessage($accountId, $folderId, $id, $m, $account, $mailBox);
153
154
		// Unified inbox hack
155
		$messageId = $id;
156
		if ($accountId === UnifiedAccount::ID) {
157
			// Add accountId, folderId for unified inbox replies
158
			list($accountId, $messageId) = json_decode(base64_decode($id));
159
			$account = $this->getAccount($accountId);
160
			$inbox = $account->getInbox();
161
			$folderId = base64_encode($inbox->getFolderId());
162
		}
163
		$json['messageId'] = $messageId;
164
		$json['accountId'] = $accountId;
165
		$json['folderId'] = $folderId;
166
		// End unified inbox hack
167
168
		return $json;
169
	}
170
171
	/**
172
	 * @NoAdminRequired
173
	 * @NoCSRFRequired
174
	 *
175
	 * @param int $accountId
176
	 * @param string $folderId
177
	 * @param mixed $id
178
	 * @return JSONResponse
179
	 */
180
	public function show($accountId, $folderId, $id) {
181
		try {
182
			$json = $this->loadMessage($accountId, $folderId, $id);
183
		} 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...
184
			return new JSONResponse([], 404);
185
		}
186
		return new JSONResponse($json);
187
	}
188
189
	/**
190
	 * @NoAdminRequired
191
	 * @NoCSRFRequired
192
	 *
193
	 * @param int $accountId
194
	 * @param string $folderId
195
	 * @param string $messageId
196
	 * @return \OCA\Mail\Http\HtmlResponse
197
	 */
198 1
	public function getHtmlBody($accountId, $folderId, $messageId) {
199
		try {
200 1
			$mailBox = $this->getFolder($accountId, $folderId);
201
202 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...
203
			$html = $m->getHtmlBody($accountId, $folderId, $messageId, function($cid) use ($m){
204
				$match = array_filter($m->attachments, function($a) use($cid){
205
					return $a['cid'] === $cid;
206
				});
207
				$match = array_shift($match);
208
				if (is_null($match)) {
209
					return null;
210
				}
211
				return $match['id'];
212 1
			});
213
214 1
			$htmlResponse = new HtmlResponse($html);
215
216
			// Harden the default security policy
217
			// FIXME: Remove once ownCloud 8.1 is a requirement for the mail app
218 1 View Code Duplication
			if(class_exists('\OCP\AppFramework\Http\ContentSecurityPolicy')) {
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...
219
				$policy = new ContentSecurityPolicy();
220
				$policy->allowEvalScript(false);
221
				$policy->disallowScriptDomain('\'self\'');
222
				$policy->disallowConnectDomain('\'self\'');
223
				$policy->disallowFontDomain('\'self\'');
224
				$policy->disallowMediaDomain('\'self\'');
225
				$htmlResponse->setContentSecurityPolicy($policy);
226
			}
227
228
			// Enable caching
229 1
			$htmlResponse->cacheFor(60 * 60);
230 1
			$htmlResponse->addHeader('Pragma', 'cache');
231
232 1
			return $htmlResponse;
233
		} catch(\Exception $ex) {
234
			return new TemplateResponse($this->appName, 'error', ['message' => $ex->getMessage()], 'none');
235
		}
236
	}
237
238
	/**
239
	 * @NoAdminRequired
240
	 * @NoCSRFRequired
241
	 *
242
	 * @param int $accountId
243
	 * @param string $folderId
244
	 * @param string $messageId
245
	 * @param string $attachmentId
246
	 * @return AttachmentDownloadResponse
247
	 */
248 1
	public function downloadAttachment($accountId, $folderId, $messageId, $attachmentId) {
249 1
		$mailBox = $this->getFolder($accountId, $folderId);
250
251 1
		$attachment = $mailBox->getAttachment($messageId, $attachmentId);
252
253 1
		return new AttachmentDownloadResponse(
254 1
			$attachment->getContents(),
255 1
			$attachment->getName(),
256 1
			$attachment->getType());
257
	}
258
259
	/**
260
	 * @NoAdminRequired
261
	 * @NoCSRFRequired
262
	 *
263
	 * @param int $accountId
264
	 * @param string $folderId
265
	 * @param string $messageId
266
	 * @param string $attachmentId
267
	 * @param string $targetPath
268
	 * @return JSONResponse
269
	 */
270 2
	public function saveAttachment($accountId, $folderId, $messageId, $attachmentId, $targetPath) {
271 2
		$mailBox = $this->getFolder($accountId, $folderId);
272
273 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...
274 2
		if($attachmentId === 0) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $attachmentId (string) and 0 (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
275
			// Save all attachments
276 1
			$m = $mailBox->getMessage($messageId);
277
			$attachmentIds = array_map(function($a){
278 1
				return $a['id'];
279 1
			}, $m->attachments);
280 1
		} else {
281 1
			$attachmentIds = [$attachmentId];
282
		}
283
284 2
		foreach($attachmentIds as $attachmentId) {
285 2
			$attachment = $mailBox->getAttachment($messageId, $attachmentId);
286
287 2
			$fileName = $attachment->getName();
288 2
			$fileParts = pathinfo($fileName);
289 2
			$fileName = $fileParts['filename'];
290 2
			$fileExtension = $fileParts['extension'];
291 2
			$fullPath = "$targetPath/$fileName.$fileExtension";
292 2
			$counter = 2;
293 2
			while($this->userFolder->nodeExists($fullPath)) {
294
				$fullPath = "$targetPath/$fileName ($counter).$fileExtension";
295
				$counter++;
296
			}
297
298 2
			$newFile = $this->userFolder->newFile($fullPath);
299 2
			$newFile->putContent($attachment->getContents());
300 2
		}
301
302 2
		return new JSONResponse();
303
	}
304
305
	/**
306
	 * @NoAdminRequired
307
	 *
308
	 * @param int $accountId
309
	 * @param string $folderId
310
	 * @param string $messageId
311
	 * @param array $flags
312
	 * @return JSONResponse
313
	 */
314 2
	public function setFlags($accountId, $folderId, $messageId, $flags) {
315 2
		$mailBox = $this->getFolder($accountId, $folderId);
316
317 2
		foreach($flags as $flag => $value) {
318 2
			$value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
319 2
			if ($flag === 'unseen') {
320 1
				$flag = 'seen';
321 1
				$value = !$value;
322 1
			}
323 2
			$mailBox->setMessageFlag($messageId, '\\'.$flag, $value);
324 2
		}
325
326 2
		return new JSONResponse();
327
	}
328
329
	/**
330
	 * @NoAdminRequired
331
	 *
332
	 * @param int $accountId
333
	 * @param string $folderId
334
	 * @param string $id
335
	 * @return JSONResponse
336
	 */
337 3
	public function destroy($accountId, $folderId, $id) {
338 3
		$this->logger->debug("deleting message <$id> of folder <$folderId>, account <$accountId>");
339
		try {
340 3
			$account = $this->getAccount($accountId);
341 2
			$account->deleteMessage(base64_decode($folderId), $id);
342 1
			return new JSONResponse();
343
344 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...
345 2
			$this->logger->error("could not delete message <$id> of folder <$folderId>, "
346 2
				. "account <$accountId> because it does not exist");
347 2
			return new JSONResponse([], Http::STATUS_NOT_FOUND);
348
		}
349
	}
350
351
	/**
352
	 * @param int $accountId
353
	 * @return \OCA\Mail\Service\IAccount
354
	 */
355 9
	private function getAccount($accountId) {
356 9
		if (!array_key_exists($accountId, $this->accounts)) {
357 9
			$this->accounts[$accountId] = $this->accountService->find($this->currentUserId, $accountId);
358 8
		}
359 8
		return $this->accounts[$accountId];
360
	}
361
362
	/**
363
	 * @param int $accountId
364
	 * @param string $folderId
365
	 * @return IMailBox
366
	 */
367 6
	private function getFolder($accountId, $folderId) {
368 6
		$account = $this->getAccount($accountId);
369 6
		return $account->getMailbox(base64_decode($folderId));
370
	}
371
372
	/**
373
	 * @param string $messageId
374
	 * @param $accountId
375
	 * @param $folderId
376
	 * @return callable
377
	 */
378
	private function enrichDownloadUrl($accountId, $folderId, $messageId, $attachment) {
379
		$downloadUrl = \OCP\Util::linkToRoute('mail.messages.downloadAttachment', [
380
			'accountId' => $accountId,
381
			'folderId' => $folderId,
382
			'messageId' => $messageId,
383
			'attachmentId' => $attachment['id'],
384
		]);
385
		$downloadUrl = \OC::$server->getURLGenerator()->getAbsoluteURL($downloadUrl);
386
		$attachment['downloadUrl'] = $downloadUrl;
387
		$attachment['mimeUrl'] = $this->mimeTypeDetector->mimeTypeIcon($attachment['mime']);
388
389
		if ($this->attachmentIsImage($attachment)) {
390
			$attachment['isImage'] = true;
391
		}
392
		return $attachment;
393
	}
394
395
	/**
396
	 * @param $attachment
397
	 *
398
	 * Determines if the content of this attachment is an image
399
	 */
400
	private function attachmentIsImage($attachment) {
401
		return in_array($attachment['mime'], array('image/jpeg',
402
			'image/png',
403
			'image/gif'));
404
	}
405
406
	/**
407
	 * @param string $accountId
408
	 * @param string $folderId
409
	 * @param string $messageId
410
	 * @return string
411
	 */
412
	private function buildHtmlBodyUrl($accountId, $folderId, $messageId) {
413
		$htmlBodyUrl = \OC::$server->getURLGenerator()->linkToRoute('mail.messages.getHtmlBody', [
414
			'accountId' => $accountId,
415
			'folderId' => $folderId,
416
			'messageId' => $messageId,
417
		]);
418
		return \OC::$server->getURLGenerator()->getAbsoluteURL($htmlBodyUrl);
419
	}
420
421
	private function loadMultiple($accountId, $folderId, $ids) {
422
		$messages = array_map(function($id) use ($accountId, $folderId){
423
			try {
424
				return $this->loadMessage($accountId, $folderId, $id);
425
			} 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...
426
				return null;
427
			}
428
		}, $ids);
429
430
		return $messages;
431
	}
432
433
	/**
434
	 * @param $accountId
435
	 * @param $folderId
436
	 * @param $id
437
	 * @param $m
438
	 * @param IAccount $account
439
	 * @param $mailBox
440
	 * @return mixed
441
	 */
442
	private function enhanceMessage($accountId, $folderId, $id, $m, IAccount $account, $mailBox) {
443
		$json = $m->getFullMessage($account->getEmail(), $mailBox->getSpecialRole());
444
		$json['senderImage'] = $this->contactsIntegration->getPhoto($m->getFromEmail());
445
		if (isset($json['hasHtmlBody'])) {
446
			$json['htmlBodyUrl'] = $this->buildHtmlBodyUrl($accountId, $folderId, $id);
447
		}
448
449
		if (isset($json['attachment'])) {
450
			$json['attachment'] = $this->enrichDownloadUrl($accountId, $folderId, $id, $json['attachment']);
451
		}
452
		if (isset($json['attachments'])) {
453
			$json['attachments'] = array_map(function ($a) use ($accountId, $folderId, $id) {
454
				return $this->enrichDownloadUrl($accountId, $folderId, $id, $a);
455
			}, $json['attachments']);
456
457
			// show images first
458
			usort($json['attachments'], function($a, $b) {
459
				if (isset($a['isImage']) && !isset($b['isImage'])) {
460
					return -1;
461
				} elseif (!isset($a['isImage']) && isset($b['isImage'])) {
462
					return 1;
463
				} else {
464
					Util::naturalSortCompare($a['fileName'], $b['fileName']);
465
				}
466
			});
467
			return $json;
468
		}
469
		return $json;
470
	}
471
472
}
473