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

MailService::sendNotifications()   F

Complexity

Conditions 19
Paths 240

Size

Total Lines 118
Code Lines 85

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 380

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 85
c 1
b 0
f 1
dl 0
loc 118
ccs 0
cts 106
cp 0
rs 3.1833
cc 19
nc 240
nop 0
crap 380

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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