Passed
Push — master ( 02306d...9f9b89 )
by Morris
14:24 queued 10s
created

Share::getItemsSharedWithUser()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 6
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bart Visscher <[email protected]>
6
 * @author Bernhard Reiter <[email protected]>
7
 * @author Bjoern Schiessle <[email protected]>
8
 * @author Björn Schießle <[email protected]>
9
 * @author Christopher Schäpers <[email protected]>
10
 * @author Joas Schilling <[email protected]>
11
 * @author Jörn Friedrich Dreyer <[email protected]>
12
 * @author Lukas Reschke <[email protected]>
13
 * @author Morris Jobke <[email protected]>
14
 * @author Robin Appelman <[email protected]>
15
 * @author Robin McCorkell <[email protected]>
16
 * @author Roeland Jago Douma <[email protected]>
17
 * @author Sebastian Döll <[email protected]>
18
 * @author Stefan Weil <[email protected]>
19
 * @author Thomas Müller <[email protected]>
20
 * @author Torben Dannhauer <[email protected]>
21
 * @author Vincent Petry <[email protected]>
22
 * @author Volkan Gezer <[email protected]>
23
 *
24
 * @license AGPL-3.0
25
 *
26
 * This code is free software: you can redistribute it and/or modify
27
 * it under the terms of the GNU Affero General Public License, version 3,
28
 * as published by the Free Software Foundation.
29
 *
30
 * This program is distributed in the hope that it will be useful,
31
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33
 * GNU Affero General Public License for more details.
34
 *
35
 * You should have received a copy of the GNU Affero General Public License, version 3,
36
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
37
 *
38
 */
39
40
namespace OC\Share;
41
42
use OCP\DB\QueryBuilder\IQueryBuilder;
43
use OCP\IConfig;
44
use OCP\ILogger;
45
use OCP\Util;
46
47
/**
48
 * This class provides the ability for apps to share their content between users.
49
 * Apps must create a backend class that implements OCP\Share_Backend and register it with this class.
50
 *
51
 * It provides the following hooks:
52
 *  - post_shared
53
 */
54
class Share extends Constants {
55
56
	/** CRUDS permissions (Create, Read, Update, Delete, Share) using a bitmask
57
	 * Construct permissions for share() and setPermissions with Or (|) e.g.
58
	 * Give user read and update permissions: PERMISSION_READ | PERMISSION_UPDATE
59
	 *
60
	 * Check if permission is granted with And (&) e.g. Check if delete is
61
	 * granted: if ($permissions & PERMISSION_DELETE)
62
	 *
63
	 * Remove permissions with And (&) and Not (~) e.g. Remove the update
64
	 * permission: $permissions &= ~PERMISSION_UPDATE
65
	 *
66
	 * Apps are required to handle permissions on their own, this class only
67
	 * stores and manages the permissions of shares
68
	 * @see lib/public/constants.php
69
	 */
70
71
	/**
72
	 * Register a sharing backend class that implements OCP\Share_Backend for an item type
73
	 * @param string $itemType Item type
74
	 * @param string $class Backend class
75
	 * @param string $collectionOf (optional) Depends on item type
76
	 * @param array $supportedFileExtensions (optional) List of supported file extensions if this item type depends on files
77
	 * @return boolean true if backend is registered or false if error
78
	 */
79
	public static function registerBackend($itemType, $class, $collectionOf = null, $supportedFileExtensions = null) {
80
		if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') == 'yes') {
81
			if (!isset(self::$backendTypes[$itemType])) {
82
				self::$backendTypes[$itemType] = array(
83
					'class' => $class,
84
					'collectionOf' => $collectionOf,
85
					'supportedFileExtensions' => $supportedFileExtensions
86
				);
87
				if(count(self::$backendTypes) === 1) {
88
					Util::addScript('core', 'dist/share_backend');
89
				}
90
				return true;
91
			}
92
			\OCP\Util::writeLog('OCP\Share',
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

92
			/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('OCP\Share',

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
93
				'Sharing backend '.$class.' not registered, '.self::$backendTypes[$itemType]['class']
94
				.' is already registered for '.$itemType,
95
				ILogger::WARN);
96
		}
97
		return false;
98
	}
99
100
	/**
101
	 * Get the items of item type shared with the current user
102
	 * @param string $itemType
103
	 * @param int $format (optional) Format type must be defined by the backend
104
	 * @param mixed $parameters (optional)
105
	 * @param int $limit Number of items to return (optional) Returns all by default
106
	 * @param boolean $includeCollections (optional)
107
	 * @return mixed Return depends on format
108
	 * @deprecated TESTS ONLY - this methods is only used by tests
109
	 * called like this:
110
	 * \OC\Share\Share::getItemsSharedWith('folder'); (apps/files_sharing/tests/UpdaterTest.php)
111
	 */
112
	public static function getItemsSharedWith() {
113
		return self::getItems('folder', null, self::$shareTypeUserAndGroups, \OC_User::getUser(), null, self::FORMAT_NONE,
114
			null, -1, false);
115
	}
116
117
	/**
118
	 * Get the items of item type shared with a user
119
	 * @param string $itemType
120
	 * @param string $user id for which user we want the shares
121
	 * @param int $format (optional) Format type must be defined by the backend
122
	 * @param mixed $parameters (optional)
123
	 * @param int $limit Number of items to return (optional) Returns all by default
124
	 * @param boolean $includeCollections (optional)
125
	 * @return mixed Return depends on format
126
	 */
127
	public static function getItemsSharedWithUser($itemType, $user, $format = self::FORMAT_NONE,
128
												  $parameters = null, $limit = -1, $includeCollections = false) {
129
		return self::getItems($itemType, null, self::$shareTypeUserAndGroups, $user, null, $format,
130
			$parameters, $limit, $includeCollections);
131
	}
132
133
	/**
134
	 * Get the item of item type shared with a given user by source
135
	 * @param string $itemType
136
	 * @param string $itemSource
137
	 * @param string $user User to whom the item was shared
138
	 * @param string $owner Owner of the share
139
	 * @param int $shareType only look for a specific share type
140
	 * @return array Return list of items with file_target, permissions and expiration
141
	 */
142
	public static function getItemSharedWithUser($itemType, $itemSource, $user, $owner = null, $shareType = null) {
143
		$shares = array();
144
		$fileDependent = false;
145
146
		$where = 'WHERE';
147
		$fileDependentWhere = '';
148
		if ($itemType === 'file' || $itemType === 'folder') {
149
			$fileDependent = true;
150
			$column = 'file_source';
151
			$fileDependentWhere = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` ';
152
			$fileDependentWhere .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` ';
153
		} else {
154
			$column = 'item_source';
155
		}
156
157
		$select = self::createSelectStatement(self::FORMAT_NONE, $fileDependent);
158
159
		$where .= ' `' . $column . '` = ? AND `item_type` = ? ';
160
		$arguments = array($itemSource, $itemType);
161
		// for link shares $user === null
162
		if ($user !== null) {
0 ignored issues
show
introduced by
The condition $user !== null is always true.
Loading history...
163
			$where .= ' AND `share_with` = ? ';
164
			$arguments[] = $user;
165
		}
166
167
		if ($shareType !== null) {
168
			$where .= ' AND `share_type` = ? ';
169
			$arguments[] = $shareType;
170
		}
171
172
		if ($owner !== null) {
173
			$where .= ' AND `uid_owner` = ? ';
174
			$arguments[] = $owner;
175
		}
176
177
		$query = \OC_DB::prepare('SELECT ' . $select . ' FROM `*PREFIX*share` '. $fileDependentWhere . $where);
178
179
		$result = \OC_DB::executeAudited($query, $arguments);
180
181
		while ($row = $result->fetchRow()) {
182
			if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
183
				continue;
184
			}
185
			if ($fileDependent && (int)$row['file_parent'] === -1) {
186
				// if it is a mount point we need to get the path from the mount manager
187
				$mountManager = \OC\Files\Filesystem::getMountManager();
188
				$mountPoint = $mountManager->findByStorageId($row['storage_id']);
189
				if (!empty($mountPoint)) {
190
					$path = $mountPoint[0]->getMountPoint();
191
					$path = trim($path, '/');
192
					$path = substr($path, strlen($owner) + 1); //normalize path to 'files/foo.txt`
193
					$row['path'] = $path;
194
				} else {
195
					\OC::$server->getLogger()->warning(
196
						'Could not resolve mount point for ' . $row['storage_id'],
197
						['app' => 'OCP\Share']
198
					);
199
				}
200
			}
201
			$shares[] = $row;
202
		}
203
204
		//if didn't found a result than let's look for a group share.
205
		if(empty($shares) && $user !== null) {
206
			$userObject = \OC::$server->getUserManager()->get($user);
207
			$groups = [];
208
			if ($userObject) {
209
				$groups = \OC::$server->getGroupManager()->getUserGroupIds($userObject);
210
			}
211
212
			if (!empty($groups)) {
213
				$where = $fileDependentWhere . ' WHERE `' . $column . '` = ? AND `item_type` = ? AND `share_with` in (?)';
214
				$arguments = array($itemSource, $itemType, $groups);
215
				$types = array(null, null, IQueryBuilder::PARAM_STR_ARRAY);
216
217
				if ($owner !== null) {
218
					$where .= ' AND `uid_owner` = ?';
219
					$arguments[] = $owner;
220
					$types[] = null;
221
				}
222
223
				// TODO: inject connection, hopefully one day in the future when this
224
				// class isn't static anymore...
225
				$conn = \OC::$server->getDatabaseConnection();
226
				$result = $conn->executeQuery(
227
					'SELECT ' . $select . ' FROM `*PREFIX*share` ' . $where,
228
					$arguments,
229
					$types
230
				);
231
232
				while ($row = $result->fetch()) {
233
					$shares[] = $row;
234
				}
235
			}
236
		}
237
238
		return $shares;
239
240
	}
