Completed
Push — stable8.2 ( aa408d...2d5902 )
by
unknown
17:45
created

Shared::isLocal()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 6
ccs 0
cts 0
cp 0
rs 9.4285
cc 1
eloc 5
nc 1
nop 0
crap 2
1
<?php
2
/**
3
 * @author Bart Visscher <[email protected]>
4
 * @author Björn Schießle <[email protected]>
5
 * @author Joas Schilling <[email protected]>
6
 * @author Michael Gapczynski <[email protected]>
7
 * @author Morris Jobke <[email protected]>
8
 * @author Robin Appelman <[email protected]>
9
 * @author Robin McCorkell <[email protected]>
10
 * @author Roeland Jago Douma <[email protected]>
11
 * @author scambra <[email protected]>
12
 * @author Vincent Petry <[email protected]>
13
 *
14
 * @copyright Copyright (c) 2015, ownCloud, Inc.
15
 * @license AGPL-3.0
16
 *
17
 * This code is free software: you can redistribute it and/or modify
18
 * it under the terms of the GNU Affero General Public License, version 3,
19
 * as published by the Free Software Foundation.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License, version 3,
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
28
 *
29
 */
30
31
namespace OC\Files\Storage;
32
33
use OC\Files\Filesystem;
34
use OCA\Files_Sharing\ISharedStorage;
35
use OCA\Files_Sharing\Propagator;
36
use OCA\Files_Sharing\SharedMount;
37
use OCP\Lock\ILockingProvider;
38
39
/**
40
 * Convert target path to source path and pass the function call to the correct storage provider
41
 */
42
class Shared extends \OC\Files\Storage\Common implements ISharedStorage {
43
44
	private $share;   // the shared resource
45
	private $files = array();
46
	private static $isInitialized = array();
47
48
	/**
49
	 * @var \OC\Files\View
50
	 */
51
	private $ownerView;
52
53
	/**
54
	 * @var \OCA\Files_Sharing\Propagation\PropagationManager
55
	 */
56
	private $propagationManager;
57
58
	/**
59
	 * @var string
60
	 */
61
	private $user;
62
63
	private $initialized = false;
64
65 95
	public function __construct($arguments) {
66 95
		$this->share = $arguments['share'];
67 95
		$this->ownerView = $arguments['ownerView'];
68 95
		$this->propagationManager = $arguments['propagationManager'];
69 95
		$this->user = $arguments['user'];
70 95
	}
71
72 79
	private function init() {
73 79
		if ($this->initialized) {
74 69
			return;
75
		}
76 79
		$this->initialized = true;
77 79
		Filesystem::initMountPoints($this->share['uid_owner']);
78
79
		// for updating our etags when changes are made to the share from the owners side (probably indirectly by us trough another share)
80 79
		$this->propagationManager->listenToOwnerChanges($this->share['uid_owner'], $this->user);
81 79
	}
82
83
	/**
84
	 * get id of the mount point
85
	 *
86
	 * @return string
87
	 */
88 82
	public function getId() {
89 82
		return 'shared::' . $this->getMountPoint();
90
	}
91
92
	/**
93
	 * get file cache of the shared item source
94
	 *
95
	 * @return int
96
	 */
97 44
	public function getSourceId() {
98 44
		return (int)$this->share['file_source'];
99
	}
100
101
	/**
102
	 * Get the source file path, permissions, and owner for a shared file
103
	 *
104
	 * @param string $target Shared target file path
105
	 * @return array Returns array with the keys path, permissions, and owner or false if not found
106
	 */
107 79
	public function getFile($target) {
108 79
		$this->init();
109 79
		if (!isset($this->files[$target])) {
110
			// Check for partial files
111 79
			if (pathinfo($target, PATHINFO_EXTENSION) === 'part') {
112 5
				$source = \OC_Share_Backend_File::getSource(substr($target, 0, -5), $this->getShare());
113 5
				if ($source) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $source 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...
114 5
					$source['path'] .= '.part';
115
					// All partial files have delete permission
116 5
					$source['permissions'] |= \OCP\Constants::PERMISSION_DELETE;
117 5
				}
118 5
			} else {
119 79
				$source = \OC_Share_Backend_File::getSource($target, $this->getShare());
120
			}
121 79
			$this->files[$target] = $source;
122 79
		}
123 79
		return $this->files[$target];
124
	}
