Completed
Pull Request — master (#551)
by Maxence
02:04
created

MemberAdd::sendPasswordExistingShares()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 62

Duplication

Lines 62
Ratio 100 %

Importance

Changes 0
Metric Value
dl 62
loc 62
rs 8.5178
c 0
b 0
f 0
cc 5
nc 9
nop 3

How to fix   Long Method   

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
declare(strict_types=1);
4
5
6
/**
7
 * Circles - Bring cloud-users closer together.
8
 *
9
 * This file is licensed under the Affero General Public License version 3 or
10
 * later. See the COPYING file.
11
 *
12
 * @author Maxence Lange <[email protected]>
13
 * @copyright 2021
14
 * @license GNU AGPL version 3 or any later version
15
 *
16
 * This program is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License as
18
 * published by the Free Software Foundation, either version 3 of the
19
 * License, or (at your option) any later version.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
28
 *
29
 */
30
31
32
namespace OCA\Circles\RemoteEvents;
33
34
35
use daita\MySmallPhpTools\Model\SimpleDataStore;
36
use Exception;
37
use OC\User\NoUserException;
38
use OCA\Circles\Exceptions\CircleDoesNotExistException;
39
use OCA\Circles\Exceptions\CircleTypeNotValidException;
40
use OCA\Circles\Exceptions\ConfigNoCircleAvailableException;
41
use OCA\Circles\Exceptions\EmailAccountInvalidFormatException;
42
use OCA\Circles\Exceptions\GlobalScaleDSyncException;
43
use OCA\Circles\Exceptions\GlobalScaleEventException;
44
use OCA\Circles\Exceptions\MemberAlreadyExistsException;
45
use OCA\Circles\Exceptions\MemberCantJoinCircleException;
46
use OCA\Circles\Exceptions\MemberIsNotModeratorException;
47
use OCA\Circles\Exceptions\MembersLimitException;
48
use OCA\Circles\Exceptions\TokenDoesNotExistException;
49
use OCA\Circles\Model\DeprecatedCircle;
50
use OCA\Circles\Model\GlobalScale\GSEvent;
51
use OCA\Circles\Model\DeprecatedMember;
52
use OCA\Circles\Model\SharesToken;
53
use OCP\IUser;
54
use OCP\Mail\IEMailTemplate;
55
use OCP\Util;
56
57
58
/**
59
 * Class MemberAdd
60
 *
61
 * @package OCA\Circles\GlobalScale
62
 */
63 View Code Duplication
class MemberAdd extends AGlobalScaleEvent {
0 ignored issues
show
Duplication introduced by
This class 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...
64
65
66
	/**
67
	 * @param GSEvent $event
68
	 * @param bool $localCheck
69
	 * @param bool $mustBeChecked
70
	 *
71
	 * @throws CircleDoesNotExistException
72
	 * @throws ConfigNoCircleAvailableException
73
	 * @throws EmailAccountInvalidFormatException
74
	 * @throws GlobalScaleDSyncException
75
	 * @throws GlobalScaleEventException
76
	 * @throws MemberAlreadyExistsException
77
	 * @throws MemberCantJoinCircleException
78
	 * @throws MembersLimitException
79
	 * @throws NoUserException
80
	 * @throws CircleTypeNotValidException
81
	 * @throws MemberIsNotModeratorException
82
	 */
83
	public function verify(GSEvent $event, bool $localCheck = false, bool $mustBeChecked = false): void {
84
		parent::verify($event, $localCheck, true);
85
86
		$eventMember = $event->getMember();
87
		$this->cleanMember($eventMember);
88
89
		if ($eventMember->getInstance() === '') {
90
			$eventMember->setInstance($event->getSource());
91
		}
92
93
		$ident = $eventMember->getUserId();
94
		$this->membersService->verifyIdentBasedOnItsType(
95
			$ident, $eventMember->getType(), $eventMember->getInstance()
96
		);
97
98
		$circle = $event->getDeprecatedCircle();
0 ignored issues
show
Deprecated Code introduced by
The method OCA\Circles\Model\Global...::getDeprecatedCircle() has been deprecated.

This method has been deprecated.

Loading history...
99
100
		if (!$event->isForced()) {
101
			$circle->getHigherViewer()
102
				   ->hasToBeModerator();
103
		}
104
105
		$member = $this->membersRequest->getFreshNewMember(
106
			$circle->getUniqueId(), $ident, $eventMember->getType(), $eventMember->getInstance()
107
		);
108
		$member->hasToBeInviteAble();
109
		$member->setCachedName($eventMember->getCachedName());
110
111
		$this->circlesService->checkThatCircleIsNotFull($circle);
112
		$this->membersService->addMemberBasedOnItsType($circle, $member);
113
114
		$password = '';
115
		$sendPasswordByMail = false;
116
		if ($this->configService->enforcePasswordProtection($circle)) {
117
			if ($circle->getSetting('password_single_enabled') === 'true') {
118
				$password = $circle->getPasswordSingle();
119
			} else {
120
				$sendPasswordByMail = true;
121
				$password = $this->miscService->token(15);
122
			}
123
		}
124
125
		$event->setData(
126
			new SimpleDataStore(
127
				[
128
					'password'       => $password,
129
					'passwordByMail' => $sendPasswordByMail
130
				]
131
			)
132
		);
133
		$event->setMember($member);
134
	}
135
136
137
	/**
138
	 * @param GSEvent $event
139
	 *
140
	 * @throws MemberAlreadyExistsException
141
	 */
142
	public function manage(GSEvent $event): void {
143
		$circle = $event->getDeprecatedCircle();
0 ignored issues
show
Deprecated Code introduced by
The method OCA\Circles\Model\Global...::getDeprecatedCircle() has been deprecated.

This method has been deprecated.

Loading history...
144
		$member = $event->getMember();
145
		if ($member->getJoined() === '') {
146
			$this->membersRequest->createMember($member);
147
		} else {
148
			$this->membersRequest->updateMemberLevel($member);
149
		}
150
151
152
		//
153
		// TODO: verifiez comment se passe le cached name sur un member_add
154
		//
155
		$cachedName = $member->getCachedName();
156
		$password = $event->getData()
157
						  ->g('password');
158
159
		$shares = $this->generateUnknownSharesLinks($circle, $member, $password);
160
		$result = [
161
			'unknownShares' => $shares,
162
			'cachedName'    => $cachedName
163
		];
164
165
		if ($member->getType() === DeprecatedMember::TYPE_CONTACT
166
			&& $this->configService->isLocalInstance($member->getInstance())) {
167
			$result['contact'] = $this->miscService->getInfosFromContact($member);
168
		}
169
170
		$event->setResult(new SimpleDataStore($result));
171
		$this->eventsService->onMemberNew($circle, $member);
172
	}
173
174
175
	/**
176
	 * @param GSEvent[] $events
177
	 *
178
	 * @throws Exception
179
	 */
180
	public function result(array $events): void {
181
		$password = $cachedName = '';
182
		$circle = $member = null;
183
		$links = [];
184
		$recipients = [];
185
		foreach ($events as $event) {
186
			$data = $event->getData();
187
			if ($data->gBool('passwordByMail') !== false) {
188
				$password = $data->g('password');
189
			}
190
			$circle = $event->getDeprecatedCircle();
0 ignored issues
show
Deprecated Code introduced by
The method OCA\Circles\Model\Global...::getDeprecatedCircle() has been deprecated.

This method has been deprecated.

Loading history...
191
			$member = $event->getMember();
192
			$result = $event->getResult();
193
			if ($result->g('cachedName') !== '') {
194
				$cachedName = $result->g('cachedName');
195
			}
196
197
			$links = array_merge($links, $result->gArray('unknownShares'));
198
			$contact = $result->gArray('contact');
199
			if (!empty($contact)) {
200
				$recipients = $contact['emails'];
201
			}
202
		}
203
204
		if (empty($links) || $circle === null || $member === null) {
205
			return;
206
		}
207
208
		if ($cachedName !== '') {
209
			$member->setCachedName($cachedName);
210
			$this->membersService->updateMember($member);
211
		}
212
213
		if ($member->getType() === DeprecatedMember::TYPE_MAIL
214
			|| $member->getType() === DeprecatedMember::TYPE_CONTACT) {
215
			if ($member->getType() === DeprecatedMember::TYPE_MAIL) {
216
				$recipients = [$member->getUserId()];
217
			}
218
219
			foreach ($recipients as $recipient) {
220
				$this->memberIsMailbox($circle, $recipient, $links, $password);
221
			}
222
		}
223
	}
224
225
226
	/**
227
	 * @param DeprecatedCircle $circle
228
	 * @param string $recipient
229
	 * @param array $links
230
	 * @param string $password
231
	 */
232
	private function memberIsMailbox(DeprecatedCircle $circle, string $recipient, array $links, string $password) {
233
		if ($circle->getViewer() === null) {
234
			$author = $circle->getOwner()
235
							 ->getUserId();
236
		} else {
237
			$author = $circle->getViewer()
238
							 ->getUserId();
239
		}
240
241
		try {
242
			$template = $this->generateMailExitingShares($author, $circle->getName());
243
			$this->fillMailExistingShares($template, $links);
244
			$this->sendMailExistingShares($template, $author, $recipient);
245
			$this->sendPasswordExistingShares($author, $recipient, $password);
246
		} catch (Exception $e) {
247
			$this->miscService->log('Failed to send mail about existing share ' . $e->getMessage());
248
		}
249
	}
250
251
252
	/**
253
	 * @param DeprecatedCircle $circle
254
	 * @param DeprecatedMember $member
255
	 * @param string $password
256
	 *
257
	 * @return array
258
	 */
259
	private function generateUnknownSharesLinks(DeprecatedCircle $circle, DeprecatedMember $member, string $password): array {
260
		$unknownShares = $this->getUnknownShares($member);
261
262
		$data = [];
263
		foreach ($unknownShares as $share) {
264
			try {
265
				$data[] = $this->getMailLinkFromShare($share, $member, $password);
266
			} catch (TokenDoesNotExistException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
267
			}
268
		}
269
270
		return $data;
271
	}
272
273
274
	/**
275
	 * @param DeprecatedMember $member
276
	 *
277
	 * @return array
278
	 */
279
	private function getUnknownShares(DeprecatedMember $member): array {
280
		$allShares = $this->sharesRequest->getSharesForCircle($member->getCircleId());
281
		$knownShares = array_map(
282
			function(SharesToken $shareToken) {
283
				return $shareToken->getShareId();
284
			},
285
			$this->tokensRequest->getTokensFromMember($member)
286
		);
287
288
		$unknownShares = [];
289
		foreach ($allShares as $share) {
290
			if (!in_array($share['id'], $knownShares)) {
291
				$unknownShares[] = $share;
292
			}
293
		}
294
295
		return $unknownShares;
296
	}
297
298
299
	/**
300
	 * @param array $share
301
	 * @param DeprecatedMember $member
302
	 * @param string $password
303
	 *
304
	 * @return array
305
	 * @throws TokenDoesNotExistException
306
	 */
307
	private function getMailLinkFromShare(array $share, DeprecatedMember $member, string $password = '') {
308
		$sharesToken = $this->tokensRequest->generateTokenForMember($member, (int)$share['id'], $password);
309
		$link = $this->urlGenerator->linkToRouteAbsolute(
310
			'files_sharing.sharecontroller.showShare',
311
			['token' => $sharesToken->getToken()]
312
		);
313
		$author = $share['uid_initiator'];
314
		$filename = basename($share['file_target']);
315
316
		return [
317
			'author'   => $author,
318
			'link'     => $link,
319
			'filename' => $filename
320
		];
321
	}
322
323
324
	/**
325
	 * @param string $author
326
	 * @param string $circleName
327
	 *
328
	 * @return IEMailTemplate
329
	 */
330
	private function generateMailExitingShares(string $author, string $circleName): IEMailTemplate {
331
		$emailTemplate = $this->mailer->createEMailTemplate('circles.ExistingShareNotification', []);
332
		$emailTemplate->addHeader();
333
334
		$text = $this->l10n->t('%s shared multiple files with \'%s\'.', [$author, $circleName]);
335
		$emailTemplate->addBodyText(htmlspecialchars($text), $text);
336
337
		return $emailTemplate;
338
	}
339
340
	/**
341
	 * @param IEMailTemplate $emailTemplate
342
	 * @param array $links
343
	 */
344
	private function fillMailExistingShares(IEMailTemplate $emailTemplate, array $links) {
345
		foreach ($links as $item) {
346
			$emailTemplate->addBodyButton(
347
				$this->l10n->t('Open »%s«', [htmlspecialchars($item['filename'])]), $item['link']
348
			);
349
		}
350
	}
351
352
353
	/**
354
	 * @param IEMailTemplate $emailTemplate
355
	 * @param string $author
356
	 * @param string $recipient
357
	 *
358
	 * @throws Exception
359
	 */
360
	private function sendMailExistingShares(IEMailTemplate $emailTemplate, string $author, string $recipient
361
	) {
362
		$subject = $this->l10n->t('%s shared multiple files with you.', [$author]);
363
364
		$instanceName = $this->defaults->getName();
365
		$senderName = $this->l10n->t('%s on %s', [$author, $instanceName]);
366
367
		$message = $this->mailer->createMessage();
368
369
		$message->setFrom([Util::getDefaultEmailAddress($instanceName) => $senderName]);
370
		$message->setSubject($subject);
371
		$message->setPlainBody($emailTemplate->renderText());
372
		$message->setHtmlBody($emailTemplate->renderHtml());
373
		$message->setTo([$recipient]);
374
375
		$this->mailer->send($message);
376
	}
377
378
379
	/**
380
	 * @param string $author
381
	 * @param string $email
382
	 * @param string $password
383
	 *
384
	 * @throws Exception
385
	 */
386
	protected function sendPasswordExistingShares(string $author, string $email, string $password) {
387
		if ($password === '') {
388
			return;
389
		}
390
391
		$message = $this->mailer->createMessage();
392
393
		$authorUser = $this->userManager->get($author);
394
		$authorName = ($authorUser instanceof IUser) ? $authorUser->getDisplayName() : $author;
0 ignored issues
show
Bug introduced by
The class OCP\IUser does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
395
		$authorEmail = ($authorUser instanceof IUser) ? $authorUser->getEMailAddress() : null;
0 ignored issues
show
Bug introduced by
The class OCP\IUser does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
396
397
		$this->miscService->log("Sending password mail about existing files to '" . $email . "'", 0);
398
399
		$plainBodyPart = $this->l10n->t(
400
			"%1\$s shared multiple files with you.\nYou should have already received a separate mail with a link to access them.\n",
401
			[$authorName]
402
		);
403
		$htmlBodyPart = $this->l10n->t(
404
			'%1$s shared multiple files with you. You should have already received a separate mail with a link to access them.',
405
			[$authorName]
406
		);
407
408
		$emailTemplate = $this->mailer->createEMailTemplate(
409
			'sharebymail.RecipientPasswordNotification', [
410
														   'password' => $password,
411
														   'author'   => $author
412
													   ]
413
		);
414
415
		$emailTemplate->setSubject(
416
			$this->l10n->t(
417
				'Password to access files shared to you by %1$s', [$authorName]
418
			)
419
		);
420
		$emailTemplate->addHeader();
421
		$emailTemplate->addHeading($this->l10n->t('Password to access files'), false);
422
		$emailTemplate->addBodyText(htmlspecialchars($htmlBodyPart), $plainBodyPart);
423
		$emailTemplate->addBodyText($this->l10n->t('It is protected with the following password:'));
424
		$emailTemplate->addBodyText($password);
425
426
		// The "From" contains the sharers name
427
		$instanceName = $this->defaults->getName();
428
		$senderName = $this->l10n->t(
429
			'%1$s via %2$s',
430
			[
431
				$authorName,
432
				$instanceName
433
			]
434
		);
435
436
		$message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]);
437
		if ($authorEmail !== null) {
438
			$message->setReplyTo([$authorEmail => $authorName]);
439
			$emailTemplate->addFooter($instanceName . ' - ' . $this->defaults->getSlogan());
440
		} else {
441
			$emailTemplate->addFooter();
442
		}
443
444
		$message->setTo([$email]);
445
		$message->useTemplate($emailTemplate);
446
		$this->mailer->send($message);
447
	}
448
449
}
450