241
242
	/**
243
	 * Get the item of item type shared with the current user by source
244
	 * @param string $itemType
245
	 * @param string $itemSource
246
	 * @param int $format (optional) Format type must be defined by the backend
247
	 * @param mixed $parameters
248
	 * @param boolean $includeCollections
249
	 * @param string $shareWith (optional) define against which user should be checked, default: current user
250
	 * @return array
251
	 */
252
	public static function getItemSharedWithBySource($itemType, $itemSource, $format = self::FORMAT_NONE,
253
													 $parameters = null, $includeCollections = false, $shareWith = null) {
254
		$shareWith = ($shareWith === null) ? \OC_User::getUser() : $shareWith;
255
		return self::getItems($itemType, $itemSource, self::$shareTypeUserAndGroups, $shareWith, null, $format,
256
			$parameters, 1, $includeCollections, true);
257
	}
258
259
	/**
260
	 * Based on the given token the share information will be returned - password protected shares will be verified
261
	 * @param string $token
262
	 * @param bool $checkPasswordProtection
263
	 * @return array|boolean false will be returned in case the token is unknown or unauthorized
264
	 */
265
	public static function getShareByToken($token, $checkPasswordProtection = true) {
266
		$query = \OC_DB::prepare('SELECT * FROM `*PREFIX*share` WHERE `token` = ?', 1);
267
		$result = $query->execute(array($token));
268
		if ($result === false) {
269
			\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage() . ', token=' . $token, ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

269
			/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage() . ', token=' . $token, ILogger::ERROR);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
270
		}
271
		$row = $result->fetchRow();
272
		if ($row === false) {
273
			return false;
274
		}
275
		if (is_array($row) and self::expireItem($row)) {
276
			return false;
277
		}
278
279
		// password protected shares need to be authenticated
280
		if ($checkPasswordProtection && !\OC\Share\Share::checkPasswordProtectedShare($row)) {
281
			return false;
282
		}
283
284
		return $row;
285
	}
286
287
	/**
288
	 * Get the shared items of item type owned by the current user
289
	 * @param string $itemType
290
	 * @param int $format (optional) Format type must be defined by the backend
291
	 * @param mixed $parameters
292
	 * @param int $limit Number of items to return (optional) Returns all by default
293
	 * @param boolean $includeCollections
294
	 * @return mixed Return depends on format
295
	 */
296
	public static function getItemsShared($itemType, $format = self::FORMAT_NONE, $parameters = null,
297
										  $limit = -1, $includeCollections = false) {
298
		return self::getItems($itemType, null, null, null, \OC_User::getUser(), $format,
299
			$parameters, $limit, $includeCollections);
300
	}
301
302
	/**
303
	 * Get the shared item of item type owned by the current user
304
	 * @param string $itemType
305
	 * @param string $itemSource
306
	 * @param int $format (optional) Format type must be defined by the backend
307
	 * @param mixed $parameters
308
	 * @param boolean $includeCollections
309
	 * @return mixed Return depends on format
310
	 */
311
	public static function getItemShared($itemType, $itemSource, $format = self::FORMAT_NONE,
312
										 $parameters = null, $includeCollections = false) {
313
		return self::getItems($itemType, $itemSource, null, null, \OC_User::getUser(), $format,
314
			$parameters, -1, $includeCollections);
315
	}
316
317
	/**
318
	 * Share an item with a user, group, or via private link
319
	 * @param string $itemType
320
	 * @param string $itemSource
321
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
322
	 * @param string $shareWith User or group the item is being shared with
323
	 * @param int $permissions CRUDS
324
	 * @param string $itemSourceName
325
	 * @param \DateTime|null $expirationDate
326
	 * @return boolean|string Returns true on success or false on failure, Returns token on success for links
327
	 * @throws \OC\HintException when the share type is remote and the shareWith is invalid
328
	 * @throws \Exception
329
	 * @since 5.0.0 - parameter $itemSourceName was added in 6.0.0, parameter $expirationDate was added in 7.0.0, parameter $passwordChanged added in 9.0.0
330
	 * @deprecated 14.0.0 TESTS ONLY - this methods is as of 2018-06 only used by tests
331
	 * called like this:
332
	 * \OC\Share\Share::shareItem('test', 1, \OCP\Share::SHARE_TYPE_USER, $otherUserId, \OCP\Constants::PERMISSION_READ);
333
	 */
334
	public static function shareItem($itemType, $itemSource, $shareType, $shareWith, $permissions) {
335
		$backend = self::getBackend($itemType);
336
337
		if ($backend->isShareTypeAllowed($shareType) === false) {
338
			$message = 'Sharing failed, because the backend does not allow shares from type %i';
339
			throw new \Exception(sprintf($message, $shareType));
340
		}
341
342
		$uidOwner = \OC_User::getUser();
343
344
		// Verify share type and sharing conditions are met
345
		if ($shareType === self::SHARE_TYPE_USER) {
346
			if ($shareWith == $uidOwner) {
347
				$message = 'Sharing failed, because you can not share with yourself';
348
				throw new \Exception($message);
349
			}
350
			if (!\OC::$server->getUserManager()->userExists($shareWith)) {
351
				$message = 'Sharing failed, because the user %s does not exist';
352
				throw new \Exception(sprintf($message, $shareWith));
353
			}
354
			// Check if the item source is already shared with the user, either from the same owner or a different user
355
			if ($checkExists = self::getItems($itemType, $itemSource, self::$shareTypeUserAndGroups,
356
				$shareWith, null, self::FORMAT_NONE, null, 1, true, true)) {
357
				// Only allow the same share to occur again if it is the same
358
				// owner and is not a user share, this use case is for increasing
359
				// permissions for a specific user
360
				if ($checkExists['uid_owner'] != $uidOwner || $checkExists['share_type'] == $shareType) {
361
					$message = 'Sharing failed, because this item is already shared with %s';
362
					throw new \Exception(sprintf($message, $shareWith));
363
				}
364
			}
365
			if ($checkExists = self::getItems($itemType, $itemSource, self::SHARE_TYPE_USER,
366
				$shareWith, null, self::FORMAT_NONE, null, 1, true, true)) {
367
				// Only allow the same share to occur again if it is the same
368
				// owner and is not a user share, this use case is for increasing
369
				// permissions for a specific user
370
				if ($checkExists['uid_owner'] != $uidOwner || $checkExists['share_type'] == $shareType) {
371
					$message = 'Sharing failed, because this item is already shared with user %s';
372
					throw new \Exception(sprintf($message, $shareWith));
373
				}
374
			}
375
		}
376
377
		// Put the item into the database
378
		$result = self::put('test', $itemSource, self::SHARE_TYPE_USER, $shareWith, $uidOwner, $permissions);
0 ignored issues
show
Deprecated Code introduced by
The function OC\Share\Share::put() has been deprecated: TESTS ONLY - this methods is only used by tests called like this: self::put('test', $itemSource, self::SHARE_TYPE_USER, $shareWith, $uidOwner, $permissions); ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

378
		$result = /** @scrutinizer ignore-deprecated */ self::put('test', $itemSource, self::SHARE_TYPE_USER, $shareWith, $uidOwner, $permissions);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
379
380
		return $result ? true : false;
381
	}
382
383
	/**
384
	 * Unshare an item from a user, group, or delete a private link
385
	 * @param string $itemType
386
	 * @param string $itemSource
387
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
388
	 * @param string $shareWith User or group the item is being shared with
389
	 * @param string $owner owner of the share, if null the current user is used
390
	 * @return boolean true on success or false on failure
391
	 */
392
	public static function unshare($itemType, $itemSource, $shareType, $shareWith, $owner = null) {
393
394
		// check if it is a valid itemType
395
		self::getBackend($itemType);
396
397
		$items = self::getItemSharedWithUser($itemType, $itemSource, $shareWith, $owner, $shareType);
398
399
		$toDelete = array();
400
		$newParent = null;
401
		$currentUser = $owner ? $owner : \OC_User::getUser();
402
		foreach ($items as $item) {
403
			// delete the item with the expected share_type and owner
404
			if ((int)$item['share_type'] === (int)$shareType && $item['uid_owner'] === $currentUser) {
405
				$toDelete = $item;
406
				// if there is more then one result we don't have to delete the children
407
				// but update their parent. For group shares the new parent should always be
408
				// the original group share and not the db entry with the unique name
409
			} else if ((int)$item['share_type'] === self::$shareTypeGroupUserUnique) {
410
				$newParent = $item['parent'];
411
			} else {
412
				$newParent = $item['id'];
413
			}
414
		}
415
416
		if (!empty($toDelete)) {
417
			self::unshareItem($toDelete, $newParent);
418
			return true;
419
		}
420
		return false;
421
	}
422
423
	/**
424
	 * sent status if users got informed by mail about share
425
	 * @param string $itemType
426
	 * @param string $itemSource
427
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
428
	 * @param string $recipient with whom was the file shared
429
	 * @param boolean $status
430
	 */
