Completed
Pull Request — master (#1128)
by René
04:25
created

MailService   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 400
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 3
Bugs 1 Features 1
Metric Value
wmc 41
eloc 228
c 3
b 1
f 1
dl 0
loc 400
rs 9.1199
ccs 0
cts 291
cp 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
F sendNotifications() 0 118 19
A sendInvitationMail() 0 61 3
A resolveEmailAddress() 0 15 4
B getRecipientsByShare() 0 74 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\Db\Log;
44
use OCA\Polls\Model\Contact;
45
use OCA\Polls\Model\Email;
46
use OCA\Polls\Model\Group;
47
use OCA\Polls\Model\User;
48
49
class MailService {
50
51
	/** @var IUserManager */
52
	private $userManager;
53
54
	/** @var IGroupManager */
55
	private $groupManager;
56
57
	/** @var IConfig */
58
	private $config;
59
60
	/** @var IURLGenerator */
61
	private $urlGenerator;
62
63
	/** @var IL10N */
64
	private $trans;
65
66
	/** @var IFactory */
67
	private $transFactory;
68
69
	/** @var IMailer */
70
	private $mailer;
71
72
	/** @var SubscriptionMapper */
73
	private $subscriptionMapper;
74
75
	/** @var ShareMapper */
76
	private $shareMapper;
77
78
	/** @var PollMapper */
79
	private $pollMapper;
80
81
	/** @var LogMapper */
82
	private $logMapper;
83
84
	/**
85
	 * MailService constructor.
86
	 * @param IUserManager $userManager
87
	 * @param IGroupManager $groupManager
88
	 * @param IConfig $config
89
	 * @param IURLGenerator $urlGenerator
90
	 * @param IL10N $trans
91
	 * @param IFactory $transFactory
92
	 * @param IMailer $mailer
93
	 * @param SubscriptionMapper $subscriptionMapper
94
	 * @param ShareMapper $shareMapper
95
	 * @param PollMapper $pollMapper
96
	 * @param LogMapper $logMapper
97
	 */
98
99
	public function __construct(
100
		IUserManager $userManager,
101
		IGroupManager $groupManager,
102
		IConfig $config,
103
		IURLGenerator $urlGenerator,
104
		IL10N $trans,
105
		IFactory $transFactory,
106
		IMailer $mailer,
107
		ShareMapper $shareMapper,
108
		SubscriptionMapper $subscriptionMapper,
109
		PollMapper $pollMapper,
110
		LogMapper $logMapper
111
	) {
112
		$this->config = $config;
113
		$this->userManager = $userManager;
114
		$this->groupManager = $groupManager;
115
		$this->urlGenerator = $urlGenerator;
116
		$this->trans = $trans;
117
		$this->transFactory = $transFactory;
118
		$this->mailer = $mailer;
119
		$this->shareMapper = $shareMapper;
120
		$this->subscriptionMapper = $subscriptionMapper;
121
		$this->pollMapper = $pollMapper;
122
		$this->logMapper = $logMapper;
123
	}
124
125
126
	/**
127
	 * sendMail - Send eMail and evaluate recipient's mail address
128
	 * and displayname if $userId is a site user
129
	 * @param IEmailTemplate $emailTemplate
130
	 * @param String $userId
131
	 * @param String $emailAddress, ignored, when $userId is set
132
	 * @param String $displayName, ignored, when $userId is set
133
	 * @return String
134
	 */
135
136
	private function sendMail($emailTemplate, $userId = '', $emailAddress = '', $displayName = '') {
137
		if ($this->userManager->get($userId) instanceof IUser) {
138
			$emailAddress = \OC::$server->getConfig()->getUserValue($userId, 'settings', 'email');
139
			$displayName = $this->userManager->get($userId)->getDisplayName();
140
		}
141
142
		if (!$emailAddress || !filter_var($emailAddress, FILTER_VALIDATE_EMAIL)) {
143
			throw new Exception('Invalid email address (' . $emailAddress . ')');
144
		}
145
146
		try {
147
			$message = $this->mailer->createMessage();
148
			$message->setTo([$emailAddress => $displayName]);
149
			$message->useTemplate($emailTemplate);
150
			$this->mailer->send($message);
151
152
			return null;
153
		} catch (\Exception $e) {
154
			\OC::$server->getLogger()->logException($e, ['app' => 'polls']);
155
			throw $e;
156
		}
157
	}
158
159
160
	/**
161
	 * @param integer $pollId
162
	 * @param string $userId
163
	 * @return string
164
	 */
165
	public function resolveEmailAddress($pollId, $userId) {
166
		if ($this->userManager->get($userId) instanceof IUser) {
167
			return \OC::$server->getConfig()->getUserValue($userId, 'settings', 'email');
168
		}
169
170
		// if $userId is no site user, eval via shares
171
		try {
172
			$share = $this->shareMapper->findByPollAndUser($pollId, $userId);
173
			if ($share->getUserEmail()) {
174
				return $share->getUserEmail();
175
			}
176
		} catch (\Exception $e) {
177
			// catch silently
178
		}
179
		return $userId;
180
	}
181
182
183
	/**
184
	 * @param Share $share
185
	 * @param String $defaultLang
186
	 * @param String $skipUser
187
	 * @return Array $recipients
188
	 */
189
	private function getRecipientsByShare($share, $defaultLang = 'en', $skipUser = null) {
190
		$recipients = [];
191
192
		$tokenLink = $this->urlGenerator->getAbsoluteURL(
193
			$this->urlGenerator->linkToRoute(
194
				'polls.page.vote_publicpublic',
195
				['token' => $share->getToken()]
196
			)
197
		);
198
199
		$internalLink = $this->urlGenerator->getAbsoluteURL(
200
			$this->urlGenerator->linkToRoute(
201
				'polls.page.indexvote',
202
				['id' => $share->getPollId()]
203
			)
204
		);
205
		switch ($share->getType()) {
206
			case Share::TYPE_USER:
207
				$user = new User($share->getUserId());
208
				$recipients[] = [
209
					'userId' => $user->getId(),
210
					'eMailAddress' => $user->getEmailAddress(),
211
					'displayName' => $user->getDisplayName(),
212
					'language' => $user->getLanguage(),
213
					'link' => $internalLink,
214
				];
215
				break;
216
			case Share::TYPE_EMAIL:
217
				$user = new Email($share->getUserId());
218
219
				$recipients[] = [
220
					'userId' => $user->getId(),
221
					'eMailAddress' => $user->getEmailAddress(),
222
					'displayName' => $user->getDisplayName(),
223
					'language' => $defaultLang,
224
					'link' => $tokenLink,
225
				];
226
				break;
227
			case Share::TYPE_CONTACT:
228
				$user = new Contact($share->getUserId());
229
230
				$recipients[] = [
231
					'userId' => $user->getId(),
232
					'eMailAddress' => $user->getEmailAddress(),
233
					'displayName' => $user->getDisplayname(),
234
					'language' => $defaultLang,
235
					'link' => $tokenLink,
236
				];
237
				break;
238
			case Share::TYPE_EXTERNAL:
239
				$recipients[] = [
240
					'userId' => $share->getUserId(),
241
					'eMailAddress' => $share->getUserEmail(),
242
					'displayName' => $share->getUserId(),
243
					'language' => $defaultLang,
244
					'link' => $tokenLink,
245
				];
246
				break;
247
			case Share::TYPE_GROUP:
248
				foreach ((new Group($share->getUserId()))->getMembers() as $user) {
249
					if ($skipUser === $user->getId() || $user->getUserIsDisabled()) {
250
						continue;
251
					}
252
253
					$recipients[] = [
254
						'userId' => $user->getId(),
255
						'eMailAddress' => $user->getEmailAddress(),
256
						'displayName' => $user->getDisplayName(),
257
						'language' => $user->getLanguage(),
258
						'link' => $internalLink,
259
					];
260
				}
261
		}
262
		return $recipients;
263
	}
264
265
	/**
266
	 * @param string $token
267
	 */
268
	public function sendInvitationMail($token) {
269
		$share = $this->shareMapper->findByToken($token);
270
		$poll = $this->pollMapper->find($share->getPollId());
271
		$owner = $this->userManager->get($poll->getOwner());
272
		$sentMails = [];
273
		$abortedMails = [];
274
275
		$recipients = $this->getRecipientsByShare(
276
			$this->shareMapper->findByToken($token),
277
			$this->config->getUserValue($poll->getOwner(), 'core', 'lang'),
278
			$poll->getOwner()
279
		);
280
281
		foreach ($recipients as $recipient) {
282
			$trans = $this->transFactory->get('polls', $recipient['language']);
283
284
285
			$emailTemplate = $this->mailer->createEMailTemplate('polls.Invitation', [
286
				'owner' => $owner->getDisplayName(),
287
				'title' => $poll->getTitle(),
288
				'link' => $recipient['link']
289
			]);
290
291
			$emailTemplate->setSubject($trans->t('Poll invitation "%s"', $poll->getTitle()));
292
			$emailTemplate->addHeader();
293
			$emailTemplate->addHeading($trans->t('Poll invitation "%s"', $poll->getTitle()), false);
294
295
			$emailTemplate->addBodyText(str_replace(
296
				['{owner}', '{title}'],
297
				[$owner->getDisplayName(), $poll->getTitle()],
298
				$trans->t('{owner} invited you to take part in the poll "{title}"')
299
			));
300
301
			$emailTemplate->addBodyText($poll->getDescription());
302
303
			$emailTemplate->addBodyButton(
304
				htmlspecialchars($trans->t('Go to poll')),
305
				$recipient['link']
306
			);
307
308
			$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: '));
309
			$emailTemplate->addBodyText($recipient['link']);
310
311
			$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.'));
312
313
			try {
314
				$this->sendMail(
315
					$emailTemplate,
316
					$recipient['userId'],
317
					$recipient['eMailAddress'],
318
					$recipient['displayName']
319
				);
320
				$share->setInvitationSent(time());
321
				$this->shareMapper->update($share);
322
				$sentMails[] = $recipient;
323
			} catch (Exception $e) {
324
				$abortedMails[] = $recipient;
325
				\OC::$server->getLogger()->alert('Error sending Mail to ' . json_encode($recipient));
326
			}
327
		}
328
		return ['sentMails' => $sentMails, 'abortedMails' => $abortedMails];
329
	}
330
331
	public function sendNotifications() {
332
		$subscriptions = [];
333
		$log = $this->logMapper->findUnprocessedPolls();
334
335
		foreach ($log as $logItem) {
336
			$subscriptions = array_merge($subscriptions, $this->subscriptionMapper->findAllByPoll($logItem->getPollId()));
337
		}
338
339
		$log = $this->logMapper->findUnprocessed();
340
341
		foreach ($subscriptions as $subscription) {
342
			$poll = $this->pollMapper->find($subscription->getPollId());
343
			$emailAddress = '';
344
			$displayName = '';
345
346
			if ($this->userManager->get($subscription->getUserId()) instanceof IUser) {
347
				$lang = $this->config->getUserValue($subscription->getUserId(), 'core', 'lang');
348
			} else {
349
				try {
350
					$emailAddress = $this->shareMapper->findByPollAndUser($subscription->getPollId(), $subscription->getUserId())->getUserEmail();
351
					$displayName = $subscription->getUserId();
352
					$lang = $this->config->getUserValue($poll->getOwner(), 'core', 'lang');
353
				} catch (\Exception $e) {
354
					continue;
355
				}
356
			}
357
358
			$trans = $this->transFactory->get('polls', $lang);
359
360
			$url = $this->urlGenerator->getAbsoluteURL(
361
				$this->urlGenerator->linkToRoute(
362
					'polls.page.indexvote',
363
					['id' => $subscription->getPollId()]
364
				)
365
			);
366
367
			$emailTemplate = $this->mailer->createEMailTemplate('polls.Invitation', [
368
				'title' => $poll->getTitle(),
369
				'link' => $url
370
			]);
371
			$emailTemplate->setSubject($trans->t('Polls App - New Activity'));
372
			$emailTemplate->addHeader();
373
			$emailTemplate->addHeading($trans->t('Polls App - New Activity'), false);
374
			$emailTemplate->addBodyText(str_replace(
375
				['{title}'],
376
				[$poll->getTitle()],
377
				$trans->t('"{title}" had recent activity: ')
378
			));
379
380
			foreach ($log as $logItem) {
381
				if ($logItem->getPollId() === $subscription->getPollId()) {
382
					if ($poll->getAnonymous() || $poll->getShowResults() !== "always") {
383
						$displayUser = $trans->t('A user');
384
					} elseif ($this->userManager->get($logItem->getUserId()) instanceof IUser) {
385
						$displayUser = $this->userManager->get($logItem->getUserId())->getDisplayName();
386
					} else {
387
						$displayUser = $logItem->getUserId();
388
					}
389
390
					if ($logItem->getMessage()) {
391
						$emailTemplate->addBodyText($logItem->getMessage());
392
					} elseif ($logItem->getMessageId() === Log::MSG_ID_SETVOTE) {
393
						$emailTemplate->addBodyText($trans->t(
394
							'- %s voted.',
395
							[$displayUser]
396
						));
397
					} elseif ($logItem->getMessageId() === Log::MSG_ID_UPDATEPOLL) {
398
						$emailTemplate->addBodyText($trans->t(
399
							'- %s updated the poll configuration. Please check your votes.',
400
							[$displayUser]
401
						));
402
					} elseif ($logItem->getMessageId() === Log::MSG_ID_DELETEPOLL) {
403
						$emailTemplate->addBodyText($trans->t(
404
							'- %s deleted the poll.',
405
							[$displayUser]
406
						));
407
					} elseif ($logItem->getMessageId() === Log::MSG_ID_RESTOREPOLL) {
408
						$emailTemplate->addBodyText($trans->t(
409
							'- %s restored the poll.',
410
							[$displayUser]
411
						));
412
					} elseif ($logItem->getMessageId() === Log::MSG_ID_EXPIREPOLL) {
413
						$emailTemplate->addBodyText($trans->t(
414
							'- The poll expired.',
415
							[$displayUser]
416
						));
417
					} elseif ($logItem->getMessageId() === Log::MSG_ID_ADDOPTION) {
418
						$emailTemplate->addBodyText($trans->t(
419
							'- %s added a vote option.',
420
							[$displayUser]
421
						));
422
					} elseif ($logItem->getMessageId() === Log::MSG_ID_DELETEOPTION) {
423
						$emailTemplate->addBodyText($trans->t(
424
							'- %s removed a vote option.',
425
							[$displayUser]
426
						));
427
					} else {
428
						$emailTemplate->addBodyText(
429
							$logItem->getMessageId() . " (" . $displayUser . ")"
430
						);
431
					}
432
				}
433
434
				$logItem->setProcessed(time());
435
				$this->logMapper->update($logItem);
436
			}
437
438
			$emailTemplate->addBodyButton(
439
				htmlspecialchars($trans->t('Go to poll')),
440
				$url,
441
				/** @scrutinizer ignore-type */ false
442
			);
443
			$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.'));
444
445
			try {
446
				$this->sendMail($emailTemplate, $subscription->getUserId(), $emailAddress, $displayName);
447
			} catch (Exception $e) {
448
				\OC::$server->getLogger()->alert('Error sending Mail to ' . $subscription->getUserId());
449
			}
450
		}
451
	}
452
}
453