125
126
	/**
127
	 * Get the source file path for a shared file
128
	 *
129
	 * @param string $target Shared target file path
130
	 * @return string|false source file path or false if not found
131
	 */
132 60
	public function getSourcePath($target) {
133 60
		$source = $this->getFile($target);
134 60
		if ($source) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $source 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...
135 60
			if (!isset($source['fullPath'])) {
136 60
				\OC\Files\Filesystem::initMountPoints($source['fileOwner']);
137 60
				$mount = \OC\Files\Filesystem::getMountByNumericId($source['storage']);
138 60
				if (is_array($mount) && !empty($mount)) {
139 60
					$this->files[$target]['fullPath'] = $mount[key($mount)]->getMountPoint() . $source['path'];
140 60
				} else {
141
					$this->files[$target]['fullPath'] = false;
142
					\OCP\Util::writeLog('files_sharing', "Unable to get mount for shared storage '" . $source['storage'] . "' user '" . $source['fileOwner'] . "'", \OCP\Util::ERROR);
143
				}
144 60
			}
145 60
			return $this->files[$target]['fullPath'];
146
		}
147
		return false;
148
	}
149
150
	/**
151
	 * Get the permissions granted for a shared file
152
	 *
153
	 * @param string $target Shared target file path
154
	 * @return int CRUDS permissions granted
155
	 */
156 74
	public function getPermissions($target = '') {
157 74
		$permissions = $this->share['permissions'];
158
		// part files and the mount point always have delete permissions
159 74
		if ($target === '' || pathinfo($target, PATHINFO_EXTENSION) === 'part') {
160 72
			$permissions |= \OCP\Constants::PERMISSION_DELETE;
161 72
		}
162
163 74
		if (\OCP\Util::isSharingDisabledForUser()) {
164
			$permissions &= ~\OCP\Constants::PERMISSION_SHARE;
165
		}
166
167 74
		return $permissions;
168
	}
169
170 1
	public function mkdir($path) {
171 1
		if ($path == '' || $path == '/' || !$this->isCreatable(dirname($path))) {
172
			return false;
173 1
		} else if ($source = $this->getSourcePath($path)) {
174 1
			list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
175 1
			return $storage->mkdir($internalPath);
176
		}
177
		return false;
178
	}
179
180
	/**
181
	 * Delete the directory if DELETE permission is granted
182
	 *
183
	 * @param string $path
184
	 * @return boolean
185
	 */
186 1 View Code Duplication
	public function rmdir($path) {
187
188
		// never delete a share mount point
189 1
		if (empty($path)) {
190
			return false;
191
		}
192
193 1
		if (($source = $this->getSourcePath($path)) && $this->isDeletable($path)) {
194 1
			list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
195 1
			return $storage->rmdir($internalPath);
196
		}
197
		return false;
198
	}
199
200 2
	public function opendir($path) {
201 2
		$source = $this->getSourcePath($path);
202 2
		list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
0 ignored issues
show
Security Bug introduced by
It seems like $source can also be of type false; however, OC\Files\Filesystem::resolvePath() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
203 2
		return $storage->opendir($internalPath);
204
	}
205
206 22
	public function is_dir($path) {
207 22
		$source = $this->getSourcePath($path);
208 22
		list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
0 ignored issues
show
Security Bug introduced by
It seems like $source can also be of type false; however, OC\Files\Filesystem::resolvePath() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
209 22
		return $storage->is_dir($internalPath);
210
	}
211
212 View Code Duplication
	public function is_file($path) {
213
		if ($source = $this->getSourcePath($path)) {
214
			list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
215
			return $storage->is_file($internalPath);
216
		}
217
		return false;
218
	}
219
220
	public function stat($path) {
221
		if ($path == '' || $path == '/') {
222
			$stat['size'] = $this->filesize($path);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$stat was never initialized. Although not strictly required by PHP, it is generally a good practice to add $stat = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
223
			$stat['mtime'] = $this->filemtime($path);
224
			return $stat;
225
		} else if ($source = $this->getSourcePath($path)) {
226
			list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
227
			return $storage->stat($internalPath);
228
		}
229
		return false;
230
	}