431
	public static function setSendMailStatus($itemType, $itemSource, $shareType, $recipient, $status) {
432
		$status = $status ? 1 : 0;
433
434
		$query = \OC_DB::prepare(
435
			'UPDATE `*PREFIX*share`
436
					SET `mail_send` = ?
437
					WHERE `item_type` = ? AND `item_source` = ? AND `share_type` = ? AND `share_with` = ?');
438
439
		$result = $query->execute(array($status, $itemType, $itemSource, $shareType, $recipient));
440
441
		if($result === false) {
442
			\OCP\Util::writeLog('OCP\Share', 'Couldn\'t set send mail status', ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

442
			/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('OCP\Share', 'Couldn\'t set send mail status', ILogger::ERROR);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
443
		}
444
	}
445
446
	/**
447
	 * Checks whether a share has expired, calls unshareItem() if yes.
448
	 * @param array $item Share data (usually database row)
449
	 * @return boolean True if item was expired, false otherwise.
450
	 */
451
	protected static function expireItem(array $item) {
452
453
		$result = false;
454
455
		// only use default expiration date for link shares
456
		if ((int) $item['share_type'] === self::SHARE_TYPE_LINK) {
457
458
			// calculate expiration date
459
			if (!empty($item['expiration'])) {
460
				$userDefinedExpire = new \DateTime($item['expiration']);
461
				$expires = $userDefinedExpire->getTimestamp();
462
			} else {
463
				$expires = null;
464
			}
465
466
467
			// get default expiration settings
468
			$defaultSettings = Helper::getDefaultExpireSetting();
469
			$expires = Helper::calculateExpireDate($defaultSettings, $item['stime'], $expires);
470
471
472
			if (is_int($expires)) {
473
				$now = time();
474
				if ($now > $expires) {
475
					self::unshareItem($item);
476
					$result = true;
477
				}
478
			}
479
		}
480
		return $result;
481
	}
482
483
	/**
484
	 * Unshares a share given a share data array
485
	 * @param array $item Share data (usually database row)
486
	 * @param int $newParent parent ID
487
	 * @return null
488
	 */
489
	protected static function unshareItem(array $item, $newParent = null) {
490
491
		$shareType = (int)$item['share_type'];
492
		$shareWith = null;
493
		if ($shareType !== \OCP\Share::SHARE_TYPE_LINK) {
494
			$shareWith = $item['share_with'];
495
		}
496
497
		// Pass all the vars we have for now, they may be useful
498
		$hookParams = array(
499
			'id'            => $item['id'],
500
			'itemType'      => $item['item_type'],
501
			'itemSource'    => $item['item_source'],
502
			'shareType'     => $shareType,
503
			'shareWith'     => $shareWith,
504
			'itemParent'    => $item['parent'],
505
			'uidOwner'      => $item['uid_owner'],
506
		);
507
		if($item['item_type'] === 'file' || $item['item_type'] === 'folder') {
508
			$hookParams['fileSource'] = $item['file_source'];
509
			$hookParams['fileTarget'] = $item['file_target'];
510
		}
511
512
		\OC_Hook::emit(\OCP\Share::class, 'pre_unshare', $hookParams);
513
		$deletedShares = Helper::delete($item['id'], false, null, $newParent);
514
		$deletedShares[] = $hookParams;
515
		$hookParams['deletedShares'] = $deletedShares;
516
		\OC_Hook::emit(\OCP\Share::class, 'post_unshare', $hookParams);
517
		if ((int)$item['share_type'] === \OCP\Share::SHARE_TYPE_REMOTE && \OC::$server->getUserSession()->getUser()) {
518
			list(, $remote) = Helper::splitUserRemote($item['share_with']);
519
			self::sendRemoteUnshare($remote, $item['id'], $item['token']);
520
		}
521
	}
522
523
	/**
524
	 * Get the backend class for the specified item type
525
	 * @param string $itemType
526
	 * @throws \Exception
527
	 * @return \OCP\Share_Backend
528
	 */
529
	public static function getBackend($itemType) {
530
		$l = \OC::$server->getL10N('lib');
531
		if (isset(self::$backends[$itemType])) {
532
			return self::$backends[$itemType];
533
		} else if (isset(self::$backendTypes[$itemType]['class'])) {
534
			$class = self::$backendTypes[$itemType]['class'];
535
			if (class_exists($class)) {
536
				self::$backends[$itemType] = new $class;
537
				if (!(self::$backends[$itemType] instanceof \OCP\Share_Backend)) {
538
					$message = 'Sharing backend %s must implement the interface OCP\Share_Backend';
539
					$message_t = $l->t('Sharing backend %s must implement the interface OCP\Share_Backend', array($class));
540
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $class), ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

540
					/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('OCP\Share', sprintf($message, $class), ILogger::ERROR);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
541
					throw new \Exception($message_t);
542
				}
543
				return self::$backends[$itemType];
544
			} else {
545
				$message = 'Sharing backend %s not found';
546
				$message_t = $l->t('Sharing backend %s not found', array($class));
547
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $class), ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

547
				/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('OCP\Share', sprintf($message, $class), ILogger::ERROR);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
548
				throw new \Exception($message_t);
549
			}
550
		}
551
		$message = 'Sharing backend for %s not found';
552
		$message_t = $l->t('Sharing backend for %s not found', array($itemType));
553
		\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemType), ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

553
		/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemType), ILogger::ERROR);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
554
		throw new \Exception($message_t);
555
	}
556
557
	/**
558
	 * Check if resharing is allowed
559
	 * @return boolean true if allowed or false
560
	 *
561
	 * Resharing is allowed by default if not configured
562
	 */
563
	public static function isResharingAllowed() {
564
		if (!isset(self::$isResharingAllowed)) {
565
			if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_resharing', 'yes') == 'yes') {
566
				self::$isResharingAllowed = true;
567
			} else {
568
				self::$isResharingAllowed = false;
569
			}
570
		}
571
		return self::$isResharingAllowed;
572
	}
573
574
	/**
575
	 * Get a list of collection item types for the specified item type
576
	 * @param string $itemType
577
	 * @return array
578
	 */
579
	private static function getCollectionItemTypes($itemType) {
580
		$collectionTypes = array($itemType);
581
		foreach (self::$backendTypes as $type => $backend) {
582
			if (in_array($backend['collectionOf'], $collectionTypes)) {
583
				$collectionTypes[] = $type;
584
			}
585
		}
586
		// TODO Add option for collections to be collection of themselves, only 'folder' does it now...
587
		if (isset(self::$backendTypes[$itemType]) && (!self::getBackend($itemType) instanceof \OCP\Share_Backend_Collection || $itemType != 'folder')) {
588
			unset($collectionTypes[0]);
589
		}
590
		// Return array if collections were found or the item type is a
591
		// collection itself - collections can be inside collections
592
		if (count($collectionTypes) > 0) {
593
			return $collectionTypes;
594
		}
595
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
596
	}
597
598
	/**
599
	 * Get the owners of items shared with a user.
600
	 *
601
	 * @param string $user The user the items are shared with.
602
	 * @param string $type The type of the items shared with the user.
603
	 * @param boolean $includeCollections Include collection item types (optional)
604
	 * @param boolean $includeOwner include owner in the list of users the item is shared with (optional)
605
	 * @return array
606
	 * @deprecated TESTS ONLY - this methods is only used by tests
607
	 * called like this:
608
	 * \OC\Share\Share::getSharedItemsOwners($this->user, $this->type, true)
609
	 */
610
	public static function getSharedItemsOwners($user, $type, $includeCollections = false, $includeOwner = false) {
611
		// First, we find out if $type is part of a collection (and if that collection is part of
612
		// another one and so on).
613
		$collectionTypes = array();
614
		if (!$includeCollections || !$collectionTypes = self::getCollectionItemTypes($type)) {
615
			$collectionTypes[] = $type;
616
		}
617
618
		// Of these collection types, along with our original $type, we make a
619
		// list of the ones for which a sharing backend has been registered.
620
		// FIXME: Ideally, we wouldn't need to nest getItemsSharedWith in this loop but just call it
621
		// with its $includeCollections parameter set to true. Unfortunately, this fails currently.
622
		$allMaybeSharedItems = array();
623
		foreach ($collectionTypes as $collectionType) {
624
			if (isset(self::$backends[$collectionType])) {
625
				$allMaybeSharedItems[$collectionType] = self::getItemsSharedWithUser(
626
					$collectionType,
627
					$user,
628
					self::FORMAT_NONE
629
				);
630
			}
631
		}
632
633
		$owners = array();
634
		if ($includeOwner) {
635
			$owners[] = $user;
636
		}
637
638
		// We take a look at all shared items of the given $type (or of the collections it is part of)
639
		// and find out their owners. Then, we gather the tags for the original $type from all owners,
640
		// and return them as elements of a list that look like "Tag (owner)".
641
		foreach ($allMaybeSharedItems as $collectionType => $maybeSharedItems) {
642
			foreach ($maybeSharedItems as $sharedItem) {
643
				if (isset($sharedItem['id'])) { //workaround for https://github.com/owncloud/core/issues/2814
644
					$owners[] = $sharedItem['uid_owner'];
645
				}
646
			}
647
		}
648
649
		return $owners;
650
	}
