Completed
Pull Request — master (#479)
by Maxence
02:08
created

MemberAdd::memberIsMailbox()   A

Complexity

Conditions 3
Paths 10

Size

Total Lines 18

Duplication

Lines 7
Ratio 38.89 %

Importance

Changes 0
Metric Value
dl 7
loc 18
rs 9.6666
c 0
b 0
f 0
cc 3
nc 10
nop 4
1
<?php declare(strict_types=1);
2
3
4
/**
5
 * Circles - Bring cloud-users closer together.
6
 *
7
 * This file is licensed under the Affero General Public License version 3 or
8
 * later. See the COPYING file.
9
 *
10
 * @author Maxence Lange <[email protected]>
11
 * @copyright 2017
12
 * @license GNU AGPL version 3 or any later version
13
 *
14
 * This program is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU Affero General Public License as
16
 * published by the Free Software Foundation, either version 3 of the
17
 * License, or (at your option) any later version.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 * GNU Affero General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public License
25
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
26
 *
27
 */
28
29
30
namespace OCA\Circles\GlobalScale;
31
32
33
use daita\MySmallPhpTools\Model\SimpleDataStore;
34
use Exception;
35
use OC\User\NoUserException;
36
use OCA\Circles\Exceptions\CircleDoesNotExistException;
37
use OCA\Circles\Exceptions\CircleTypeNotValidException;
38
use OCA\Circles\Exceptions\ConfigNoCircleAvailableException;
39
use OCA\Circles\Exceptions\EmailAccountInvalidFormatException;
40
use OCA\Circles\Exceptions\GlobalScaleDSyncException;
41
use OCA\Circles\Exceptions\GlobalScaleEventException;
42
use OCA\Circles\Exceptions\MemberAlreadyExistsException;
43
use OCA\Circles\Exceptions\MemberCantJoinCircleException;
44
use OCA\Circles\Exceptions\MemberIsNotModeratorException;
45
use OCA\Circles\Exceptions\MembersLimitException;
46
use OCA\Circles\Exceptions\TokenDoesNotExistException;
47
use OCA\Circles\Model\Circle;
48
use OCA\Circles\Model\GlobalScale\GSEvent;
49
use OCA\Circles\Model\Member;
50
use OCA\Circles\Model\SharesToken;
51
use OCP\IUser;
52
use OCP\Mail\IEMailTemplate;
53
use OCP\Util;
54
55
56
/**
57
 * Class MemberAdd
58
 *
59
 * @package OCA\Circles\GlobalScale
60
 */
61
class MemberAdd extends AGlobalScaleEvent {
62
63
64
	/**
65
	 * @param GSEvent $event
66
	 * @param bool $localCheck
67
	 * @param bool $mustBeChecked
68
	 *
69
	 * @throws CircleDoesNotExistException
70
	 * @throws ConfigNoCircleAvailableException
71
	 * @throws EmailAccountInvalidFormatException
72
	 * @throws GlobalScaleDSyncException
73
	 * @throws GlobalScaleEventException
74
	 * @throws MemberAlreadyExistsException
75
	 * @throws MemberCantJoinCircleException
76
	 * @throws MembersLimitException
77
	 * @throws NoUserException
78
	 * @throws CircleTypeNotValidException
79
	 * @throws MemberIsNotModeratorException
80
	 */
81
	public function verify(GSEvent $event, bool $localCheck = false, bool $mustBeChecked = false): void {
82
		parent::verify($event, $localCheck, true);
83
84
		$eventMember = $event->getMember();
85
		$this->cleanMember($eventMember);
86
87
		if ($eventMember->getInstance() === '') {
88
			$eventMember->setInstance($event->getSource());
89
		}
90
91
		$ident = $eventMember->getUserId();
92
		$this->membersService->verifyIdentBasedOnItsType(
93
			$ident, $eventMember->getType(), $eventMember->getInstance()
94
		);
95
96
		$circle = $event->getCircle();
97
98
		if (!$event->isForced()) {
99
			$circle->getHigherViewer()
100
				   ->hasToBeModerator();
101
		}
102
103
		$member = $this->membersRequest->getFreshNewMember(
104
			$circle->getUniqueId(), $ident, $eventMember->getType(), $eventMember->getInstance()
105
		);
106
		$member->hasToBeInviteAble();
107
108
		$this->circlesService->checkThatCircleIsNotFull($circle);
109
		$this->membersService->addMemberBasedOnItsType($circle, $member);
110
111
		$password = '';
112
		$sendPasswordByMail = false;
113 View Code Duplication
		if ($this->configService->enforcePasswordProtection($circle)) {
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...
114
			if ($circle->getSetting('password_single_enabled') === 'true') {
115
				$password = $circle->getPasswordSingle();
116
			} else {
117
				$sendPasswordByMail = true;
118
				$password = $this->miscService->token(15);
119
			}
120
		}
121
122
		$event->setData(
123
			new SimpleDataStore(
124
				[
125
					'password'       => $password,
126
					'passwordByMail' => $sendPasswordByMail
127
				]
128
			)
129
		);
130
		$event->setMember($member);
131
	}
132
133
134
	/**
135
	 * @param GSEvent $event
136
	 *
137
	 * @throws MemberAlreadyExistsException
138
	 */
139
	public function manage(GSEvent $event): void {
140
		$circle = $event->getCircle();
141
		$member = $event->getMember();
142
		if ($member->getJoined() === '') {
143
			$this->membersRequest->createMember($member);
144
		} else {
145
			$this->membersRequest->updateMemberLevel($member);
146
		}
147
148
		$this->miscService->updateCachedName($member);
149
		$cachedName = $member->getCachedName();
150
151
		$password = $event->getData()
152
						  ->g('password');
153
154
		$shares = $this->generateUnknownSharesLinks($circle, $member, $password);
155
		$result = [
156
			'unknownShares' => $shares,
157
			'cachedName'    => $cachedName
158
		];
159
160
		if ($member->getType() === Member::TYPE_CONTACT
161
			&& $member->getInstance() === $this->configService->getLocalCloudId()) {
162
			$result['contact'] = $this->miscService->getInfosFromContact($member);
163
		}
164
165
		$event->setResult(new SimpleDataStore($result));
166
		$this->eventsService->onMemberNew($circle, $member);
167
	}
168
169
170
	/**
171
	 * @param GSEvent[] $events
172
	 *
173
	 * @throws Exception
174
	 */
175
	public function result(array $events): void {
176
		$password = $cachedName = '';
177
		$circle = $member = null;
178
		$links = [];
179
		$recipients = [];
180
		foreach ($events as $event) {
181
			$data = $event->getData();
182
			if ($data->gBool('passwordByMail') !== false) {
183
				$password = $data->g('password');
184
			}
185
			$circle = $event->getCircle();
186
			$member = $event->getMember();
187
			$result = $event->getResult();
188
			if ($result->g('cachedName') !== '') {
189
				$cachedName = $result->g('cachedName');
190
			}
191
192
			$links = array_merge($links, $result->gArray('unknownShares'));
193
			$contact = $result->gArray('contact');
194
			if (!empty($contact)) {
195
				$recipients = $contact['emails'];
196
			}
197
		}
198
199
		if ($circle === null || $member === null) {
200
			return;
201
		}
202
203
		if ($cachedName !== '') {
204
			$member->setCachedName($cachedName);
205
			$this->membersService->updateMember($member);
206
		}
207
208
		if ($member->getType() === Member::TYPE_MAIL
209
			|| $member->getType() === Member::TYPE_CONTACT) {
210
			if ($member->getType() === Member::TYPE_MAIL) {
211
				$recipients = [$member->getUserId()];
212
			}
213
214
			foreach ($recipients as $recipient) {
215
				$this->memberIsMailbox($circle, $recipient, $links, $password);
216
			}
217
		}
218
	}
219
220
221
	/**
222
	 * @param Circle $circle
223
	 * @param string $recipient
224
	 * @param array $links
225
	 * @param string $password
226
	 */
227
	private function memberIsMailbox(Circle $circle, string $recipient, array $links, string $password) {
228 View Code Duplication
		if ($circle->getViewer() === null) {
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...
229
			$author = $circle->getOwner()
230
							 ->getUserId();
231
		} else {
232
			$author = $circle->getViewer()
233
							 ->getUserId();
234
		}
235
236
		try {
237
			$template = $this->generateMailExitingShares($author, $circle->getName());
238
			$this->fillMailExistingShares($template, $links);
239
			$this->sendMailExistingShares($template, $author, $recipient);
240
			$this->sendPasswordExistingShares($author, $recipient, $password);
241
		} catch (Exception $e) {
242
			$this->miscService->log('Failed to send mail about existing share ' . $e->getMessage());
243
		}
244
	}
245
246
247
	/**
248
	 * @param Circle $circle
249
	 * @param Member $member
250
	 * @param string $password
251
	 *
252
	 * @return array
253
	 */
254
	private function generateUnknownSharesLinks(Circle $circle, Member $member, string $password): array {
0 ignored issues
show
Unused Code introduced by
The parameter $circle is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
255
		$unknownShares = $this->getUnknownShares($member);
256
257
		$data = [];
258
		foreach ($unknownShares as $share) {
259
			try {
260
				$data[] = $this->getMailLinkFromShare($share, $member, $password);
261
			} catch (TokenDoesNotExistException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
262
			}
263
		}
264
265
		return $data;
266
	}
267
268
269
	/**
270
	 * @param Member $member
271
	 *
272
	 * @return array
273
	 */
274
	private function getUnknownShares(Member $member): array {
275
		$allShares = $this->sharesRequest->getSharesForCircle($member->getCircleId());
276
		$knownShares = array_map(
277
			function(SharesToken $shareToken) {
278
				return $shareToken->getShareId();
279
			},
280
			$this->tokensRequest->getTokensFromMember($member)
281
		);
282
283
		$unknownShares = [];
284
		foreach ($allShares as $share) {
285
			if (!in_array($share['id'], $knownShares)) {
286
				$unknownShares[] = $share;
287
			}
288
		}
289
290
		return $unknownShares;
291
	}
292
293
294
	/**
295
	 * @param array $share
296
	 * @param Member $member
297
	 * @param string $password
298
	 *
299
	 * @return array
300
	 * @throws TokenDoesNotExistException
301
	 */
302 View Code Duplication
	private function getMailLinkFromShare(array $share, Member $member, string $password = '') {
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...
303
		$sharesToken = $this->tokensRequest->generateTokenForMember($member, (int)$share['id'], $password);
304
		$link = $this->urlGenerator->linkToRouteAbsolute(
305
			'files_sharing.sharecontroller.showShare',
306
			['token' => $sharesToken->getToken()]
307
		);
308
		$author = $share['uid_initiator'];
309
		$filename = basename($share['file_target']);
310
311
		return [
312
			'author'   => $author,
313
			'link'     => $link,
314
			'filename' => $filename
315
		];
316
	}
317
318
319
	/**
320
	 * @param string $author
321
	 * @param string $circleName
322
	 *
323
	 * @return IEMailTemplate
324
	 */
325
	private function generateMailExitingShares(string $author, string $circleName): IEMailTemplate {
326
		$emailTemplate = $this->mailer->createEMailTemplate('circles.ExistingShareNotification', []);
327
		$emailTemplate->addHeader();
328
329
		$text = $this->l10n->t('%s shared multiple files with \'%s\'.', [$author, $circleName]);
330
		$emailTemplate->addBodyText(htmlspecialchars($text), $text);
331
332
		return $emailTemplate;
333
	}
334
335
	/**
336
	 * @param IEMailTemplate $emailTemplate
337
	 * @param array $links
338
	 */
339 View Code Duplication
	private function fillMailExistingShares(IEMailTemplate $emailTemplate, array $links) {
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...
340
		foreach ($links as $item) {
341
			$emailTemplate->addBodyButton(
342
				$this->l10n->t('Open »%s«', [htmlspecialchars($item['filename'])]), $item['link']
343
			);
344
		}
345
	}
346
347
348
	/**
349
	 * @param IEMailTemplate $emailTemplate
350
	 * @param string $author
351
	 * @param string $recipient
352
	 *
353
	 * @throws Exception
354
	 */
355 View Code Duplication
	private function sendMailExistingShares(IEMailTemplate $emailTemplate, string $author, string $recipient
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...
356
	) {
357
		$subject = $this->l10n->t('%s shared multiple files with you.', [$author]);
358
359
		$instanceName = $this->defaults->getName();
360
		$senderName = $this->l10n->t('%s on %s', [$author, $instanceName]);
361
362
		$message = $this->mailer->createMessage();
363
364
		$message->setFrom([Util::getDefaultEmailAddress($instanceName) => $senderName]);
365
		$message->setSubject($subject);
366
		$message->setPlainBody($emailTemplate->renderText());
367
		$message->setHtmlBody($emailTemplate->renderHtml());
368
		$message->setTo([$recipient]);
369
370
		$this->mailer->send($message);
371
	}
372
373
374
	/**
375
	 * @param string $author
376
	 * @param string $email
377
	 * @param string $password
378
	 *
379
	 * @throws Exception
380
	 */
381 View Code Duplication
	protected function sendPasswordExistingShares(string $author, string $email, string $password) {
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...
382
		if ($password === '') {
383
			return;
384
		}
385
386
		$message = $this->mailer->createMessage();
387
388
		$authorUser = $this->userManager->get($author);
389
		$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...
390
		$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...
391
392
		$this->miscService->log("Sending password mail about existing files to '" . $email . "'", 0);
393
394
		$plainBodyPart = $this->l10n->t(
395
			"%1\$s shared multiple files with you.\nYou should have already received a separate mail with a link to access them.\n",
396
			[$authorName]
397
		);
398
		$htmlBodyPart = $this->l10n->t(
399
			'%1$s shared multiple files with you. You should have already received a separate mail with a link to access them.',
400
			[$authorName]
401
		);
402
403
		$emailTemplate = $this->mailer->createEMailTemplate(
404
			'sharebymail.RecipientPasswordNotification', [
405
														   'password' => $password,
406
														   'author'   => $author
407
													   ]
408
		);
409
410
		$emailTemplate->setSubject(
411
			$this->l10n->t(
412
				'Password to access files shared to you by %1$s', [$authorName]
413
			)
414
		);
415
		$emailTemplate->addHeader();
416
		$emailTemplate->addHeading($this->l10n->t('Password to access files'), false);
417
		$emailTemplate->addBodyText(htmlspecialchars($htmlBodyPart), $plainBodyPart);
418
		$emailTemplate->addBodyText($this->l10n->t('It is protected with the following password:'));
419
		$emailTemplate->addBodyText($password);
420
421
		// The "From" contains the sharers name
422
		$instanceName = $this->defaults->getName();
423
		$senderName = $this->l10n->t(
424
			'%1$s via %2$s',
425
			[
426
				$authorName,
427
				$instanceName
428
			]
429
		);
430
431
		$message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]);
432
		if ($authorEmail !== null) {
433
			$message->setReplyTo([$authorEmail => $authorName]);
434
			$emailTemplate->addFooter($instanceName . ' - ' . $this->defaults->getSlogan());
435
		} else {
436
			$emailTemplate->addFooter();
437
		}
438
439
		$message->setTo([$email]);
440
		$message->useTemplate($emailTemplate);
441
		$this->mailer->send($message);
442
	}
443
444
}
445