Completed
Push — master ( 1a83f1...fe13f2 )
by Morris
13:34
created

ShareByMailProvider::sendMailNotification()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 53
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 35
nc 4
nop 4
dl 0
loc 53
rs 9.5797
c 0
b 0
f 0

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
 * @copyright Copyright (c) 2016 Bjoern Schiessle <[email protected]>
4
 *
5
 * @license GNU AGPL version 3 or any later version
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Affero General Public License as
9
 * published by the Free Software Foundation, either version 3 of the
10
 * License, or (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License
18
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
 *
20
 */
21
22
namespace OCA\ShareByMail;
23
24
use OC\CapabilitiesManager;
25
use OC\HintException;
26
use OC\Share20\Exception\InvalidShare;
27
use OCA\ShareByMail\Settings\SettingsManager;
28
use OCP\Activity\IManager;
29
use OCP\DB\QueryBuilder\IQueryBuilder;
30
use OCP\Defaults;
31
use OCP\Files\Folder;
32
use OCP\Files\IRootFolder;
33
use OCP\Files\Node;
34
use OCP\IDBConnection;
35
use OCP\IL10N;
36
use OCP\ILogger;
37
use OCP\IURLGenerator;
38
use OCP\IUser;
39
use OCP\IUserManager;
40
use OCP\Mail\IMailer;
41
use OCP\Security\IHasher;
42
use OCP\Security\ISecureRandom;
43
use OC\Share20\Share;
44
use OCP\Share\Exceptions\ShareNotFound;
45
use OCP\Share\IShare;
46
use OCP\Share\IShareProvider;
47
48
/**
49
 * Class ShareByMail
50
 *
51
 * @package OCA\ShareByMail
52
 */
53
class ShareByMailProvider implements IShareProvider {
54
55
	/** @var  IDBConnection */
56
	private $dbConnection;
57
58
	/** @var ILogger */
59
	private $logger;
60
61
	/** @var ISecureRandom */
62
	private $secureRandom;
63
64
	/** @var IUserManager */
65
	private $userManager;
66
67
	/** @var IRootFolder */
68
	private $rootFolder;
69
70
	/** @var IL10N */
71
	private $l;
72
73
	/** @var IMailer */
74
	private $mailer;
75
76
	/** @var IURLGenerator */
77
	private $urlGenerator;
78
79
	/** @var IManager  */
80
	private $activityManager;
81
82
	/** @var SettingsManager */
83
	private $settingsManager;
84
85
	/** @var Defaults */
86
	private $defaults;
87
88
	/** @var IHasher */
89
	private $hasher;
90
91
	/** @var  CapabilitiesManager */
92
	private $capabilitiesManager;
93
94
	/**
95
	 * Return the identifier of this provider.
96
	 *
97
	 * @return string Containing only [a-zA-Z0-9]
98
	 */
99
	public function identifier() {
100
		return 'ocMailShare';
101
	}
102
103
	/**
104
	 * DefaultShareProvider constructor.
105
	 *
106
	 * @param IDBConnection $connection
107
	 * @param ISecureRandom $secureRandom
108
	 * @param IUserManager $userManager
109
	 * @param IRootFolder $rootFolder
110
	 * @param IL10N $l
111
	 * @param ILogger $logger
112
	 * @param IMailer $mailer
113
	 * @param IURLGenerator $urlGenerator
114
	 * @param IManager $activityManager
115
	 * @param SettingsManager $settingsManager
116
	 * @param Defaults $defaults
117
	 * @param IHasher $hasher
118
	 * @param CapabilitiesManager $capabilitiesManager
119
	 */
120
	public function __construct(
121
		IDBConnection $connection,
122
		ISecureRandom $secureRandom,
123
		IUserManager $userManager,
124
		IRootFolder $rootFolder,
125
		IL10N $l,
126
		ILogger $logger,
127
		IMailer $mailer,
128
		IURLGenerator $urlGenerator,
129
		IManager $activityManager,
130
		SettingsManager $settingsManager,
131
		Defaults $defaults,
132
		IHasher $hasher,
133
		CapabilitiesManager $capabilitiesManager
134
	) {
135
		$this->dbConnection = $connection;
136
		$this->secureRandom = $secureRandom;
137
		$this->userManager = $userManager;
138
		$this->rootFolder = $rootFolder;
139
		$this->l = $l;
140
		$this->logger = $logger;
141
		$this->mailer = $mailer;
142
		$this->urlGenerator = $urlGenerator;
143
		$this->activityManager = $activityManager;
144
		$this->settingsManager = $settingsManager;
145
		$this->defaults = $defaults;
146
		$this->hasher = $hasher;
147
		$this->capabilitiesManager = $capabilitiesManager;
148
	}
149
150
	/**
151
	 * Share a path
152
	 *
153
	 * @param IShare $share
154
	 * @return IShare The share object
155
	 * @throws ShareNotFound
156
	 * @throws \Exception
157
	 */
158
	public function create(IShare $share) {
159
160
		$shareWith = $share->getSharedWith();
161
		/*
162
		 * Check if file is not already shared with the remote user
163
		 */
164
		$alreadyShared = $this->getSharedWith($shareWith, \OCP\Share::SHARE_TYPE_EMAIL, $share->getNode(), 1, 0);
165 View Code Duplication
		if (!empty($alreadyShared)) {
166
			$message = 'Sharing %s failed, this item is already shared with %s';
167
			$message_t = $this->l->t('Sharing %s failed, this item is already shared with %s', array($share->getNode()->getName(), $shareWith));
168
			$this->logger->debug(sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'Federated File Sharing']);
169
			throw new \Exception($message_t);
170
		}
171
172
		// if the admin enforces a password for all mail shares we create a
173
		// random password and send it to the recipient
174
		$password = '';
175
		$passwordEnforced = $this->settingsManager->enforcePasswordProtection();
176
		if ($passwordEnforced) {
177
			$password = $this->autoGeneratePassword($share);
178
		}
179
180
		$shareId = $this->createMailShare($share);
181
		$send = $this->sendPassword($share, $password);
182
		if ($passwordEnforced && $send === false) {
183
			$this->sendPasswordToOwner($share, $password);
184
		}
185
186
		$this->createShareActivity($share);
187
		$data = $this->getRawShare($shareId);
188
189
		return $this->createShareObject($data);
190
191
	}
192
193
	/**
194
	 * auto generate password in case of password enforcement on mail shares
195
	 *
196
	 * @param IShare $share
197
	 * @return string
198
	 * @throws \Exception
199
	 */
200
	protected function autoGeneratePassword($share) {
201
		$initiatorUser = $this->userManager->get($share->getSharedBy());
202
		$initiatorEMailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null;
203
		$allowPasswordByMail = $this->settingsManager->sendPasswordByMail();
204
205
		if ($initiatorEMailAddress === null && !$allowPasswordByMail) {
206
			throw new \Exception(
207
				$this->l->t("We can't send you the auto-generated password. Please set a valid email address in your personal settings and try again.")
208
			);
209
		}
210
211
		$passwordPolicy = $this->getPasswordPolicy();
212
		$passwordCharset = ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_DIGITS;
213
		$passwordLength = 8;
214
		if (!empty($passwordPolicy)) {
215
			$passwordLength = (int)$passwordPolicy['minLength'] > 0 ? (int)$passwordPolicy['minLength'] : $passwordLength;
216
			$passwordCharset .= $passwordPolicy['enforceSpecialCharacters'] ? ISecureRandom::CHAR_SYMBOLS : '';
217
		}
218
219
		$password = $this->secureRandom->generate($passwordLength, $passwordCharset);
220
221
		$share->setPassword($this->hasher->hash($password));
222
223
		return $password;
224
	}
225
226
	/**
227
	 * get password policy
228
	 *
229
	 * @return array
230
	 */
231
	protected function getPasswordPolicy() {
232
		$capabilities = $this->capabilitiesManager->getCapabilities();
233
		if (isset($capabilities['password_policy'])) {
234
			return $capabilities['password_policy'];
235
		}
236
237
		return [];
238
	}
239
240
	/**
241
	 * create activity if a file/folder was shared by mail
242
	 *
243
	 * @param IShare $share
244
	 */
245
	protected function createShareActivity(IShare $share) {
246
247
		$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
248
249
		$this->publishActivity(
250
			Activity::SUBJECT_SHARED_EMAIL_SELF,
251
			[$userFolder->getRelativePath($share->getNode()->getPath()), $share->getSharedWith()],
252
			$share->getSharedBy(),
253
			$share->getNode()->getId(),
254
			$userFolder->getRelativePath($share->getNode()->getPath())
255
		);
256
257
		if ($share->getShareOwner() !== $share->getSharedBy()) {
258
			$ownerFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
259
			$fileId = $share->getNode()->getId();
260
			$nodes = $ownerFolder->getById($fileId);
261
			$ownerPath = $nodes[0]->getPath();
262
			$this->publishActivity(
263
				Activity::SUBJECT_SHARED_EMAIL_BY,
264
				[$ownerFolder->getRelativePath($ownerPath), $share->getSharedWith(), $share->getSharedBy()],
265
				$share->getShareOwner(),
266
				$fileId,
267
				$ownerFolder->getRelativePath($ownerPath)
268
			);
269
		}
270
271
	}
272
273
	/**
274
	 * create activity if a file/folder was shared by mail
275
	 *
276
	 * @param IShare $share
277
	 * @param string $sharedWith
278
	 * @param bool $sendToSelf
279
	 */
280
	protected function createPasswordSendActivity(IShare $share, $sharedWith, $sendToSelf) {
281
282
		$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
283
284
		if ($sendToSelf) {
285
			$this->publishActivity(
286
				Activity::SUBJECT_SHARED_EMAIL_PASSWORD_SEND_SELF,
287
				[$userFolder->getRelativePath($share->getNode()->getPath())],
288
				$share->getSharedBy(),
289
				$share->getNode()->getId(),
290
				$userFolder->getRelativePath($share->getNode()->getPath())
291
			);
292
		} else {
293
			$this->publishActivity(
294
				Activity::SUBJECT_SHARED_EMAIL_PASSWORD_SEND,
295
				[$userFolder->getRelativePath($share->getNode()->getPath()), $sharedWith],
296
				$share->getSharedBy(),
297
				$share->getNode()->getId(),
298
				$userFolder->getRelativePath($share->getNode()->getPath())
299
			);
300
		}
301
	}
302
303
304
	/**
305
	 * publish activity if a file/folder was shared by mail
306
	 *
307
	 * @param $subject
308
	 * @param $parameters
309
	 * @param $affectedUser
310
	 * @param $fileId
311
	 * @param $filePath
312
	 */
313 View Code Duplication
	protected function publishActivity($subject, $parameters, $affectedUser, $fileId, $filePath) {
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...
314
		$event = $this->activityManager->generateEvent();
315
		$event->setApp('sharebymail')
316
			->setType('shared')
317
			->setSubject($subject, $parameters)
318
			->setAffectedUser($affectedUser)
319
			->setObject('files', $fileId, $filePath);
320
		$this->activityManager->publish($event);
321
322
	}
323
324
	/**
325
	 * @param IShare $share
326
	 * @return int
327
	 * @throws \Exception
328
	 */
329
	protected function createMailShare(IShare $share) {
330
		$share->setToken($this->generateToken());
331
		$shareId = $this->addShareToDB(
332
			$share->getNodeId(),
333
			$share->getNodeType(),
334
			$share->getSharedWith(),
335
			$share->getSharedBy(),
336
			$share->getShareOwner(),
337
			$share->getPermissions(),
338
			$share->getToken(),
339
			$share->getPassword()
340
		);
341
342
		try {
343
			$link = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare',
344
				['token' => $share->getToken()]);
345
			$this->sendMailNotification(
346
				$share->getNode()->getName(),
347
				$link,
348
				$share->getSharedBy(),
349
				$share->getSharedWith()
350
			);
351
		} catch (HintException $hintException) {
352
			$this->logger->error('Failed to send share by mail: ' . $hintException->getMessage());
353
			$this->removeShareFromTable($shareId);
354
			throw $hintException;
355
		} catch (\Exception $e) {
356
			$this->logger->error('Failed to send share by mail: ' . $e->getMessage());
357
			$this->removeShareFromTable($shareId);
358
			throw new HintException('Failed to send share by mail',
359
				$this->l->t('Failed to send share by E-mail'));
360
		}
361
362
		return $shareId;
363
364
	}