651
652
	/**
653
	 * Get shared items from the database
654
	 * @param string $itemType
655
	 * @param string $item Item source or target (optional)
656
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, SHARE_TYPE_LINK, $shareTypeUserAndGroups, or $shareTypeGroupUserUnique
657
	 * @param string $shareWith User or group the item is being shared with
658
	 * @param string $uidOwner User that is the owner of shared items (optional)
659
	 * @param int $format Format to convert items to with formatItems() (optional)
660
	 * @param mixed $parameters to pass to formatItems() (optional)
661
	 * @param int $limit Number of items to return, -1 to return all matches (optional)
662
	 * @param boolean $includeCollections Include collection item types (optional)
663
	 * @param boolean $itemShareWithBySource (optional)
664
	 * @param boolean $checkExpireDate
665
	 * @return array
666
	 *
667
	 * See public functions getItem(s)... for parameter usage
668
	 *
669
	 */
670
	public static function getItems($itemType, $item = null, $shareType = null, $shareWith = null,
671
									$uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1,
672
									$includeCollections = false, $itemShareWithBySource = false, $checkExpireDate  = true) {
673
		if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') != 'yes') {
674
			return array();
675
		}
676
		$backend = self::getBackend($itemType);
677
		$collectionTypes = false;
678
		// Get filesystem root to add it to the file target and remove from the
679
		// file source, match file_source with the file cache
680
		if ($itemType == 'file' || $itemType == 'folder') {
681
			if(!is_null($uidOwner)) {
682
				$root = \OC\Files\Filesystem::getRoot();
683
			} else {
684
				$root = '';
685
			}
686
			$where = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` ';
687
			if (!isset($item)) {
688
				$where .= ' AND `file_target` IS NOT NULL ';
689
			}
690
			$where .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` ';
691
			$fileDependent = true;
692
			$queryArgs = array();
693
		} else {
694
			$fileDependent = false;
695
			$root = '';
696
			$collectionTypes = self::getCollectionItemTypes($itemType);
697
			if ($includeCollections && !isset($item) && $collectionTypes) {
698
				// If includeCollections is true, find collections of this item type, e.g. a music album contains songs
699
				if (!in_array($itemType, $collectionTypes)) {
700
					$itemTypes = array_merge(array($itemType), $collectionTypes);
701
				} else {
702
					$itemTypes = $collectionTypes;
703
				}
704
				$placeholders = implode(',', array_fill(0, count($itemTypes), '?'));
705
				$where = ' WHERE `item_type` IN ('.$placeholders.'))';
706
				$queryArgs = $itemTypes;
707
			} else {
708
				$where = ' WHERE `item_type` = ?';
709
				$queryArgs = array($itemType);
710
			}
711
		}
712
		if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
713
			$where .= ' AND `share_type` != ?';
714
			$queryArgs[] = self::SHARE_TYPE_LINK;
715
		}
716
		if (isset($shareType)) {
717
			// Include all user and group items
718
			if ($shareType == self::$shareTypeUserAndGroups && isset($shareWith)) {
719
				$where .= ' AND ((`share_type` in (?, ?) AND `share_with` = ?) ';
720
				$queryArgs[] = self::SHARE_TYPE_USER;
721
				$queryArgs[] = self::$shareTypeGroupUserUnique;
722
				$queryArgs[] = $shareWith;
723
724
				$user = \OC::$server->getUserManager()->get($shareWith);
725
				$groups = [];
726
				if ($user) {
727
					$groups = \OC::$server->getGroupManager()->getUserGroupIds($user);
728
				}
729
				if (!empty($groups)) {
730
					$placeholders = implode(',', array_fill(0, count($groups), '?'));
731
					$where .= ' OR (`share_type` = ? AND `share_with` IN ('.$placeholders.')) ';
732
					$queryArgs[] = self::SHARE_TYPE_GROUP;
733
					$queryArgs = array_merge($queryArgs, $groups);
734
				}
735
				$where .= ')';
736
				// Don't include own group shares
737
				$where .= ' AND `uid_owner` != ?';
738
				$queryArgs[] = $shareWith;
739
			} else {
740
				$where .= ' AND `share_type` = ?';
741
				$queryArgs[] = $shareType;
742
				if (isset($shareWith)) {
743
					$where .= ' AND `share_with` = ?';
744
					$queryArgs[] = $shareWith;
745
				}
746
			}
747
		}
748
		if (isset($uidOwner)) {
749
			$where .= ' AND `uid_owner` = ?';
750
			$queryArgs[] = $uidOwner;
751
			if (!isset($shareType)) {
752
				// Prevent unique user targets for group shares from being selected
753
				$where .= ' AND `share_type` != ?';
754
				$queryArgs[] = self::$shareTypeGroupUserUnique;
755
			}
756
			if ($fileDependent) {
757
				$column = 'file_source';
758
			} else {
759
				$column = 'item_source';
760
			}
761
		} else {
762
			if ($fileDependent) {
763
				$column = 'file_target';
764
			} else {
765
				$column = 'item_target';
766
			}
767
		}
768
		if (isset($item)) {
769
			$collectionTypes = self::getCollectionItemTypes($itemType);
770
			if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
771
				$where .= ' AND (';
772
			} else {
773
				$where .= ' AND';
774
			}
775
			// If looking for own shared items, check item_source else check item_target
776
			if (isset($uidOwner) || $itemShareWithBySource) {
777
				// If item type is a file, file source needs to be checked in case the item was converted
778
				if ($fileDependent) {
779
					$where .= ' `file_source` = ?';
780
					$column = 'file_source';
781
				} else {
782
					$where .= ' `item_source` = ?';
783
					$column = 'item_source';
784
				}
785
			} else {
786
				if ($fileDependent) {
787
					$where .= ' `file_target` = ?';
788
					$item = \OC\Files\Filesystem::normalizePath($item);
789
				} else {
790
					$where .= ' `item_target` = ?';
791
				}
792
			}
793
			$queryArgs[] = $item;
794
			if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
795
				$placeholders = implode(',', array_fill(0, count($collectionTypes), '?'));
796
				$where .= ' OR `item_type` IN ('.$placeholders.'))';
797
				$queryArgs = array_merge($queryArgs, $collectionTypes);
798
			}
799
		}
800
801
		if ($shareType == self::$shareTypeUserAndGroups && $limit === 1) {
802
			// Make sure the unique user target is returned if it exists,
803
			// unique targets should follow the group share in the database
804
			// If the limit is not 1, the filtering can be done later
805
			$where .= ' ORDER BY `*PREFIX*share`.`id` DESC';
806
		} else {
807
			$where .= ' ORDER BY `*PREFIX*share`.`id` ASC';
808
		}
809
810
		if ($limit != -1 && !$includeCollections) {
811
			// The limit must be at least 3, because filtering needs to be done
812
			if ($limit < 3) {
813
				$queryLimit = 3;
814
			} else {
815
				$queryLimit = $limit;
816
			}
817
		} else {
818
			$queryLimit = null;
819
		}
820
		$select = self::createSelectStatement($format, $fileDependent, $uidOwner);
821
		$root = strlen($root);
822
		$query = \OC_DB::prepare('SELECT '.$select.' FROM `*PREFIX*share` '.$where, $queryLimit);
823
		$result = $query->execute($queryArgs);
