Passed
Pull Request — master (#1128)
by René
04:45
created

MailService   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 396
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 4
Bugs 1 Features 1
Metric Value
wmc 41
eloc 223
dl 0
loc 396
ccs 0
cts 286
cp 0
rs 9.1199
c 4
b 1
f 1

6 Methods

Rating   Name   Duplication   Size   Complexity  
A sendInvitationMail() 0 61 3
F sendNotifications() 0 118 19
A resolveEmailAddress() 0 15 4
B getRecipientsByShare() 0 70 9
A sendMail() 0 20 5
A __construct() 0 24 1

How to fix   Complexity   

Complex Class

Complex classes like MailService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MailService, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @copyright Copyright (c) 2017 Vinzenz Rosenkranz <[email protected]>
4
 *
5
 * @author René Gieling <[email protected]>
6
 *
7
 * @license GNU AGPL version 3 or any later version
8
 *
9
 *  This program is free software: you can redistribute it and/or modify
10
 *  it under the terms of the GNU Affero General Public License as
11
 *  published by the Free Software Foundation, either version 3 of the
12
 *  License, or (at your option) any later version.
13
 *
14
 *  This program is distributed in the hope that it will be useful,
15
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 *  GNU Affero General Public License for more details.
18
 *
19
 *  You should have received a copy of the GNU Affero General Public License
20
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 *
22
 */
23
24
namespace OCA\Polls\Service;
25
26
use Exception;
27
28
use OCP\IUser;
29
use OCP\IUserManager;
30
use OCP\IGroupManager;
31
use OCP\IConfig;
32
use OCP\IURLGenerator;
33
use OCP\IL10N;
34
use OCP\L10N\IFactory;
35
use OCP\Mail\IMailer;
36
use OCP\Mail\IEMailTemplate;
37
38
use OCA\Polls\Db\SubscriptionMapper;
39
use OCA\Polls\Db\PollMapper;
40
use OCA\Polls\Db\ShareMapper;
41
use OCA\Polls\Db\Share;
42
use OCA\Polls\Db\LogMapper;
43
use OCA\Polls\Model\User;
44
use OCA\Polls\Model\Contact;
45
use OCA\Polls\Model\Group;
46
47
class MailService {
48
49
	/** @var IUserManager */
50
	private $userManager;
51
52
	/** @var IGroupManager */
53
	private $groupManager;
54
55
	/** @var IConfig */
56
	private $config;
57
58
	/** @var IURLGenerator */
59
	private $urlGenerator;
60
61
	/** @var IL10N */
62
	private $trans;
63
64
	/** @var IFactory */
65
	private $transFactory;
66
67
	/** @var IMailer */
68
	private $mailer;
69
70
	/** @var SubscriptionMapper */
71
	private $subscriptionMapper;
72
73
	/** @var ShareMapper */
74
	private $shareMapper;
75
76
	/** @var PollMapper */
77
	private $pollMapper;
78
79
	/** @var LogMapper */
80
	private $logMapper;
81
82
	/**
83
	 * MailService constructor.
84
	 * @param IUserManager $userManager
85
	 * @param IGroupManager $groupManager
86
	 * @param IConfig $config
87
	 * @param IURLGenerator $urlGenerator
88
	 * @param IL10N $trans
89
	 * @param IFactory $transFactory
90
	 * @param IMailer $mailer
91
	 * @param SubscriptionMapper $subscriptionMapper
92
	 * @param ShareMapper $shareMapper
93
	 * @param PollMapper $pollMapper
94
	 * @param LogMapper $logMapper
95
	 */
96
97
	public function __construct(
98
		IUserManager $userManager,
99
		IGroupManager $groupManager,
100
		IConfig $config,
101
		IURLGenerator $urlGenerator,
102
		IL10N $trans,
103
		IFactory $transFactory,
104
		IMailer $mailer,
105
		ShareMapper $shareMapper,
106
		SubscriptionMapper $subscriptionMapper,
107
		PollMapper $pollMapper,
108
		LogMapper $logMapper
109
	) {
110
		$this->config = $config;
111
		$this->userManager = $userManager;
112
		$this->groupManager = $groupManager;
113
		$this->urlGenerator = $urlGenerator;
114
		$this->trans = $trans;
115
		$this->transFactory = $transFactory;
116
		$this->mailer = $mailer;
117
		$this->shareMapper = $shareMapper;
118
		$this->subscriptionMapper = $subscriptionMapper;
119
		$this->pollMapper = $pollMapper;
120
		$this->logMapper = $logMapper;
121
	}
122
123
124
	/**
125
	 * sendMail - Send eMail and evaluate recipient's mail address
126
	 * and displayname if $userId is a site user
127
	 * @param IEmailTemplate $emailTemplate
128
	 * @param String $userId
129
	 * @param String $emailAddress, ignored, when $userId is set
130
	 * @param String $displayName, ignored, when $userId is set
131
	 * @return String
132
	 */
133
134
	private function sendMail($emailTemplate, $userId = '', $emailAddress = '', $displayName = '') {
135
		if ($this->userManager->get($userId) instanceof IUser) {
136
			$emailAddress = \OC::$server->getConfig()->getUserValue($userId, 'settings', 'email');
137
			$displayName = $this->userManager->get($userId)->getDisplayName();
138
		}
139
140
		if (!$emailAddress || !filter_var($emailAddress, FILTER_VALIDATE_EMAIL)) {
141
			throw new Exception('Invalid email address (' . $emailAddress . ')');
142
		}
143
144
		try {
145
			$message = $this->mailer->createMessage();
146
			$message->setTo([$emailAddress => $displayName]);
147
			$message->useTemplate($emailTemplate);
148
			$this->mailer->send($message);
149
150
			return null;
151
		} catch (\Exception $e) {
152
			\OC::$server->getLogger()->logException($e, ['app' => 'polls']);
153
			throw $e;
154
		}
155
	}
156
157
158
	/**
159
	 * @param integer $pollId
160
	 * @param string $userId
161
	 * @return string
162
	 */
163
	public function resolveEmailAddress($pollId, $userId) {
164
		if ($this->userManager->get($userId) instanceof IUser) {
165
			return \OC::$server->getConfig()->getUserValue($userId, 'settings', 'email');
166
		}
167
168
		// if $userId is no site user, eval via shares
169
		try {
170
			$share = $this->shareMapper->findByPollAndUser($pollId, $userId);
171
			if ($share->getUserEmail()) {
172
				return $share->getUserEmail();
173
			}
174
		} catch (\Exception $e) {
175
			// catch silently
176
		}
177
		return $userId;
178
	}
179
180
181
	/**
182
	 * @param Share $share
183
	 * @param String $defaultLang
184
	 * @param String $skipUser
185
	 * @return Array $recipients
186
	 */
187
	private function getRecipientsByShare($share, $defaultLang = 'en', $skipUser = null) {
188
		$recipients = [];
189
190
		$tokenLink = $this->urlGenerator->getAbsoluteURL(
191
			$this->urlGenerator->linkToRoute(
192
				'polls.page.vote_publicpublic',
193
				['token' => $share->getToken()]
194
			)
195
		);
196
197
		$internalLink = $this->urlGenerator->getAbsoluteURL(
198
			$this->urlGenerator->linkToRoute(
199
				'polls.page.indexvote',
200
				['id' => $share->getPollId()]
201
			)
202
		);
203
204
		if ($share->getType() === Share::TYPE_USER) {
205
			$user = new User($share->getUserId());
206
			$recipients[] = [
207
				'userId' => $user->getUserId(),
208
				'eMailAddress' => $user->getEmailAddress(),
209
				'displayName' => $user->getDisplayName(),
210
				'language' => $user->getLanguage(),
211
				'link' => $internalLink,
212
			];
213
		} elseif ($share->getType() === Share::TYPE_EMAIL) {
214
			$user = new Email($share->getUserId());
0 ignored issues
show
Bug introduced by
The type OCA\Polls\Service\Email was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
215
216
			$recipients[] = [
217
				'userId' => $user->getUserId(),
218
				'eMailAddress' => $user->getEmailAddress(),
219
				'displayName' => $user->getDisplayName(),
220
				'language' => $defaultLang,
221
				'link' => $tokenLink,
222
			];
223
		} elseif ($share->getType() === Share::TYPE_CONTACT) {
224
			$user = new Contact($share->getUserId());
225
226
			$recipients[] = [
227
				'userId' => $user->getUserId(),
0 ignored issues
show
Bug introduced by
The method getUserId() does not exist on OCA\Polls\Model\Contact. Did you maybe mean getUser()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

227
				'userId' => $user->/** @scrutinizer ignore-call */ getUserId(),

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
228
				'eMailAddress' => $user->getEmailAddress(),
229
				'displayName' => $user->getDisplayname(),
230
				'language' => $defaultLang,
231
				'link' => $tokenLink,
232
			];
233
		} elseif ($share->getType() === Share::TYPE_EXTERNAL) {
234
			$recipients[] = [
235
				'userId' => $share->getUserId(),
236
				'eMailAddress' => $share->getUserEmail(),
237
				'displayName' => $share->getUserId(),
238
				'language' => $defaultLang,
239
				'link' => $tokenLink,
240
			];
241
		} elseif ($share->getType() === Share::TYPE_GROUP) {
242
			foreach ((new Group($share->getUserId()))->getMembers() as $user) {
243
				if ($skipUser === $user->getId() || !$user->isUserDisabled()) {
0 ignored issues
show
Bug introduced by
The method isUserDisabled() does not exist on OCA\Polls\Model\User. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

243
				if ($skipUser === $user->getId() || !$user->/** @scrutinizer ignore-call */ isUserDisabled()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
244
					continue;
245
				}
246
247
				$recipients[] = [
248
					'userId' => $user->getId(),
249
					'eMailAddress' => $user->getEmailAddress(),
250
					'displayName' => $user->getDisplayName(),
251
					'language' => $user->getLanguage(),
252
					'link' => $internalLink,
253
				];
254
			}
255
		}
256
		return $recipients;
257
	}
258
259
	/**
260
	 * @param string $token
261
	 */
262
	public function sendInvitationMail($token) {
263
		$share = $this->shareMapper->findByToken($token);
264
		$poll = $this->pollMapper->find($share->getPollId());
265
		$owner = $this->userManager->get($poll->getOwner());
266
		$sentMails = [];
267
		$abortedMails = [];
268
269
		$recipients = $this->getRecipientsByShare(
270
			$this->shareMapper->findByToken($token),
271
			$this->config->getUserValue($poll->getOwner(), 'core', 'lang'),
272
			$poll->getOwner()
273
		);
274
275
		foreach ($recipients as $recipient) {
276
			$trans = $this->transFactory->get('polls', $recipient['language']);
277
278
279
			$emailTemplate = $this->mailer->createEMailTemplate('polls.Invitation', [
280
				'owner' => $owner->getDisplayName(),
281
				'title' => $poll->getTitle(),
282
				'link' => $recipient['link']
283
			]);
284
285
			$emailTemplate->setSubject($trans->t('Poll invitation "%s"', $poll->getTitle()));
286
			$emailTemplate->addHeader();
287
			$emailTemplate->addHeading($trans->t('Poll invitation "%s"', $poll->getTitle()), false);
288
289
			$emailTemplate->addBodyText(str_replace(
290
				['{owner}', '{title}'],
291
				[$owner->getDisplayName(), $poll->getTitle()],
292
				$trans->t('{owner} invited you to take part in the poll "{title}"')
293
			));
294
295
			$emailTemplate->addBodyText($poll->getDescription());
296
297
			$emailTemplate->addBodyButton(
298
				htmlspecialchars($trans->t('Go to poll')),
299
				$recipient['link']
300
			);
301
302
			$emailTemplate->addBodyText($trans->t('This link gives you personal access to the poll named above. Press the button above or copy the following link and add it in your browser\'s location bar: '));
303
			$emailTemplate->addBodyText($recipient['link']);
304
305
			$emailTemplate->addFooter($trans->t('This email is sent to you, because you are invited to vote in this poll by the poll owner. At least your name or your email address is recorded in this poll. If you want to get removed from this poll, contact the site administrator or the initiator of this poll, where the mail is sent from.'));
306
307
			try {
308
				$this->sendMail(
309
					$emailTemplate,
310
					$recipient['userId'],
311
					$recipient['eMailAddress'],
312
					$recipient['displayName']
313
				);
314
				$share->setInvitationSent(time());
315
				$this->shareMapper->update($share);
316
				$sentMails[] = $recipient;
317
			} catch (Exception $e) {
318
				$abortedMails[] = $recipient;
319
				\OC::$server->getLogger()->alert('Error sending Mail to ' . json_encode($recipient));
320
			}
321
		}
322
		return ['sentMails' => $sentMails, 'abortedMails' => $abortedMails];
323
	}
324
325
	public function sendNotifications() {
326
		$subscriptions = [];
327
		$log = $this->logMapper->findUnprocessedPolls();
328
329
		foreach ($log as $logItem) {
330
			$subscriptions = array_merge($subscriptions, $this->subscriptionMapper->findAllByPoll($logItem->getPollId()));
331
		}
332
333
		$log = $this->logMapper->findUnprocessed();
334
335
		foreach ($subscriptions as $subscription) {
336
			$poll = $this->pollMapper->find($subscription->getPollId());
337
			$emailAddress = '';
338
			$displayName = '';
339
340
			if ($this->userManager->get($subscription->getUserId()) instanceof IUser) {
341
				$lang = $this->config->getUserValue($subscription->getUserId(), 'core', 'lang');
342
			} else {
343
				try {
344
					$emailAddress = $this->shareMapper->findByPollAndUser($subscription->getPollId(), $subscription->getUserId())->getUserEmail();
345
					$displayName = $subscription->getUserId();
346
					$lang = $this->config->getUserValue($poll->getOwner(), 'core', 'lang');
347
				} catch (\Exception $e) {
348
					continue;
349
				}
350
			}
351
352
			$trans = $this->transFactory->get('polls', $lang);
353
354
			$url = $this->urlGenerator->getAbsoluteURL(
355
				$this->urlGenerator->linkToRoute(
356
					'polls.page.indexvote',
357
					['id' => $subscription->getPollId()]
358
				)
359
			);
360
361
			$emailTemplate = $this->mailer->createEMailTemplate('polls.Invitation', [
362
				'title' => $poll->getTitle(),
363
				'link' => $url
364
			]);
365
			$emailTemplate->setSubject($trans->t('Polls App - New Activity'));
366
			$emailTemplate->addHeader();
367
			$emailTemplate->addHeading($trans->t('Polls App - New Activity'), false);
368
			$emailTemplate->addBodyText(str_replace(
369
				['{title}'],
370
				[$poll->getTitle()],
371
				$trans->t('"{title}" had recent activity: ')
372
			));
373
374
			foreach ($log as $logItem) {
375
				if ($logItem->getPollId() === $subscription->getPollId()) {
376
					if ($poll->getAnonymous() || $poll->getShowResults() !== "always") {
377
						$displayUser = $trans->t('A user');
378
					} elseif ($this->userManager->get($logItem->getUserId()) instanceof IUser) {
379
						$displayUser = $this->userManager->get($logItem->getUserId())->getDisplayName();
380
					} else {
381
						$displayUser = $logItem->getUserId();
382
					}
383
384
					if ($logItem->getMessage()) {
385
						$emailTemplate->addBodyText($logItem->getMessage());
386
					} elseif ($logItem->getMessageId() === Log::MSG_ID_SETVOTE) {
387
						$emailTemplate->addBodyText($trans->t(
388
							'- %s voted.',
389
							[$displayUser]
390
						));
391
					} elseif ($logItem->getMessageId() === Log::MSG_ID_UPDATEPOLL) {
392
						$emailTemplate->addBodyText($trans->t(
393
							'- %s updated the poll configuration. Please check your votes.',
394
							[$displayUser]
395
						));
396
					} elseif ($logItem->getMessageId() === Log::MSG_ID_DELETEPOLL) {
397
						$emailTemplate->addBodyText($trans->t(
398
							'- %s deleted the poll.',
399
							[$displayUser]
400
						));
401
					} elseif ($logItem->getMessageId() === Log::MSG_ID_RESTOREPOLL) {
402
						$emailTemplate->addBodyText($trans->t(
403
							'- %s restored the poll.',
404
							[$displayUser]
405
						));
406
					} elseif ($logItem->getMessageId() === Log::MSG_ID_EXPIREPOLL) {
407
						$emailTemplate->addBodyText($trans->t(
408
							'- The poll expired.',
409
							[$displayUser]
410
						));
411
					} elseif ($logItem->getMessageId() === Log::MSG_ID_ADDOPTION) {
412
						$emailTemplate->addBodyText($trans->t(
413
							'- %s added a vote option.',
414
							[$displayUser]
415
						));
416
					} elseif ($logItem->getMessageId() === Log::MSG_ID_DELETEOPTION) {
417
						$emailTemplate->addBodyText($trans->t(
418
							'- %s removed a vote option.',
419
							[$displayUser]
420
						));
421
					} else {
422
						$emailTemplate->addBodyText(
423
							$logItem->getMessageId() . " (" . $displayUser . ")"
424
						);
425
					}
426
				}
427
428
				$logItem->setProcessed(time());
429
				$this->logMapper->update($logItem);
430
			}
431
432
			$emailTemplate->addBodyButton(
433
				htmlspecialchars($trans->t('Go to poll')),
434
				$url,
435
				/** @scrutinizer ignore-type */ false
436
			);
437
			$emailTemplate->addFooter($trans->t('This email is sent to you, because you subscribed to notifications of this poll. To opt out, visit the poll and remove your subscription.'));
438
439
			try {
440
				$this->sendMail($emailTemplate, $subscription->getUserId(), $emailAddress, $displayName);
441
			} catch (Exception $e) {
442
				\OC::$server->getLogger()->alert('Error sending Mail to ' . $subscription->getUserId());
443
			}
444
		}
445
	}
446
}
447