365
366
	/**
367
	 * @param string $filename
368
	 * @param string $link
369
	 * @param string $initiator
370
	 * @param string $shareWith
371
	 * @throws \Exception If mail couldn't be sent
372
	 */
373
	protected function sendMailNotification($filename,
374
											$link,
375
											$initiator,
376
											$shareWith) {
377
		$initiatorUser = $this->userManager->get($initiator);
378
		$initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
379
		$subject = (string)$this->l->t('%s shared »%s« with you', array($initiatorDisplayName, $filename));
380
381
		$message = $this->mailer->createMessage();
382
383
		$emailTemplate = $this->mailer->createEMailTemplate();
384
385
		$emailTemplate->addHeader();
386
		$emailTemplate->addHeading($this->l->t('%s shared »%s« with you', [$initiatorDisplayName, $filename]), false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
387
		$text = $this->l->t('%s shared »%s« with you.', [$initiatorDisplayName, $filename]);
388
389
		$emailTemplate->addBodyText(
390
			$text . ' ' . $this->l->t('Click the button below to open it.'),
391
			$text
392
		);
393
		$emailTemplate->addBodyButton(
394
			$this->l->t('Open »%s«', [$filename]),
395
			$link
396
		);
397
398
		$message->setTo([$shareWith]);
399
400
		// The "From" contains the sharers name
401
		$instanceName = $this->defaults->getName();
402
		$senderName = $this->l->t(
403
			'%s via %s',
404
			[
405
				$initiatorDisplayName,
406
				$instanceName
407
			]
408
		);
409
		$message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]);