824
		if ($result === false) {
825
			\OCP\Util::writeLog('OCP\Share',
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

825
			/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('OCP\Share',

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
826
				\OC_DB::getErrorMessage() . ', select=' . $select . ' where=',
827
				ILogger::ERROR);
828
		}
829
		$items = array();
830
		$targets = array();
831
		$switchedItems = array();
832
		$mounts = array();
833
		while ($row = $result->fetchRow()) {
834
			self::transformDBResults($row);
835
			// Filter out duplicate group shares for users with unique targets
836
			if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
837
				continue;
838
			}
839
			if ($row['share_type'] == self::$shareTypeGroupUserUnique && isset($items[$row['parent']])) {
840
				$row['share_type'] = self::SHARE_TYPE_GROUP;
841
				$row['unique_name'] = true; // remember that we use a unique name for this user
842
				$row['share_with'] = $items[$row['parent']]['share_with'];
843
				// if the group share was unshared from the user we keep the permission, otherwise
844
				// we take the permission from the parent because this is always the up-to-date
845
				// permission for the group share
846
				if ($row['permissions'] > 0) {
847
					$row['permissions'] = $items[$row['parent']]['permissions'];
848
				}
849
				// Remove the parent group share
850
				unset($items[$row['parent']]);
851
				if ($row['permissions'] == 0) {
852
					continue;
853
				}
854
			} else if (!isset($uidOwner)) {
855
				// Check if the same target already exists
856
				if (isset($targets[$row['id']])) {
857
					// Check if the same owner shared with the user twice
858
					// through a group and user share - this is allowed
859
					$id = $targets[$row['id']];
860
					if (isset($items[$id]) && $items[$id]['uid_owner'] == $row['uid_owner']) {
861
						// Switch to group share type to ensure resharing conditions aren't bypassed
862
						if ($items[$id]['share_type'] != self::SHARE_TYPE_GROUP) {
863
							$items[$id]['share_type'] = self::SHARE_TYPE_GROUP;
864
							$items[$id]['share_with'] = $row['share_with'];
865
						}
866
						// Switch ids if sharing permission is granted on only
867
						// one share to ensure correct parent is used if resharing
868
						if (~(int)$items[$id]['permissions'] & \OCP\Constants::PERMISSION_SHARE
869
							&& (int)$row['permissions'] & \OCP\Constants::PERMISSION_SHARE) {
870
							$items[$row['id']] = $items[$id];
871
							$switchedItems[$id] = $row['id'];
872
							unset($items[$id]);
873
							$id = $row['id'];
874
						}
875
						$items[$id]['permissions'] |= (int)$row['permissions'];
876
877
					}
878
					continue;
879
				} elseif (!empty($row['parent'])) {
880
					$targets[$row['parent']] = $row['id'];
881
				}
882
			}
883
			// Remove root from file source paths if retrieving own shared items
884
			if (isset($uidOwner) && isset($row['path'])) {
885
				if (isset($row['parent'])) {
886
					$query = \OC_DB::prepare('SELECT `file_target` FROM `*PREFIX*share` WHERE `id` = ?');
887
					$parentResult = $query->execute(array($row['parent']));
888
					if ($result === false) {
889
						\OCP\Util::writeLog('OCP\Share', 'Can\'t select parent: ' .
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

889
						/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('OCP\Share', 'Can\'t select parent: ' .

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
890
							\OC_DB::getErrorMessage() . ', select=' . $select . ' where=' . $where,
891
							ILogger::ERROR);
892
					} else {
893
						$parentRow = $parentResult->fetchRow();
894
						$tmpPath = $parentRow['file_target'];
895
						// find the right position where the row path continues from the target path
896
						$pos = strrpos($row['path'], $parentRow['file_target']);
897
						$subPath = substr($row['path'], $pos);
898
						$splitPath = explode('/', $subPath);
899
						foreach (array_slice($splitPath, 2) as $pathPart) {
900
							$tmpPath = $tmpPath . '/' . $pathPart;
901
						}
902
						$row['path'] = $tmpPath;
903
					}
904
				} else {
905
					if (!isset($mounts[$row['storage']])) {
906
						$mountPoints = \OC\Files\Filesystem::getMountByNumericId($row['storage']);
907
						if (is_array($mountPoints) && !empty($mountPoints)) {
908
							$mounts[$row['storage']] = current($mountPoints);
909
						}
910
					}
911
					if (!empty($mounts[$row['storage']])) {
912
						$path = $mounts[$row['storage']]->getMountPoint().$row['path'];
913
						$relPath = substr($path, $root); // path relative to data/user
914
						$row['path'] = rtrim($relPath, '/');
915
					}
916
				}
917
			}
918
919
			if($checkExpireDate) {
920
				if (self::expireItem($row)) {
921
					continue;
922
				}
923
			}
924
			// Check if resharing is allowed, if not remove share permission
925
			if (isset($row['permissions']) && (!self::isResharingAllowed() | \OCP\Util::isSharingDisabledForUser())) {
0 ignored issues
show
Bug introduced by
Are you sure you want to use the bitwise | or did you mean ||?
Loading history...
Deprecated Code introduced by
The function OCP\Util::isSharingDisabledForUser() has been deprecated: 9.1.0 Use \OC::$server->getShareManager()->sharingDisabledForUser ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

925
			if (isset($row['permissions']) && (!self::isResharingAllowed() | /** @scrutinizer ignore-deprecated */ \OCP\Util::isSharingDisabledForUser())) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
926
				$row['permissions'] &= ~\OCP\Constants::PERMISSION_SHARE;
927
			}
928
			// Add display names to result
929
			$row['share_with_displayname'] = $row['share_with'];
930
			if ( isset($row['share_with']) && $row['share_with'] != '' &&
931
				$row['share_type'] === self::SHARE_TYPE_USER) {
932
				$shareWithUser = \OC::$server->getUserManager()->get($row['share_with']);
933
				$row['share_with_displayname'] = $shareWithUser === null ? $row['share_with'] : $shareWithUser->getDisplayName();
934
			} else if(isset($row['share_with']) && $row['share_with'] != '' &&
935
				$row['share_type'] === self::SHARE_TYPE_REMOTE) {
936
				$addressBookEntries = \OC::$server->getContactsManager()->search($row['share_with'], ['CLOUD']);
937
				foreach ($addressBookEntries as $entry) {
938
					foreach ($entry['CLOUD'] as $cloudID) {
939
						if ($cloudID === $row['share_with']) {
940
							$row['share_with_displayname'] = $entry['FN'];
941
						}
942
					}
943
				}
944
			}
945
			if ( isset($row['uid_owner']) && $row['uid_owner'] != '') {
946
				$ownerUser = \OC::$server->getUserManager()->get($row['uid_owner']);
947
				$row['displayname_owner'] = $ownerUser === null ? $row['uid_owner'] : $ownerUser->getDisplayName();
948
			}
949
950
			if ($row['permissions'] > 0) {
951
				$items[$row['id']] = $row;
952
			}
953
954
		}
955
956
		// group items if we are looking for items shared with the current user
957
		if (isset($shareWith) && $shareWith === \OCP\User::getUser()) {
0 ignored issues
show
Deprecated Code introduced by
The function OCP\User::getUser() has been deprecated: 8.0.0 Use \OC::$server->getUserSession()->getUser()->getUID() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

957
		if (isset($shareWith) && $shareWith === /** @scrutinizer ignore-deprecated */ \OCP\User::getUser()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
958
			$items = self::groupItems($items, $itemType);
959
		}
960
961
		if (!empty($items)) {
962
			$collectionItems = array();
963
			foreach ($items as &$row) {
964
				// Return only the item instead of a 2-dimensional array
965
				if ($limit == 1 && $row[$column] == $item && ($row['item_type'] == $itemType || $itemType == 'file')) {
966
					if ($format == self::FORMAT_NONE) {
967
						return $row;
968
					} else {
969
						break;
970
					}
971
				}
972
				// Check if this is a collection of the requested item type
973
				if ($includeCollections && $collectionTypes && $row['item_type'] !== 'folder' && in_array($row['item_type'], $collectionTypes)) {
974
					if (($collectionBackend = self::getBackend($row['item_type']))
975
						&& $collectionBackend instanceof \OCP\Share_Backend_Collection) {
976
						// Collections can be inside collections, check if the item is a collection
977
						if (isset($item) && $row['item_type'] == $itemType && $row[$column] == $item) {
978
							$collectionItems[] = $row;
979
						} else {
980
							$collection = array();
981
							$collection['item_type'] = $row['item_type'];
982
							if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
983
								$collection['path'] = basename($row['path']);
984
							}
985
							$row['collection'] = $collection;
986
							// Fetch all of the children sources
987
							$children = $collectionBackend->getChildren($row[$column]);
988
							foreach ($children as $child) {
989
								$childItem = $row;
990
								$childItem['item_type'] = $itemType;
991
								if ($row['item_type'] != 'file' && $row['item_type'] != 'folder') {
992
									$childItem['item_source'] = $child['source'];
993
									$childItem['item_target'] = $child['target'];
994
								}
995
								if ($backend instanceof \OCP\Share_Backend_File_Dependent) {
996
									if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
997
										$childItem['file_source'] = $child['source'];
998
									} else { // TODO is this really needed if we already know that we use the file backend?
999
										$meta = \OC\Files\Filesystem::getFileInfo($child['file_path']);
1000
										$childItem['file_source'] = $meta['fileid'];
1001
									}
1002
									$childItem['file_target'] =
1003
										\OC\Files\Filesystem::normalizePath($child['file_path']);
1004
								}
1005
								if (isset($item)) {
1006
									if ($childItem[$column] == $item) {
1007
										// Return only the item instead of a 2-dimensional array
1008
										if ($limit == 1) {
1009
											if ($format == self::FORMAT_NONE) {
1010
												return $childItem;
1011
											} else {
1012
												// Unset the items array and break out of both loops
1013
												$items = array();
1014
												$items[] = $childItem;
1015
												break 2;
1016
											}
1017
										} else {
1018
											$collectionItems[] = $childItem;
1019
										}
1020
									}
1021
								} else {
1022
									$collectionItems[] = $childItem;
1023
								}
1024
							}
1025
						}
1026
					}
1027
					// Remove collection item
1028
					$toRemove = $row['id'];
1029
					if (array_key_exists($toRemove, $switchedItems)) {
1030
						$toRemove = $switchedItems[$toRemove];
1031
					}
1032
					unset($items[$toRemove]);
1033
				} elseif ($includeCollections && $collectionTypes && in_array($row['item_type'], $collectionTypes)) {
1034
					// FIXME: Thats a dirty hack to improve file sharing performance,
1035
					// see github issue #10588 for more details
1036
					// Need to find a solution which works for all back-ends
1037
					$collectionBackend = self::getBackend($row['item_type']);
1038
					$sharedParents = $collectionBackend->getParents($row['item_source']);
0 ignored issues
show
Bug introduced by
The method getParents() does not exist on OCP\Share_Backend. It seems like you code against a sub-type of OCP\Share_Backend such as OCA\Files_Sharing\ShareBackend\Folder or OCA\Files_Sharing\ShareBackend\Folder. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1038
					/** @scrutinizer ignore-call */ 
1039
     $sharedParents = $collectionBackend->getParents($row['item_source']);
Loading history...
1039
					foreach ($sharedParents as $parent) {
1040
						$collectionItems[] = $parent;
1041
					}
1042
				}
1043
			}
1044
			if (!empty($collectionItems)) {
1045
				$collectionItems = array_unique($collectionItems, SORT_REGULAR);
1046
				$items = array_merge($items, $collectionItems);
1047
			}
1048
1049
			// filter out invalid items, these can appear when subshare entries exist
1050
			// for a group in which the requested user isn't a member any more
1051
			$items = array_filter($items, function($item) {
1052
				return $item['share_type'] !== self::$shareTypeGroupUserUnique;
1053
			});
1054
1055
			return self::formatResult($items, $column, $backend, $format, $parameters);
1056
		} elseif ($includeCollections && $collectionTypes && in_array('folder', $collectionTypes)) {
1057
			// FIXME: Thats a dirty hack to improve file sharing performance,
1058
			// see github issue #10588 for more details
1059
			// Need to find a solution which works for all back-ends
1060
			$collectionItems = array();
1061
			$collectionBackend = self::getBackend('folder');
1062
			$sharedParents = $collectionBackend->getParents($item, $shareWith, $uidOwner);
1063
			foreach ($sharedParents as $parent) {
1064
				$collectionItems[] = $parent;
1065
			}
1066
			if ($limit === 1) {
1067
				return reset($collectionItems);
1068
			}
1069
			return self::formatResult($collectionItems, $column, $backend, $format, $parameters);
1070
		}