231
232 View Code Duplication
	public function filetype($path) {
233
		if ($path == '' || $path == '/') {
234
			return 'dir';
235
		} else if ($source = $this->getSourcePath($path)) {
236
			list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
237
			return $storage->filetype($internalPath);
238
		}
239
		return false;
240
	}
241
242 14
	public function filesize($path) {
243 14
		$source = $this->getSourcePath($path);
244 14
		list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
0 ignored issues
show
Security Bug introduced by
It seems like $source can also be of type false; however, OC\Files\Filesystem::resolvePath() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
245 14
		return $storage->filesize($internalPath);
246
	}
247
248 7
	public function isCreatable($path) {
249 7
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_CREATE);
250
	}
251
252
	public function isReadable($path) {
253
		$isReadable = false;
254
		if ($source = $this->getSourcePath($path)) {
255
			list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
256
			$isReadable = $storage->isReadable($internalPath);
257
		}
258
259
		return $isReadable && $this->file_exists($path);
260
	}
261
262 13
	public function isUpdatable($path) {
263 13
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_UPDATE);
264
	}
265
266 10
	public function isDeletable($path) {
267 10
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_DELETE);
268
	}
269
270 33
	public function isSharable($path) {
271 33
		if (\OCP\Util::isSharingDisabledForUser() || !\OC\Share\Share::isResharingAllowed()) {
272
			return false;
273
		}
274 33
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_SHARE);
275
	}
276
277 49 View Code Duplication
	public function file_exists($path) {
278 49
		if ($path == '' || $path == '/') {
279 21
			return true;
280 32
		} else if ($source = $this->getSourcePath($path)) {
281 32
			list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
282 32
			return $storage->file_exists($internalPath);
283
		}
284
		return false;
285
	}
286
287 49
	public function filemtime($path) {
288 49
		$source = $this->getSourcePath($path);
289 49
		list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
0 ignored issues
show
Security Bug introduced by
It seems like $source can also be of type false; however, OC\Files\Filesystem::resolvePath() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
290 49
		return $storage->filemtime($internalPath);
291
	}
292
293 2
	public function file_get_contents($path) {
294 2
		$source = $this->getSourcePath($path);
295 2
		if ($source) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $source of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
296
			$info = array(
297 2
				'target' => $this->getMountPoint() . $path,
298 2
				'source' => $source,
299 2
			);
300 2
			\OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info);
301 2
			list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
302 2
			return $storage->file_get_contents($internalPath);
303
		}
304
	}
305
306 12
	public function file_put_contents($path, $data) {
307 12
		if ($source = $this->getSourcePath($path)) {
308
			// Check if permission is granted
309 12
			if (($this->file_exists($path) && !$this->isUpdatable($path))
310 12
				|| ($this->is_dir($path) && !$this->isCreatable($path))
311 12
			) {
312
				return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type of the parent method OC\Files\Storage\Common::file_put_contents of type integer.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
313
			}
314
			$info = array(
315 12
				'target' => $this->getMountPoint() . '/' . $path,
316 12
				'source' => $source,
317 12
			);
318 12
			\OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info);
319 12
			list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
320 12
			$result = $storage->file_put_contents($internalPath, $data);
321 12
			return $result;
322
		}
323
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type of the parent method OC\Files\Storage\Common::file_put_contents of type integer.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
324
	}
325
326
	/**
327
	 * Delete the file if DELETE permission is granted
328
	 *
329
	 * @param string $path
330
	 * @return boolean
331
	 */
332 6 View Code Duplication
	public function unlink($path) {
333
334
		// never delete a share mount point
335 6
		if (empty($path)) {
336
			return false;
337
		}
338 6
		if ($source = $this->getSourcePath($path)) {
339 6
			if ($this->isDeletable($path)) {
340 6
				list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
341 6
				return $storage->unlink($internalPath);
342
			}
343
		}
344
		return false;
345
	}