410
411
		// The "Reply-To" is set to the sharer if an mail address is configured
412
		// also the default footer contains a "Do not reply" which needs to be adjusted.
413
		$initiatorEmail = $initiatorUser->getEMailAddress();
414
		if($initiatorEmail !== null) {
415
			$message->setReplyTo([$initiatorEmail => $initiatorDisplayName]);
416
			$emailTemplate->addFooter($instanceName . ' - ' . $this->defaults->getSlogan());
417
		} else {
418
			$emailTemplate->addFooter();
419
		}
420
421
		$message->setSubject($subject);
422
		$message->setPlainBody($emailTemplate->renderText());
423
		$message->setHtmlBody($emailTemplate->renderHtml());
424
		$this->mailer->send($message);
425
	}
426
427
	/**
428
	 * send password to recipient of a mail share
429
	 *
430
	 * @param IShare $share
431
	 * @param string $password
432
	 * @return bool
433
	 */
434
	protected function sendPassword(IShare $share, $password) {
435
436
		$filename = $share->getNode()->getName();
437
		$initiator = $share->getSharedBy();
438
		$shareWith = $share->getSharedWith();
439
440
		if ($password === '' || $this->settingsManager->sendPasswordByMail() === false) {
441
			return false;
442
		}
443
444
		$initiatorUser = $this->userManager->get($initiator);
445
		$initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
446
		$initiatorEmailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null;
447
448
		$subject = (string)$this->l->t('Password to access »%s« shared to you by %s', [$filename, $initiatorDisplayName]);
449
		$plainBodyPart = $this->l->t("%s shared »%s« with you.\nYou should have already received a separate mail with a link to access it.\n", [$initiatorDisplayName, $filename]);
450
		$htmlBodyPart = $this->l->t('%s shared »%s« with you. You should have already received a separate mail with a link to access it.', [$initiatorDisplayName, $filename]);
451
452
		$message = $this->mailer->createMessage();
453
454
		$emailTemplate = $this->mailer->createEMailTemplate();
455
		$emailTemplate->addHeader();
456
		$emailTemplate->addHeading($this->l->t('Password to access »%s«', [$filename]), false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
457
		$emailTemplate->addBodyText($htmlBodyPart, $plainBodyPart);
458
		$emailTemplate->addBodyText($this->l->t('It is protected with the following password: %s', [$password]));
459
		$emailTemplate->addFooter();
460
461
		if ($initiatorEmailAddress !== null) {
462
			$message->setFrom([$initiatorEmailAddress => $initiatorDisplayName]);
463
		}
464
		$message->setTo([$shareWith]);
465
		$message->setSubject($subject);
466
		$message->setBody($emailTemplate->renderText(), 'text/plain');
467
		$message->setHtmlBody($emailTemplate->renderHtml());
468
		$this->mailer->send($message);
469
470
		$this->createPasswordSendActivity($share, $shareWith, false);
471
472
		return true;
473
	}
474
475
	/**
476
	 * send auto generated password to the owner. This happens if the admin enforces
477
	 * a password for mail shares and forbid to send the password by mail to the recipient
478
	 *
479
	 * @param IShare $share
480
	 * @param string $password
481
	 * @return bool
482
	 * @throws \Exception
483
	 */
484
	protected function sendPasswordToOwner(IShare $share, $password) {
485
486
		$filename = $share->getNode()->getName();
487
		$initiator = $this->userManager->get($share->getSharedBy());
488
		$initiatorEMailAddress = ($initiator instanceof IUser) ? $initiator->getEMailAddress() : null;
489
		$initiatorDisplayName = ($initiator instanceof IUser) ? $initiator->getDisplayName() : $share->getSharedBy();
490
		$shareWith = $share->getSharedWith();
491
492
		if ($initiatorEMailAddress === null) {
493
			throw new \Exception(
494
				$this->l->t("We can't send you the auto-generated password. Please set a valid email address in your personal settings and try again.")
495
			);
496
		}
497
498
		$subject = (string)$this->l->t('Password to access »%s« shared with %s', [$filename, $shareWith]);
499
		$bodyPart = $this->l->t("You just shared »%s« with %s. The share was already send to the recipient. Due to the security policies defined by the administrator of %s each share needs to be protected by password and it is not allowed to send the password directly to the recipient. Therefore you need to forward the password manually to the recipient.", [$filename, $shareWith, $this->defaults->getName()]);
500
501
		$message = $this->mailer->createMessage();
502
		$emailTemplate = $this->mailer->createEMailTemplate();
503
504
		$emailTemplate->addHeader();
505
		$emailTemplate->addHeading($this->l->t('Password to access »%s«', [$filename]), false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
506
		$emailTemplate->addBodyText($bodyPart);
507
		$emailTemplate->addBodyText($this->l->t('This is the password: %s', [$password]));
508
		$emailTemplate->addBodyText($this->l->t('You can choose a different password at any time in the share dialog.'));
509
		$emailTemplate->addFooter();
510
511
		if ($initiatorEMailAddress) {
512
			$message->setFrom([$initiatorEMailAddress => $initiatorDisplayName]);
513
		}
514
		$message->setTo([$initiatorEMailAddress => $initiatorDisplayName]);
515
		$message->setSubject($subject);
516
		$message->setBody($emailTemplate->renderText(), 'text/plain');
517
		$message->setHtmlBody($emailTemplate->renderHtml());
518
		$this->mailer->send($message);
519
520
		$this->createPasswordSendActivity($share, $shareWith, true);
521
522
		return true;
523
	}
524
525
	/**
526
	 * generate share token
527
	 *
528
	 * @return string
529
	 */
530
	protected function generateToken($size = 15) {
531
		$token = $this->secureRandom->generate(
532
			$size, ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_DIGITS);
533
		return $token;
534
	}
535
536
	/**
537
	 * Get all children of this share
538
	 *
539
	 * @param IShare $parent
540
	 * @return IShare[]
541
	 */
542 View Code Duplication
	public function getChildren(IShare $parent) {
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...
543
		$children = [];
544
545
		$qb = $this->dbConnection->getQueryBuilder();
546
		$qb->select('*')
547
			->from('share')
548
			->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
549
			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)))