1071
1072
		return array();
1073
	}
1074
1075
	/**
1076
	 * group items with link to the same source
1077
	 *
1078
	 * @param array $items
1079
	 * @param string $itemType
1080
	 * @return array of grouped items
1081
	 */
1082
	protected static function groupItems($items, $itemType) {
1083
1084
		$fileSharing = $itemType === 'file' || $itemType === 'folder';
1085
1086
		$result = array();
1087
1088
		foreach ($items as $item) {
1089
			$grouped = false;
1090
			foreach ($result as $key => $r) {
1091
				// for file/folder shares we need to compare file_source, otherwise we compare item_source
1092
				// only group shares if they already point to the same target, otherwise the file where shared
1093
				// before grouping of shares was added. In this case we don't group them toi avoid confusions
1094
				if (( $fileSharing && $item['file_source'] === $r['file_source'] && $item['file_target'] === $r['file_target']) ||
1095
					(!$fileSharing && $item['item_source'] === $r['item_source'] && $item['item_target'] === $r['item_target'])) {
1096
					// add the first item to the list of grouped shares
1097
					if (!isset($result[$key]['grouped'])) {
1098
						$result[$key]['grouped'][] = $result[$key];
1099
					}
1100
					$result[$key]['permissions'] = (int) $item['permissions'] | (int) $r['permissions'];
1101
					$result[$key]['grouped'][] = $item;
1102
					$grouped = true;
1103
					break;
1104
				}
1105
			}
1106
1107
			if (!$grouped) {
1108
				$result[] = $item;
1109
			}
1110
1111
		}
1112
1113
		return $result;
1114
	}
1115
1116
	/**
1117
	 * Put shared item into the database
1118
	 * @param string $itemType Item type
1119
	 * @param string $itemSource Item source
1120
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
1121
	 * @param string $shareWith User or group the item is being shared with
1122
	 * @param string $uidOwner User that is the owner of shared item
1123
	 * @param int $permissions CRUDS permissions
1124
	 * @throws \Exception
1125
	 * @return mixed id of the new share or false
1126
	 * @deprecated TESTS ONLY - this methods is only used by tests
1127
	 * called like this:
1128
	 * self::put('test', $itemSource, self::SHARE_TYPE_USER, $shareWith, $uidOwner, $permissions);
1129
	 */
1130
	private static function put($itemType, $itemSource, $shareType, $shareWith, $uidOwner,
1131
								$permissions) {
1132
1133
		$queriesToExecute = array();
1134
		$suggestedItemTarget = null;
1135
		$groupFileTarget = $fileTarget = $suggestedFileTarget = $filePath = '';
1136
		$groupItemTarget = $itemTarget = $fileSource = $parent = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $itemTarget is dead and can be removed.
Loading history...
1137
1138
		$result = self::checkReshare('test', $itemSource, self::SHARE_TYPE_USER, $shareWith, $uidOwner, $permissions, null, null);
0 ignored issues
show
Deprecated Code introduced by
The function OC\Share\Share::checkReshare() has been deprecated: TESTS ONLY - this methods is only used by tests called like this: self::checkReshare('test', $itemSource, self::SHARE_TYPE_USER, $shareWith, $uidOwner, $permissions, null, null); ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1138
		$result = /** @scrutinizer ignore-deprecated */ self::checkReshare('test', $itemSource, self::SHARE_TYPE_USER, $shareWith, $uidOwner, $permissions, null, null);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1139
		if(!empty($result)) {
1140
			$parent = $result['parent'];
1141
			$itemSource = $result['itemSource'];
1142
			$fileSource = $result['fileSource'];
1143
			$suggestedItemTarget = $result['suggestedItemTarget'];
1144
			$suggestedFileTarget = $result['suggestedFileTarget'];
1145
			$filePath = $result['filePath'];
1146
		}
1147
1148
		$isGroupShare = false;
1149
			$users = array($shareWith);
1150
			$itemTarget = Helper::generateTarget($itemType, $itemSource, $shareType, $shareWith, $uidOwner,
1151
				$suggestedItemTarget);
1152
1153
		$run = true;
1154
		$error = '';
1155
		$preHookData = array(
1156
			'itemType' => $itemType,
1157
			'itemSource' => $itemSource,
1158
			'shareType' => $shareType,
1159
			'uidOwner' => $uidOwner,
1160
			'permissions' => $permissions,
1161
			'fileSource' => $fileSource,
1162
			'expiration' => null,
1163
			'token' => null,
1164
			'run' => &$run,
1165
			'error' => &$error
1166
		);
1167
1168
		$preHookData['itemTarget'] = $itemTarget;
1169
		$preHookData['shareWith'] = $shareWith;
1170
1171
		\OC_Hook::emit(\OCP\Share::class, 'pre_shared', $preHookData);
1172
1173
		if ($run === false) {
0 ignored issues
show
introduced by
The condition $run === false is always false.
Loading history...
1174
			throw new \Exception($error);
1175
		}
1176
1177
		foreach ($users as $user) {
1178
			$sourceId = ($itemType === 'file' || $itemType === 'folder') ? $fileSource : $itemSource;
1179
			$sourceExists = self::getItemSharedWithBySource($itemType, $sourceId, self::FORMAT_NONE, null, true, $user);
1180
1181
			$userShareType = $shareType;
1182
1183
			if ($sourceExists && $sourceExists['item_source'] === $itemSource) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $sourceExists of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1184
				$fileTarget = $sourceExists['file_target'];
1185
				$itemTarget = $sourceExists['item_target'];
1186
1187
			} elseif(!$sourceExists)  {
0 ignored issues
show
Bug Best Practice introduced by
The expression $sourceExists of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1188
1189
				$itemTarget = Helper::generateTarget($itemType, $itemSource, $userShareType, $user,
1190
					$uidOwner, $suggestedItemTarget, $parent);
1191
				if (isset($fileSource)) {
1192
						$fileTarget = Helper::generateTarget('file', $filePath, $userShareType,
1193
							$user, $uidOwner, $suggestedFileTarget, $parent);
1194
				} else {
1195
					$fileTarget = null;
1196
				}
1197
1198
			} else {
1199
1200
				// group share which doesn't exists until now, check if we need a unique target for this user
1201
1202
				$itemTarget = Helper::generateTarget($itemType, $itemSource, self::SHARE_TYPE_USER, $user,
1203
					$uidOwner, $suggestedItemTarget, $parent);
1204
1205
				// do we also need a file target
1206
				if (isset($fileSource)) {
1207
					$fileTarget = Helper::generateTarget('file', $filePath, self::SHARE_TYPE_USER, $user,
1208
						$uidOwner, $suggestedFileTarget, $parent);
1209
				} else {
1210
					$fileTarget = null;
1211
				}
1212
1213
				if (($itemTarget === $groupItemTarget) &&
1214
					(!isset($fileSource) || $fileTarget === $groupFileTarget)) {
1215
					continue;
1216
				}
1217
			}
1218
1219
			$queriesToExecute[] = array(
1220
				'itemType'			=> $itemType,
1221
				'itemSource'		=> $itemSource,
1222
				'itemTarget'		=> $itemTarget,
1223
				'shareType'			=> $userShareType,
1224
				'shareWith'			=> $user,
1225
				'uidOwner'			=> $uidOwner,
1226
				'permissions'		=> $permissions,
1227
				'shareTime'			=> time(),
1228
				'fileSource'		=> $fileSource,
1229
				'fileTarget'		=> $fileTarget,
1230
				'token'				=> null,
1231
				'parent'			=> $parent,
1232
				'expiration'		=> null,
1233
			);
1234
1235
		}
1236
1237
		$id = false;
1238
1239
		foreach ($queriesToExecute as $shareQuery) {
1240
			$shareQuery['parent'] = $parent;
1241
			$id = self::insertShare($shareQuery);
1242
		}
1243
1244
		$postHookData = array(
1245
			'itemType' => $itemType,
1246
			'itemSource' => $itemSource,
1247
			'parent' => $parent,
1248
			'shareType' => $shareType,
1249
			'uidOwner' => $uidOwner,
1250
			'permissions' => $permissions,
1251
			'fileSource' => $fileSource,
1252
			'id' => $parent,
1253
			'token' => null,
1254
			'expirationDate' => null,
1255
		);