346
347 9
	public function rename($path1, $path2) {
348 9
		$this->init();
349
		// we need the paths relative to data/user/files
350 9
		$relPath1 = $this->getMountPoint() . '/' . $path1;
351 9
		$relPath2 = $this->getMountPoint() . '/' . $path2;
352 9
		$pathinfo = pathinfo($relPath1);
353
354 9
		$isPartFile = (isset($pathinfo['extension']) && $pathinfo['extension'] === 'part');
355 9
		$targetExists = $this->file_exists($path2);
356 9
		$sameFolder = (dirname($relPath1) === dirname($relPath2));
357 9
		if ($targetExists || ($sameFolder && !$isPartFile)) {
358
			// note that renaming a share mount point is always allowed
359 7
			if (!$this->isUpdatable('')) {
360 3
				return false;
361
			}
362 4
		} else {
363 3
			if (!$this->isCreatable('')) {
364 1
				return false;
365
			}
366
		}
367
368
369
		/**
370
		 * @var \OC\Files\Storage\Storage $sourceStorage
371
		 */
372 6
		list($sourceStorage, $sourceInternalPath) = $this->resolvePath($path1);
373
		/**
374
		 * @var \OC\Files\Storage\Storage $targetStorage
375
		 */
376 6
		list($targetStorage, $targetInternalPath) = $this->resolvePath($path2);
377
378 6
		return $targetStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
379
	}
380
381
	public function copy($path1, $path2) {
382
		// Copy the file if CREATE permission is granted
383
		if ($this->isCreatable(dirname($path2))) {
384
			/**
385
			 * @var \OC\Files\Storage\Storage $sourceStorage
386
			 */
387
			list($sourceStorage, $sourceInternalPath) = $this->resolvePath($path1);
388
			/**
389
			 * @var \OC\Files\Storage\Storage $targetStorage
390
			 */
391
			list($targetStorage, $targetInternalPath) = $this->resolvePath($path2);
392
393
			return $targetStorage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
394
		}
395
		return false;
396
	}
397
398 4
	public function fopen($path, $mode) {
399 4
		if ($source = $this->getSourcePath($path)) {
400
			switch ($mode) {
401 4
				case 'r+':
402 4
				case 'rb+':
403 4
				case 'w+':
404 4
				case 'wb+':
405 4
				case 'x+':
406 4
				case 'xb+':
407 4
				case 'a+':
408 4
				case 'ab+':
409 4
				case 'w':
410 4
				case 'wb':
411 4
				case 'x':
412 4
				case 'xb':
413 4
				case 'a':
414 4
				case 'ab':
415 4
					$creatable = $this->isCreatable($path);
416 4
					$updatable = $this->isUpdatable($path);
417
					// if neither permissions given, no need to continue
418 4
					if (!$creatable && !$updatable) {
419 2
						return false;
420
					}
421
422 2
					$exists = $this->file_exists($path);
423
					// if a file exists, updatable permissions are required
424 2
					if ($exists && !$updatable) {
425 1
						return false;
426
					}
427
428
					// part file is allowed if !$creatable but the final file is $updatable
429 2
					if (pathinfo($path, PATHINFO_EXTENSION) !== 'part') {
430 2
						if (!$exists && !$creatable) {
431 1
							return false;
432
						}
433 2
					}
434 2
			}
435
			$info = array(
436 2
				'target' => $this->getMountPoint() . $path,
437 2
				'source' => $source,
438 2
				'mode' => $mode,
439 2
			);
440 2
			\OCP\Util::emitHook('\OC\Files\Storage\Shared', 'fopen', $info);
441 2
			list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
442 2
			return $storage->fopen($internalPath, $mode);
443
		}
444
		return false;
445
	}
446
447 13 View Code Duplication
	public function getMimeType($path) {
448 13
		if ($source = $this->getSourcePath($path)) {
449 13
			list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
450 13
			return $storage->getMimeType($internalPath);
451
		}
452
		return false;
453
	}
454
455
	public function free_space($path) {
456
		$source = $this->getSourcePath($path);
457
		if ($source) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $source of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
458
			list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
459
			return $storage->free_space($internalPath);
460
		}
461
		return \OCP\Files\FileInfo::SPACE_UNKNOWN;
462
	}
463
464 View Code Duplication
	public function getLocalFile($path) {
465
		if ($source = $this->getSourcePath($path)) {
466
			list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
467
			return $storage->getLocalFile($internalPath);
468
		}
469
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type of the parent method OC\Files\Storage\Common::getLocalFile of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
470
	}