550
			->orderBy('id');
551
552
		$cursor = $qb->execute();
553
		while($data = $cursor->fetch()) {
554
			$children[] = $this->createShareObject($data);
555
		}
556
		$cursor->closeCursor();
557
558
		return $children;
559
	}
560
561
	/**
562
	 * add share to the database and return the ID
563
	 *
564
	 * @param int $itemSource
565
	 * @param string $itemType
566
	 * @param string $shareWith
567
	 * @param string $sharedBy
568
	 * @param string $uidOwner
569
	 * @param int $permissions
570
	 * @param string $token
571
	 * @return int
572
	 */
573
	protected function addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, $token, $password) {
574
		$qb = $this->dbConnection->getQueryBuilder();
575
		$qb->insert('share')
576
			->setValue('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL))
577
			->setValue('item_type', $qb->createNamedParameter($itemType))
578
			->setValue('item_source', $qb->createNamedParameter($itemSource))
579
			->setValue('file_source', $qb->createNamedParameter($itemSource))
580
			->setValue('share_with', $qb->createNamedParameter($shareWith))
581
			->setValue('uid_owner', $qb->createNamedParameter($uidOwner))
582
			->setValue('uid_initiator', $qb->createNamedParameter($sharedBy))
583
			->setValue('permissions', $qb->createNamedParameter($permissions))
584
			->setValue('token', $qb->createNamedParameter($token))
585
			->setValue('password', $qb->createNamedParameter($password))
586
			->setValue('stime', $qb->createNamedParameter(time()));
587
588
		/*
589
		 * Added to fix https://github.com/owncloud/core/issues/22215
590
		 * Can be removed once we get rid of ajax/share.php
591
		 */
592
		$qb->setValue('file_target', $qb->createNamedParameter(''));
593
594
		$qb->execute();
595
		$id = $qb->getLastInsertId();
596
597
		return (int)$id;
598
	}
599
600
	/**
601
	 * Update a share
602
	 *
603
	 * @param IShare $share
604
	 * @param string|null $plainTextPassword
605
	 * @return IShare The share object
606
	 */
607
	public function update(IShare $share, $plainTextPassword = null) {
608
609
		$originalShare = $this->getShareById($share->getId());
610
611
		// a real password was given
612
		$validPassword = $plainTextPassword !== null && $plainTextPassword !== '';
613
614
		if($validPassword && $originalShare->getPassword() !== $share->getPassword()) {
615
			$this->sendPassword($share, $plainTextPassword);
616
		}
617
		/*
618
		 * We allow updating the permissions and password of mail shares
619
		 */
620
		$qb = $this->dbConnection->getQueryBuilder();
621
		$qb->update('share')
622
			->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
623
			->set('permissions', $qb->createNamedParameter($share->getPermissions()))
624
			->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
625
			->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
626
			->set('password', $qb->createNamedParameter($share->getPassword()))
627
			->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
628
			->execute();
629
630
		return $share;
631
	}
632
633
	/**
634
	 * @inheritdoc
635
	 */
636
	public function move(IShare $share, $recipient) {
637
		/**
638
		 * nothing to do here, mail shares are only outgoing shares
639
		 */
640
		return $share;
641
	}
642
643
	/**
644
	 * Delete a share (owner unShares the file)
645
	 *
646
	 * @param IShare $share
647
	 */
648
	public function delete(IShare $share) {
649
		$this->removeShareFromTable($share->getId());
650
	}
651
652
	/**
653
	 * @inheritdoc
654
	 */
655
	public function deleteFromSelf(IShare $share, $recipient) {
656
		// nothing to do here, mail shares are only outgoing shares
657
		return;
658
	}
659
660
	/**
661
	 * @inheritdoc
662
	 */
663 View Code Duplication
	public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
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...
664
		$qb = $this->dbConnection->getQueryBuilder();
665
		$qb->select('*')
666
			->from('share');
667
668
		$qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)));