1256
1257
		$postHookData['shareWith'] = $isGroupShare ? $shareWith['group'] : $shareWith;
0 ignored issues
show
introduced by
The condition $isGroupShare is always false.
Loading history...
1258
		$postHookData['itemTarget'] = $isGroupShare ? $groupItemTarget : $itemTarget;
0 ignored issues
show
introduced by
The condition $isGroupShare is always false.
Loading history...
1259
		$postHookData['fileTarget'] = $isGroupShare ? $groupFileTarget : $fileTarget;
0 ignored issues
show
introduced by
The condition $isGroupShare is always false.
Loading history...
1260
1261
		\OC_Hook::emit(\OCP\Share::class, 'post_shared', $postHookData);
1262
1263
1264
		return $id ? $id : false;
1265
	}
1266
1267
	/**
1268
	 * @param string $itemType
1269
	 * @param string $itemSource
1270
	 * @param int $shareType
1271
	 * @param string $shareWith
1272
	 * @param string $uidOwner
1273
	 * @param int $permissions
1274
	 * @param string|null $itemSourceName
1275
	 * @param null|\DateTime $expirationDate
1276
	 * @deprecated TESTS ONLY - this methods is only used by tests
1277
	 * called like this:
1278
	 * self::checkReshare('test', $itemSource, self::SHARE_TYPE_USER, $shareWith, $uidOwner, $permissions, null, null);
1279
	 */
1280
	private static function checkReshare($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, $itemSourceName, $expirationDate) {
0 ignored issues
show
Unused Code introduced by
The parameter $shareType is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1280
	private static function checkReshare($itemType, $itemSource, /** @scrutinizer ignore-unused */ $shareType, $shareWith, $uidOwner, $permissions, $itemSourceName, $expirationDate) {

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

Loading history...
1281
		$backend = self::getBackend($itemType);
1282
1283
		$result = array();
1284
1285
		$column = ($itemType === 'file' || $itemType === 'folder') ? 'file_source' : 'item_source';
1286
1287
		$checkReshare = self::getItemSharedWithBySource($itemType, $itemSource, self::FORMAT_NONE, null, true);
1288
		if ($checkReshare) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $checkReshare of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1289
			// Check if attempting to share back to owner
1290
			if ($checkReshare['uid_owner'] == $shareWith) {
1291
				$message = 'Sharing %1$s failed, because the user %2$s is the original sharer';
1292
				throw new \Exception(sprintf($message, $itemSourceName, $shareWith));
1293
			}
1294
		}
1295
1296
		if ($checkReshare && $checkReshare['uid_owner'] !== \OC_User::getUser()) {
1297
			// Check if share permissions is granted
1298
			if (self::isResharingAllowed() && (int)$checkReshare['permissions'] & \OCP\Constants::PERMISSION_SHARE) {
1299
				if (~(int)$checkReshare['permissions'] & $permissions) {
1300
					$message = 'Sharing %1$s failed, because the permissions exceed permissions granted to %2$s';
1301
					throw new \Exception(sprintf($message, $itemSourceName, $uidOwner));
1302
				} else {
1303
					// TODO Don't check if inside folder
1304
					$result['parent'] = $checkReshare['id'];
1305
1306
					$result['expirationDate'] = $expirationDate;
1307
					// $checkReshare['expiration'] could be null and then is always less than any value
1308
					if(isset($checkReshare['expiration']) && $checkReshare['expiration'] < $expirationDate) {
1309
						$result['expirationDate'] = $checkReshare['expiration'];
1310
					}
1311
1312
					// only suggest the same name as new target if it is a reshare of the
1313
					// same file/folder and not the reshare of a child
1314
					if ($checkReshare[$column] === $itemSource) {
1315
						$result['filePath'] = $checkReshare['file_target'];
1316
						$result['itemSource'] = $checkReshare['item_source'];
1317
						$result['fileSource'] = $checkReshare['file_source'];
1318
						$result['suggestedItemTarget'] = $checkReshare['item_target'];
1319
						$result['suggestedFileTarget'] = $checkReshare['file_target'];
1320
					} else {
1321
						$result['filePath'] = ($backend instanceof \OCP\Share_Backend_File_Dependent) ? $backend->getFilePath($itemSource, $uidOwner) : null;
1322
						$result['suggestedItemTarget'] = null;
1323
						$result['suggestedFileTarget'] = null;
1324
						$result['itemSource'] = $itemSource;
1325
						$result['fileSource'] = ($backend instanceof \OCP\Share_Backend_File_Dependent) ? $itemSource : null;
1326
					}
1327
				}
1328
			} else {
1329
				$message = 'Sharing %s failed, because resharing is not allowed';
1330
				throw new \Exception(sprintf($message, $itemSourceName));
1331
			}
1332
		} else {
1333
			$result['parent'] = null;
1334
			$result['suggestedItemTarget'] = null;
1335
			$result['suggestedFileTarget'] = null;
1336
			$result['itemSource'] = $itemSource;
1337
			$result['expirationDate'] = $expirationDate;
1338
			if (!$backend->isValidSource($itemSource, $uidOwner)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $backend->isValidSource($itemSource, $uidOwner) of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
1339
				$message = 'Sharing %1$s failed, because the sharing backend for '
1340
					.'%2$s could not find its source';
1341
				throw new \Exception(sprintf($message, $itemSource, $itemType));
1342
			}
1343
			if ($backend instanceof \OCP\Share_Backend_File_Dependent) {
1344
				$result['filePath'] = $backend->getFilePath($itemSource, $uidOwner);
1345
					$meta = \OC\Files\Filesystem::getFileInfo($result['filePath']);
1346
					$result['fileSource'] = $meta['fileid'];
1347
				if ($result['fileSource'] == -1) {
1348
					$message = 'Sharing %s failed, because the file could not be found in the file cache';
1349
					throw new \Exception(sprintf($message, $itemSource));
1350
				}
1351
			} else {
1352
				$result['filePath'] = null;
1353
				$result['fileSource'] = null;
1354
			}
1355
		}
1356
1357
		return $result;
1358
	}
1359
1360
	/**
1361
	 *
1362
	 * @param array $shareData
1363
	 * @return mixed false in case of a failure or the id of the new share
1364
	 */