471
472 View Code Duplication
	public function touch($path, $mtime = null) {
473
		if ($source = $this->getSourcePath($path)) {
474
			list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
475
			return $storage->touch($internalPath, $mtime);
476
		}
477
		return false;
478
	}
479
480
	/**
481
	 * return mount point of share, relative to data/user/files
482
	 *
483
	 * @return string
484
	 */
485 82
	public function getMountPoint() {
486 82
		return $this->share['file_target'];
487
	}
488
489 42
	public function setMountPoint($path) {
490 42
		$this->share['file_target'] = $path;
491 42
	}
492
493
	public function getShareType() {
494
		return $this->share['share_type'];
495
	}
496
497
	/**
498
	 * does the group share already has a user specific unique name
499
	 *
500
	 * @return bool
501
	 */
502
	public function uniqueNameSet() {
503
		return (isset($this->share['unique_name']) && $this->share['unique_name']);
504
	}
505
506
	/**
507
	 * the share now uses a unique name of this user
508
	 *
509
	 * @brief the share now uses a unique name of this user
510
	 */
511 42
	public function setUniqueName() {
512 42
		$this->share['unique_name'] = true;
513 42
	}
514
515
	/**
516
	 * get share ID
517
	 *
518
	 * @return integer unique share ID
519
	 */
520
	public function getShareId() {
521
		return $this->share['id'];
522
	}
523
524
	/**
525
	 * get the user who shared the file
526
	 *
527
	 * @return string
528
	 */
529 72
	public function getSharedFrom() {
530 72
		return $this->share['uid_owner'];
531
	}
532
533
	/**
534
	 * @return array
535
	 */
536 89
	public function getShare() {
537 89
		return $this->share;
538
	}
539
540
	/**
541
	 * return share type, can be "file" or "folder"
542
	 *
543
	 * @return string
544
	 */
545 5
	public function getItemType() {
546 5
		return $this->share['item_type'];
547
	}
548
549 49
	public function hasUpdated($path, $time) {
550 49
		return $this->filemtime($path) > $time;
551
	}
552
553 76
	public function getCache($path = '', $storage = null) {
554 76
		if (!$storage) {
555
			$storage = $this;
556
		}
557 76
		return new \OC\Files\Cache\Shared_Cache($storage);
558
	}
559
560 49
	public function getScanner($path = '', $storage = null) {
561 49
		if (!$storage) {
562
			$storage = $this;
563
		}
564 49
		return new \OC\Files\Cache\SharedScanner($storage);
565
	}
566
567 49
	public function getWatcher($path = '', $storage = null) {
568 49
		if (!$storage) {
569
			$storage = $this;
570
		}
571 49
		return new \OC\Files\Cache\Shared_Watcher($storage);
572
	}
573
574 72
	public function getOwner($path) {
575 72
		if ($path == '') {
576 69
			$path = $this->getMountPoint();
577 69
		}
578 72
		$source = $this->getFile($path);
579 72
		if ($source) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $source 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...
580 72
			return $source['fileOwner'];
581
		}
582
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type declared by the interface OC\Files\Storage\Storage::getOwner of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
583
	}
584
585 26 View Code Duplication
	public function getETag($path) {
586 26
		if ($source = $this->getSourcePath($path)) {
587 26
			list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
588 26
			return $storage->getETag($internalPath);
589
		}
590
		return null;
591
	}
592
593
	/**
594
	 * unshare complete storage, also the grouped shares
595
	 *
596
	 * @return bool
597
	 */
598 5
	public function unshareStorage() {
599 5
		$result = true;
600 5
		if (!empty($this->share['grouped'])) {
601 2
			foreach ($this->share['grouped'] as $share) {
602 2
				$result = $result && \OCP\Share::unshareFromSelf($share['item_type'], $share['file_target']);
603 2
			}
604 2
		}
605 5
		$result = $result && \OCP\Share::unshareFromSelf($this->getItemType(), $this->getMountPoint());
606
607 5
		return $result;
608
	}
609
610
	/**
611
	 * Resolve the path for the source of the share
612
	 *
613
	 * @param string $path
614
	 * @return array
615
	 */