669
670
		/**
671
		 * Reshares for this user are shares where they are the owner.
672
		 */
673
		if ($reshares === false) {
674
			//Special case for old shares created via the web UI
675
			$or1 = $qb->expr()->andX(
676
				$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
677
				$qb->expr()->isNull('uid_initiator')
678
			);
679
680
			$qb->andWhere(
681
				$qb->expr()->orX(
682
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)),
683
					$or1
684
				)
685
			);
686
		} else {
687
			$qb->andWhere(
688
				$qb->expr()->orX(
689
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
690
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
691
				)
692
			);
693
		}
694
695
		if ($node !== null) {
696
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
697
		}
698
699
		if ($limit !== -1) {
700
			$qb->setMaxResults($limit);
701
		}
702
703
		$qb->setFirstResult($offset);
704
		$qb->orderBy('id');
705
706
		$cursor = $qb->execute();
707
		$shares = [];
708
		while($data = $cursor->fetch()) {
709
			$shares[] = $this->createShareObject($data);
710
		}
711
		$cursor->closeCursor();
712
713
		return $shares;
714
	}
715
716
	/**
717
	 * @inheritdoc
718
	 */
719 View Code Duplication
	public function getShareById($id, $recipientId = null) {
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...
720
		$qb = $this->dbConnection->getQueryBuilder();
721
722
		$qb->select('*')
723
			->from('share')
724
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
725
			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)));