1365
	private static function insertShare(array $shareData) {
1366
1367
		$query = \OC_DB::prepare('INSERT INTO `*PREFIX*share` ('
1368
			.' `item_type`, `item_source`, `item_target`, `share_type`,'
1369
			.' `share_with`, `uid_owner`, `permissions`, `stime`, `file_source`,'
1370
			.' `file_target`, `token`, `parent`, `expiration`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)');
1371
		$query->bindValue(1, $shareData['itemType']);
1372
		$query->bindValue(2, $shareData['itemSource']);
1373
		$query->bindValue(3, $shareData['itemTarget']);
1374
		$query->bindValue(4, $shareData['shareType']);
1375
		$query->bindValue(5, $shareData['shareWith']);
1376
		$query->bindValue(6, $shareData['uidOwner']);
1377
		$query->bindValue(7, $shareData['permissions']);
1378
		$query->bindValue(8, $shareData['shareTime']);
1379
		$query->bindValue(9, $shareData['fileSource']);
1380
		$query->bindValue(10, $shareData['fileTarget']);
1381
		$query->bindValue(11, $shareData['token']);
1382
		$query->bindValue(12, $shareData['parent']);
1383
		$query->bindValue(13, $shareData['expiration'], 'datetime');
0 ignored issues
show
Bug introduced by
'datetime' of type string is incompatible with the type integer expected by parameter $type of OC_DB_StatementWrapper::bindValue(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1383
		$query->bindValue(13, $shareData['expiration'], /** @scrutinizer ignore-type */ 'datetime');
Loading history...
1384
		$result = $query->execute();
1385
1386
		$id = false;
1387
		if ($result) {
1388
			$id =  \OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*share');
1389
		}
1390
1391
		return $id;
1392
1393
	}
1394
1395
	/**
1396
	 * In case a password protected link is not yet authenticated this function will return false
1397
	 *
1398
	 * @param array $linkItem
1399
	 * @return boolean
1400
	 */
1401
	public static function checkPasswordProtectedShare(array $linkItem) {
1402
		if (!isset($linkItem['share_with'])) {
1403
			return true;
1404
		}
1405
		if (!isset($linkItem['share_type'])) {
1406
			return true;
1407
		}
1408
		if (!isset($linkItem['id'])) {
1409
			return true;
1410
		}
1411
1412
		if ($linkItem['share_type'] != \OCP\Share::SHARE_TYPE_LINK) {
1413
			return true;
1414
		}
1415
1416
		if ( \OC::$server->getSession()->exists('public_link_authenticated')
1417
			&& \OC::$server->getSession()->get('public_link_authenticated') === (string)$linkItem['id'] ) {
1418
			return true;
1419
		}
1420
1421
		return false;
1422
	}
1423
1424
	/**
1425
	 * construct select statement
1426
	 * @param int $format
1427
	 * @param boolean $fileDependent ist it a file/folder share or a generla share
1428
	 * @param string $uidOwner
1429
	 * @return string select statement
1430
	 */
1431
	private static function createSelectStatement($format, $fileDependent, $uidOwner = null) {
1432
		$select = '*';
1433
		if ($format == self::FORMAT_STATUSES) {
1434
			if ($fileDependent) {
1435
				$select = '`*PREFIX*share`.`id`, `*PREFIX*share`.`parent`, `share_type`, `path`, `storage`, '
1436
					. '`share_with`, `uid_owner` , `file_source`, `stime`, `*PREFIX*share`.`permissions`, '
1437
					. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`, '
1438
					. '`uid_initiator`';
1439
			} else {
1440
				$select = '`id`, `parent`, `share_type`, `share_with`, `uid_owner`, `item_source`, `stime`, `*PREFIX*share`.`permissions`';
1441
			}
1442
		} else {
1443
			if (isset($uidOwner)) {
1444
				if ($fileDependent) {
1445
					$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`,'
1446
						. ' `share_type`, `share_with`, `file_source`, `file_target`, `path`, `*PREFIX*share`.`permissions`, `stime`,'
1447
						. ' `expiration`, `token`, `storage`, `mail_send`, `uid_owner`, '
1448
						. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
1449
				} else {
1450
					$select = '`id`, `item_type`, `item_source`, `parent`, `share_type`, `share_with`, `*PREFIX*share`.`permissions`,'
1451
						. ' `stime`, `file_source`, `expiration`, `token`, `mail_send`, `uid_owner`';
1452
				}
1453
			} else {
1454
				if ($fileDependent) {
1455
					if ($format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_GET_FOLDER_CONTENTS || $format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_FILE_APP_ROOT) {
1456
						$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`, `uid_owner`, '
1457
							. '`share_type`, `share_with`, `file_source`, `path`, `file_target`, `stime`, '
1458
							. '`*PREFIX*share`.`permissions`, `expiration`, `storage`, `*PREFIX*filecache`.`parent` as `file_parent`, '
1459
							. '`name`, `mtime`, `mimetype`, `mimepart`, `size`, `encrypted`, `etag`, `mail_send`';
1460
					} else {
1461
						$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `item_target`,'
1462
							. '`*PREFIX*share`.`parent`, `share_type`, `share_with`, `uid_owner`,'
1463
							. '`file_source`, `path`, `file_target`, `*PREFIX*share`.`permissions`,'
1464
						    . '`stime`, `expiration`, `token`, `storage`, `mail_send`,'
1465
							. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
1466
					}
1467
				}
1468
			}
1469
		}
1470
		return $select;
1471
	}
1472
1473
1474
	/**
1475
	 * transform db results
1476
	 * @param array $row result
1477
	 */
1478
	private static function transformDBResults(&$row) {
1479
		if (isset($row['id'])) {
1480
			$row['id'] = (int) $row['id'];
1481
		}
1482
		if (isset($row['share_type'])) {
1483
			$row['share_type'] = (int) $row['share_type'];
1484
		}
1485
		if (isset($row['parent'])) {
1486
			$row['parent'] = (int) $row['parent'];
1487
		}
1488
		if (isset($row['file_parent'])) {
1489
			$row['file_parent'] = (int) $row['file_parent'];
1490
		}
1491
		if (isset($row['file_source'])) {
1492
			$row['file_source'] = (int) $row['file_source'];
1493
		}
1494
		if (isset($row['permissions'])) {
1495
			$row['permissions'] = (int) $row['permissions'];
1496
		}
1497
		if (isset($row['storage'])) {
1498
			$row['storage'] = (int) $row['storage'];
1499
		}
1500
		if (isset($row['stime'])) {
1501
			$row['stime'] = (int) $row['stime'];
1502
		}
1503
		if (isset($row['expiration']) && $row['share_type'] !== self::SHARE_TYPE_LINK) {
1504
			// discard expiration date for non-link shares, which might have been
1505
			// set by ancient bugs
1506
			$row['expiration'] = null;
1507
		}
1508
	}
1509
1510
	/**
1511
	 * format result
1512
	 * @param array $items result
1513
	 * @param string $column is it a file share or a general share ('file_target' or 'item_target')
1514
	 * @param \OCP\Share_Backend $backend sharing backend
1515
	 * @param int $format
1516
	 * @param array $parameters additional format parameters
1517
	 * @return array format result
1518
	 */
1519
	private static function formatResult($items, $column, $backend, $format = self::FORMAT_NONE , $parameters = null) {
1520
		if ($format === self::FORMAT_NONE) {
1521
			return $items;
1522
		} else if ($format === self::FORMAT_STATUSES) {
1523
			$statuses = array();
1524
			foreach ($items as $item) {
1525
				if ($item['share_type'] === self::SHARE_TYPE_LINK) {
1526
					if ($item['uid_initiator'] !== \OC::$server->getUserSession()->getUser()->getUID()) {
1527
						continue;
1528
					}
1529
					$statuses[$item[$column]]['link'] = true;
1530
				} else if (!isset($statuses[$item[$column]])) {
1531
					$statuses[$item[$column]]['link'] = false;
1532
				}
1533
				if (!empty($item['file_target'])) {
1534
					$statuses[$item[$column]]['path'] = $item['path'];
1535
				}
1536
			}
1537
			return $statuses;
1538
		} else {
1539
			return $backend->formatItems($items, $format, $parameters);
1540
		}
1541
	}
1542
1543
	/**
1544
	 * remove protocol from URL
1545
	 *
1546
	 * @param string $url
1547
	 * @return string
1548
	 */
1549
	public static function removeProtocolFromUrl($url) {
1550
		if (strpos($url, 'https://') === 0) {
1551
			return substr($url, strlen('https://'));
1552
		} else if (strpos($url, 'http://') === 0) {
1553
			return substr($url, strlen('http://'));
1554
		}
1555
1556
		return $url;
1557
	}
1558
1559
	/**
1560
	 * try http post first with https and then with http as a fallback
1561
	 *
1562
	 * @param string $remoteDomain
1563
	 * @param string $urlSuffix
1564
	 * @param array $fields post parameters
1565
	 * @return array
1566
	 */
1567
	private static function tryHttpPostToShareEndpoint($remoteDomain, $urlSuffix, array $fields) {
1568
		$protocol = 'https://';
1569
		$result = [
1570
			'success' => false,
1571
			'result' => '',
1572
		];
1573
		$try = 0;
1574
		$discoveryService = \OC::$server->query(\OCP\OCS\IDiscoveryService::class);
1575
		while ($result['success'] === false && $try < 2) {
1576
			$federationEndpoints = $discoveryService->discover($protocol . $remoteDomain, 'FEDERATED_SHARING');
1577
			$endpoint = isset($federationEndpoints['share']) ? $federationEndpoints['share'] : '/ocs/v2.php/cloud/shares';
1578
			$client = \OC::$server->getHTTPClientService()->newClient();
1579
1580
			try {
1581
				$response = $client->post(
1582
					$protocol . $remoteDomain . $endpoint . $urlSuffix . '?format=' . self::RESPONSE_FORMAT,
1583
					[
1584
						'body' => $fields,
1585
						'connect_timeout' => 10,
1586
					]
1587
				);
1588
1589
				$result = ['success' => true, 'result' => $response->getBody()];
1590
			} catch (\Exception $e) {
1591
				$result = ['success' => false, 'result' => $e->getMessage()];
1592
			}
1593
1594
			$try++;
1595
			$protocol = 'http://';
1596
		}
1597
1598
		return $result;
1599
	}
1600
1601
	/**
1602
	 * send server-to-server unshare to remote server
1603
	 *
1604
	 * @param string $remote url
1605
	 * @param int $id share id
1606
	 * @param string $token
1607
	 * @return bool
1608
	 */
1609
	private static function sendRemoteUnshare($remote, $id, $token) {
1610
		$url = rtrim($remote, '/');
1611
		$fields = array('token' => $token, 'format' => 'json');
1612
		$url = self::removeProtocolFromUrl($url);
1613
		$result = self::tryHttpPostToShareEndpoint($url, '/'.$id.'/unshare', $fields);
1614
		$status = json_decode($result['result'], true);
1615
1616
		return ($result['success'] && ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200));
1617
	}
1618
1619
	/**
1620
	 * @return bool
1621
	 */
1622
	public static function isDefaultExpireDateEnabled() {
1623
		$defaultExpireDateEnabled = \OC::$server->getConfig()->getAppValue('core', 'shareapi_default_expire_date', 'no');
1624
		return $defaultExpireDateEnabled === 'yes';
1625
	}
1626
1627
	/**
1628
	 * @return int
1629
	 */
1630
	public static function getExpireInterval() {
1631
		return (int)\OC::$server->getConfig()->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1632
	}
1633
1634
	/**
1635
	 * Checks whether the given path is reachable for the given owner
1636
	 *
1637
	 * @param string $path path relative to files
1638
	 * @param string $ownerStorageId storage id of the owner
1639
	 *
1640
	 * @return boolean true if file is reachable, false otherwise
1641
	 */
1642
	private static function isFileReachable($path, $ownerStorageId) {
1643
		// if outside the home storage, file is always considered reachable
1644
		if (!(substr($ownerStorageId, 0, 6) === 'home::' ||
1645
			substr($ownerStorageId, 0, 13) === 'object::user:'
1646
		)) {
1647
			return true;
1648
		}
1649
1650
		// if inside the home storage, the file has to be under "/files/"
1651
		$path = ltrim($path, '/');
1652
		if (substr($path, 0, 6) === 'files/') {
1653
			return true;
1654
		}
1655
1656
		return false;
1657
	}
1658
}
1659