616 33
	private function resolvePath($path) {
617 33
		$source = $this->getSourcePath($path);
618 33
		return \OC\Files\Filesystem::resolvePath($source);
0 ignored issues
show
Security Bug introduced by
It seems like $source defined by $this->getSourcePath($path) on line 617 can also be of type false; however, OC\Files\Filesystem::resolvePath() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
619
	}
620
621
	/**
622
	 * @param \OCP\Files\Storage $sourceStorage
623
	 * @param string $sourceInternalPath
624
	 * @param string $targetInternalPath
625
	 * @return bool
626
	 */
627 1
	public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
628
		/** @var \OCP\Files\Storage $targetStorage */
629 1
		list($targetStorage, $targetInternalPath) = $this->resolvePath($targetInternalPath);
630 1
		return $targetStorage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
631
	}
632
633
	/**
634
	 * @param \OCP\Files\Storage $sourceStorage
635
	 * @param string $sourceInternalPath
636
	 * @param string $targetInternalPath
637
	 * @return bool
638
	 */
639 3
	public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
640
		/** @var \OCP\Files\Storage $targetStorage */
641 3
		list($targetStorage, $targetInternalPath) = $this->resolvePath($targetInternalPath);
642 3
		return $targetStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
643
	}
644
645
	/**
646
	 * @param string $path
647
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
648
	 * @param \OCP\Lock\ILockingProvider $provider
649
	 * @throws \OCP\Lock\LockedException
650
	 */
651 31 View Code Duplication
	public function acquireLock($path, $type, ILockingProvider $provider) {
652
		/** @var \OCP\Files\Storage $targetStorage */
653 31
		list($targetStorage, $targetInternalPath) = $this->resolvePath($path);
654 31
		$targetStorage->acquireLock($targetInternalPath, $type, $provider);
655
		// lock the parent folders of the owner when locking the share as recipient
656 31
		if ($path === '') {
657 30
			$sourcePath = $this->ownerView->getPath($this->share['file_source']);
658 30
			$this->ownerView->lockFile(dirname($sourcePath), ILockingProvider::LOCK_SHARED, true);
659 30
		}
660 31
	}
661
662
	/**
663
	 * @param string $path
664
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
665
	 * @param \OCP\Lock\ILockingProvider $provider
666
	 */
667 30 View Code Duplication
	public function releaseLock($path, $type, ILockingProvider $provider) {
668
		/** @var \OCP\Files\Storage $targetStorage */
669 30
		list($targetStorage, $targetInternalPath) = $this->resolvePath($path);
670 30
		$targetStorage->releaseLock($targetInternalPath, $type, $provider);
671
		// unlock the parent folders of the owner when unlocking the share as recipient
672 30
		if ($path === '') {
673 29
			$sourcePath = $this->ownerView->getPath($this->share['file_source']);
674 29
			$this->ownerView->unlockFile(dirname($sourcePath), ILockingProvider::LOCK_SHARED, true);
675 29
		}
676 30
	}
677
678
	/**
679
	 * @param string $path
680
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
681
	 * @param \OCP\Lock\ILockingProvider $provider
682
	 */
683 27
	public function changeLock($path, $type, ILockingProvider $provider) {
684
		/** @var \OCP\Files\Storage $targetStorage */
685 27
		list($targetStorage, $targetInternalPath) = $this->resolvePath($path);
686 27
		$targetStorage->changeLock($targetInternalPath, $type, $provider);
687 27
	}
688
689
	/**
690
	 * @return array [ available, last_checked ]
691
	 */
692 86
	public function getAvailability() {
693
		// shares do not participate in availability logic
694
		return [
695 86
			'available' => true,
696
			'last_checked' => 0
697 86
		];
698
	}
699
700
	/**
701
	 * @param bool $available
702
	 */
703
	public function setAvailability($available) {
704
		// shares do not participate in availability logic
705
	}
706
707
	public function isLocal() {
708
		$this->init();
709
		$ownerPath = $this->ownerView->getPath($this->share['item_source']);
710
		list($targetStorage) = $this->ownerView->resolvePath($ownerPath);
711
		return $targetStorage->isLocal();
712
	}
713
}
714