726
727
		$cursor = $qb->execute();
728
		$data = $cursor->fetch();
729
		$cursor->closeCursor();
730
731
		if ($data === false) {
732
			throw new ShareNotFound();
733
		}
734
735
		try {
736
			$share = $this->createShareObject($data);
737
		} catch (InvalidShare $e) {
738
			throw new ShareNotFound();
739
		}
740
741
		return $share;
742
	}
743
744
	/**
745
	 * Get shares for a given path
746
	 *
747
	 * @param \OCP\Files\Node $path
748
	 * @return IShare[]
749
	 */
750 View Code Duplication
	public function getSharesByPath(Node $path) {
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...
751
		$qb = $this->dbConnection->getQueryBuilder();
752
753
		$cursor = $qb->select('*')
754
			->from('share')
755
			->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
756
			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)))
757
			->execute();
758
759
		$shares = [];
760
		while($data = $cursor->fetch()) {
761
			$shares[] = $this->createShareObject($data);
762
		}
763
		$cursor->closeCursor();
764
765
		return $shares;
766
	}
767
768
	/**
769
	 * @inheritdoc
770
	 */
771 View Code Duplication
	public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
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...
772
		/** @var IShare[] $shares */
773
		$shares = [];
774
775
		//Get shares directly with this user
776
		$qb = $this->dbConnection->getQueryBuilder();
777
		$qb->select('*')
778
			->from('share');
779
780
		// Order by id
781
		$qb->orderBy('id');
782
783
		// Set limit and offset
784
		if ($limit !== -1) {
785
			$qb->setMaxResults($limit);
786
		}
787
		$qb->setFirstResult($offset);
788
789
		$qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)));
790
		$qb->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)));
791
792
		// Filter by node if provided
793
		if ($node !== null) {
794
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
795
		}
796
797
		$cursor = $qb->execute();
798
799
		while($data = $cursor->fetch()) {
800
			$shares[] = $this->createShareObject($data);
801
		}
802
		$cursor->closeCursor();
803
804
805
		return $shares;
806
	}
807
808
	/**
809
	 * Get a share by token
810
	 *
811
	 * @param string $token
812
	 * @return IShare
813
	 * @throws ShareNotFound
814
	 */
815 View Code Duplication
	public function getShareByToken($token) {
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...
816
		$qb = $this->dbConnection->getQueryBuilder();
817
818
		$cursor = $qb->select('*')
819
			->from('share')
820
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)))
821
			->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
822
			->execute();
823
824
		$data = $cursor->fetch();
825
826
		if ($data === false) {
827
			throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
828
		}
829
830
		try {
831
			$share = $this->createShareObject($data);
832
		} catch (InvalidShare $e) {
833
			throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
834
		}
835
836
		return $share;
837
	}
838
839
	/**
840
	 * remove share from table
841
	 *
842
	 * @param string $shareId
843
	 */
844
	protected function removeShareFromTable($shareId) {
845
		$qb = $this->dbConnection->getQueryBuilder();
846
		$qb->delete('share')
847
			->where($qb->expr()->eq('id', $qb->createNamedParameter($shareId)));
848
		$qb->execute();
849
	}
850
851
	/**
852
	 * Create a share object from an database row
853
	 *
854
	 * @param array $data
855
	 * @return IShare
856
	 * @throws InvalidShare
857
	 * @throws ShareNotFound
858
	 */
859
	protected function createShareObject($data) {
860
861
		$share = new Share($this->rootFolder, $this->userManager);
862
		$share->setId((int)$data['id'])
863
			->setShareType((int)$data['share_type'])
864
			->setPermissions((int)$data['permissions'])
865
			->setTarget($data['file_target'])
866
			->setMailSend((bool)$data['mail_send'])
867
			->setToken($data['token']);
868
869
		$shareTime = new \DateTime();
870
		$shareTime->setTimestamp((int)$data['stime']);
871
		$share->setShareTime($shareTime);
872
		$share->setSharedWith($data['share_with']);
873
		$share->setPassword($data['password']);
874
875
		if ($data['uid_initiator'] !== null) {
876
			$share->setShareOwner($data['uid_owner']);
877
			$share->setSharedBy($data['uid_initiator']);
878
		} else {
879
			//OLD SHARE
880
			$share->setSharedBy($data['uid_owner']);
881
			$path = $this->getNode($share->getSharedBy(), (int)$data['file_source']);
882
883
			$owner = $path->getOwner();
884
			$share->setShareOwner($owner->getUID());
885
		}
886
887 View Code Duplication
		if ($data['expiration'] !== null) {
888
			$expiration = \DateTime::createFromFormat('Y-m-d H:i:s', $data['expiration']);
889
			if ($expiration !== false) {
890
				$share->setExpirationDate($expiration);
891
			}
892
		}
893
894
		$share->setNodeId((int)$data['file_source']);
895
		$share->setNodeType($data['item_type']);
896
897
		$share->setProviderId($this->identifier());
898
899
		return $share;
900
	}
901
902
	/**
903
	 * Get the node with file $id for $user
904
	 *
905
	 * @param string $userId
906
	 * @param int $id
907
	 * @return \OCP\Files\File|\OCP\Files\Folder
908
	 * @throws InvalidShare
909
	 */
910 View Code Duplication
	private function getNode($userId, $id) {
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...
911
		try {
912
			$userFolder = $this->rootFolder->getUserFolder($userId);
913
		} catch (NotFoundException $e) {
0 ignored issues
show
Bug introduced by
The class OCA\ShareByMail\NotFoundException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
914
			throw new InvalidShare();
915
		}
916
917
		$nodes = $userFolder->getById($id);
918
919
		if (empty($nodes)) {
920
			throw new InvalidShare();
921
		}
922
923
		return $nodes[0];
924
	}
925
926
	/**
927
	 * A user is deleted from the system
928
	 * So clean up the relevant shares.
929
	 *
930
	 * @param string $uid
931
	 * @param int $shareType
932
	 */
933 View Code Duplication
	public function userDeleted($uid, $shareType) {
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...
934
		$qb = $this->dbConnection->getQueryBuilder();
935
936
		$qb->delete('share')
937
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)))
938
			->andWhere($qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)))
939
			->execute();
940
	}
941
942
	/**
943
	 * This provider does not support group shares
944
	 *
945
	 * @param string $gid
946
	 */
947
	public function groupDeleted($gid) {
948
		return;
949
	}
950
951
	/**
952
	 * This provider does not support group shares
953
	 *
954
	 * @param string $uid
955
	 * @param string $gid
956
	 */
957
	public function userDeletedFromGroup($uid, $gid) {
958
		return;
959
	}
960
961
	/**
962
	 * get database row of a give share
963
	 *
964
	 * @param $id
965
	 * @return array
966
	 * @throws ShareNotFound
967
	 */
968 View Code Duplication
	protected function getRawShare($id) {
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...
969
970
		// Now fetch the inserted share and create a complete share object
971
		$qb = $this->dbConnection->getQueryBuilder();
972
		$qb->select('*')
973
			->from('share')
974
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
975
976
		$cursor = $qb->execute();
977
		$data = $cursor->fetch();
978
		$cursor->closeCursor();
979
980
		if ($data === false) {
981
			throw new ShareNotFound;
982
		}
983
984
		return $data;
985
	}
986
987 View Code Duplication
	public function getSharesInFolder($userId, Folder $node, $reshares) {
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...
988
		$qb = $this->dbConnection->getQueryBuilder();
989
		$qb->select('*')
990
			->from('share', 's')
991
			->andWhere($qb->expr()->orX(
992
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
993
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
994
			))
995
			->andWhere(
996
				$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL))
997
			);
998
999
		/**
1000
		 * Reshares for this user are shares where they are the owner.
1001
		 */
1002
		if ($reshares === false) {
1003
			$qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
1004
		} else {
1005
			$qb->andWhere(
1006
				$qb->expr()->orX(
1007
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
1008
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
1009
				)
1010
			);
1011
		}
1012
1013
		$qb->innerJoin('s', 'filecache' ,'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
1014
		$qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())));
1015
1016
		$qb->orderBy('id');
1017
1018
		$cursor = $qb->execute();
1019
		$shares = [];
1020
		while ($data = $cursor->fetch()) {
1021
			$shares[$data['fileid']][] = $this->createShareObject($data);
1022
		}
1023
		$cursor->closeCursor();
1024
1025
		return $shares;
1026
	}
1027
1028
	/**
1029
	 * @inheritdoc
1030
	 */
1031
	public function getAccessList($nodes, $currentAccess) {
1032
		$ids = [];
1033
		foreach ($nodes as $node) {
1034
			$ids[] = $node->getId();
1035
		}
1036
1037
		$qb = $this->dbConnection->getQueryBuilder();
1038
		$qb->select('share_with')
1039
			->from('share')
1040
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)))
1041
			->andWhere($qb->expr()->in('file_source', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)))
1042
			->andWhere($qb->expr()->orX(
1043
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
1044
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
1045
			))
1046
			->setMaxResults(1);
1047
		$cursor = $qb->execute();
1048
1049
		$mail = $cursor->fetch() !== false;
1050
		$cursor->closeCursor();
1051
1052
		return ['public' => $mail];
1053
	}
1054
1055
}
1056