Completed
Push — master ( b4df57...e6895c )
by Lukas
26s
created

Preview::deleteAllPreviews()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 4
eloc 13
c 2
b 0
f 0
nc 4
nop 0
dl 0
loc 24
rs 8.6845
1
<?php
2
/**
3
 * @author Björn Schießle <[email protected]>
4
 * @author Frank Karlitschek <[email protected]>
5
 * @author Georg Ehrke <[email protected]>
6
 * @author Joas Schilling <[email protected]>
7
 * @author Jörn Friedrich Dreyer <[email protected]>
8
 * @author Lukas Reschke <[email protected]>
9
 * @author Morris Jobke <[email protected]>
10
 * @author Olivier Paroz <[email protected]>
11
 * @author Robin Appelman <[email protected]>
12
 * @author Roeland Jago Douma <[email protected]>
13
 * @author Thomas Müller <[email protected]>
14
 * @author Tobias Kaminsky <[email protected]>
15
 *
16
 * @copyright Copyright (c) 2016, ownCloud, Inc.
17
 * @license AGPL-3.0
18
 *
19
 * This code is free software: you can redistribute it and/or modify
20
 * it under the terms of the GNU Affero General Public License, version 3,
21
 * as published by the Free Software Foundation.
22
 *
23
 * This program is distributed in the hope that it will be useful,
24
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
 * GNU Affero General Public License for more details.
27
 *
28
 * You should have received a copy of the GNU Affero General Public License, version 3,
29
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
30
 *
31
 */
32
namespace OC;
33
34
use OC\Preview\Provider;
35
use OCP\Files\FileInfo;
36
use OCP\Files\NotFoundException;
37
38
class Preview {
39
	//the thumbnail folder
40
	const THUMBNAILS_FOLDER = 'thumbnails';
41
42
	const MODE_FILL = 'fill';
43
	const MODE_COVER = 'cover';
44
45
	//config
46
	private $maxScaleFactor;
47
	/** @var int maximum width allowed for a preview */
48
	private $configMaxWidth;
49
	/** @var int maximum height allowed for a preview */
50
	private $configMaxHeight;
51
52
	//fileview object
53
	private $fileView = null;
54
	private $userView = null;
55
56
	//vars
57
	private $file;
58
	private $maxX;
59
	private $maxY;
60
	private $scalingUp;
61
	private $mimeType;
62
	private $keepAspect = false;
63
	private $mode = self::MODE_FILL;
64
65
	//used to calculate the size of the preview to generate
66
	/** @var int $maxPreviewWidth max width a preview can have */
67
	private $maxPreviewWidth;
68
	/** @var int $maxPreviewHeight max height a preview can have */
69
	private $maxPreviewHeight;
70
	/** @var int $previewWidth calculated width of the preview we're looking for */
71
	private $previewWidth;
72
	/** @var int $previewHeight calculated height of the preview we're looking for */
73
	private $previewHeight;
74
75
	//filemapper used for deleting previews
76
	// index is path, value is fileinfo
77
	static public $deleteFileMapper = array();
78
	static public $deleteChildrenMapper = array();
79
80
	/**
81
	 * preview images object
82
	 *
83
	 * @var \OCP\IImage
84
	 */
85
	private $preview;
86
87
	/**
88
	 * @var \OCP\Files\FileInfo
89
	 */
90
	protected $info;
91
92
	/**
93
	 * check if thumbnail or bigger version of thumbnail of file is cached
94
	 *
95
	 * @param string $user userid - if no user is given, OC_User::getUser will be used
96
	 * @param string $root path of root
97
	 * @param string $file The path to the file where you want a thumbnail from
98
	 * @param int $maxX The maximum X size of the thumbnail. It can be smaller depending on the
99
	 *     shape of the image
100
	 * @param int $maxY The maximum Y size of the thumbnail. It can be smaller depending on the
101
	 *     shape of the image
102
	 * @param bool $scalingUp Disable/Enable upscaling of previews
103
	 *
104
	 * @throws \Exception
105
	 * @return mixed (bool / string)
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
106
	 *                    false if thumbnail does not exist
107
	 *                    path to thumbnail if thumbnail exists
108
	 */
109
	public function __construct(
110
		$user = '',
111
		$root = '/',
112
		$file = '', $maxX = 1,
113
		$maxY = 1,
114
		$scalingUp = true
115
	) {
116
		//init fileviews
117
		if ($user === '') {
118
			$user = \OC_User::getUser();
119
		}
120
		$this->fileView = new \OC\Files\View('/' . $user . '/' . $root);
121
		$this->userView = new \OC\Files\View('/' . $user);
122
123
		//set config
124
		$sysConfig = \OC::$server->getConfig();
125
		$this->configMaxWidth = $sysConfig->getSystemValue('preview_max_x', 2048);
126
		$this->configMaxHeight = $sysConfig->getSystemValue('preview_max_y', 2048);
127
		$this->maxScaleFactor = $sysConfig->getSystemValue('preview_max_scale_factor', 2);
128
129
		//save parameters
130
		$this->setFile($file);
131
		$this->setMaxX((int)$maxX);
132
		$this->setMaxY((int)$maxY);
133
		$this->setScalingUp($scalingUp);
134
135
		$this->preview = null;
136
137
		//check if there are preview backends
138
		if (!\OC::$server->getPreviewManager()
139
				->hasProviders()
140
			&& \OC::$server->getConfig()
141
				->getSystemValue('enable_previews', true)
142
		) {
143
			\OCP\Util::writeLog('core', 'No preview providers exist', \OCP\Util::ERROR);
144
			throw new \Exception('No preview providers');
145
		}
146
	}
147
148
	/**
149
	 * returns the path of the file you want a thumbnail from
150
	 *
151
	 * @return string
152
	 */
153
	public function getFile() {
154
		return $this->file;
155
	}
156
157
	/**
158
	 * returns the max width of the preview
159
	 *
160
	 * @return integer
161
	 */
162
	public function getMaxX() {
163
		return $this->maxX;
164
	}
165
166
	/**
167
	 * returns the max height of the preview
168
	 *
169
	 * @return integer
170
	 */
171
	public function getMaxY() {
172
		return $this->maxY;
173
	}
174
175
	/**
176
	 * returns whether or not scalingup is enabled
177
	 *
178
	 * @return bool
179
	 */
180
	public function getScalingUp() {
181
		return $this->scalingUp;
182
	}
183
184
	/**
185
	 * returns the name of the thumbnailfolder
186
	 *
187
	 * @return string
188
	 */
189
	public function getThumbnailsFolder() {
190
		return self::THUMBNAILS_FOLDER;
191
	}
192
193
	/**
194
	 * returns the max scale factor
195
	 *
196
	 * @return string
197
	 */
198
	public function getMaxScaleFactor() {
199
		return $this->maxScaleFactor;
200
	}
201
202
	/**
203
	 * returns the max width set in ownCloud's config
204
	 *
205
	 * @return integer
206
	 */
207
	public function getConfigMaxX() {
208
		return $this->configMaxWidth;
209
	}
210
211
	/**
212
	 * returns the max height set in ownCloud's config
213
	 *
214
	 * @return integer
215
	 */
216
	public function getConfigMaxY() {
217
		return $this->configMaxHeight;
218
	}
219
220
	/**
221
	 * Returns the FileInfo object associated with the file to preview
222
	 *
223
	 * @return false|Files\FileInfo|\OCP\Files\FileInfo
224
	 */
225
	protected function getFileInfo() {
226
		$absPath = $this->fileView->getAbsolutePath($this->file);
227
		$absPath = Files\Filesystem::normalizePath($absPath);
228
		if (array_key_exists($absPath, self::$deleteFileMapper)) {
229
			$this->info = self::$deleteFileMapper[$absPath];
230
		} else if (!$this->info) {
231
			$this->info = $this->fileView->getFileInfo($this->file);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->fileView->getFileInfo($this->file) can also be of type false. However, the property $info is declared as type object<OCP\Files\FileInfo>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
232
		}
233
234
		return $this->info;
235
	}
236
237
238
	/**
239
	 * @return array|null
240
	 */
241
	private function getChildren() {
242
		$absPath = $this->fileView->getAbsolutePath($this->file);
243
		$absPath = Files\Filesystem::normalizePath($absPath);
244
245
		if (array_key_exists($absPath, self::$deleteChildrenMapper)) {
246
			return self::$deleteChildrenMapper[$absPath];
247
		}
248
249
		return null;
250
	}
251
252
	/**
253
	 * Sets the path of the file you want a preview of
254
	 *
255
	 * @param string $file
256
	 * @param \OCP\Files\FileInfo|null $info
257
	 *
258
	 * @return \OC\Preview
259
	 */
260
	public function setFile($file, $info = null) {
261
		$this->file = $file;
262
		$this->info = $info;
263
264
		if ($file !== '') {
265
			$this->getFileInfo();
266
			if ($this->info instanceof \OCP\Files\FileInfo) {
267
				$this->mimeType = $this->info->getMimetype();
268
			}
269
		}
270
271
		return $this;
272
	}
273
274
	/**
275
	 * Forces the use of a specific media type
276
	 *
277
	 * @param string $mimeType
278
	 */
279
	public function setMimetype($mimeType) {
280
		$this->mimeType = $mimeType;
281
	}
282
283
	/**
284
	 * Sets the max width of the preview. It's capped by the maximum allowed size set in the
285
	 * configuration
286
	 *
287
	 * @param int $maxX
288
	 *
289
	 * @throws \Exception
290
	 * @return \OC\Preview
291
	 */
292
	public function setMaxX($maxX = 1) {
293
		if ($maxX <= 0) {
294
			throw new \Exception('Cannot set width of 0 or smaller!');
295
		}
296
		$configMaxX = $this->getConfigMaxX();
297
		$maxX = $this->limitMaxDim($maxX, $configMaxX, 'maxX');
298
		$this->maxX = $maxX;
299
300
		return $this;
301
	}
302
303
	/**
304
	 * Sets the max height of the preview. It's capped by the maximum allowed size set in the
305
	 * configuration
306
	 *
307
	 * @param int $maxY
308
	 *
309
	 * @throws \Exception
310
	 * @return \OC\Preview
311
	 */
312
	public function setMaxY($maxY = 1) {
313
		if ($maxY <= 0) {
314
			throw new \Exception('Cannot set height of 0 or smaller!');
315
		}
316
		$configMaxY = $this->getConfigMaxY();
317
		$maxY = $this->limitMaxDim($maxY, $configMaxY, 'maxY');
318
		$this->maxY = $maxY;
319
320
		return $this;
321
	}
322
323
	/**
324
	 * Sets whether we're allowed to scale up when generating a preview. It's capped by the maximum
325
	 * allowed scale factor set in the configuration
326
	 *
327
	 * @param bool $scalingUp
328
	 *
329
	 * @return \OC\Preview
330
	 */
331
	public function setScalingup($scalingUp) {
332
		if ($this->getMaxScaleFactor() === 1) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $this->getMaxScaleFactor() (string) and 1 (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
333
			$scalingUp = false;
334
		}
335
		$this->scalingUp = $scalingUp;
336
337
		return $this;
338
	}
339
340
	/**
341
	 * Set whether to cover or fill the specified dimensions
342
	 *
343
	 * @param string $mode
344
	 *
345
	 * @return \OC\Preview
346
	 */
347
	public function setMode($mode) {
348
		$this->mode = $mode;
349
350
		return $this;
351
	}
352
353
	/**
354
	 * Sets whether we need to generate a preview which keeps the aspect ratio of the original file
355
	 *
356
	 * @param bool $keepAspect
357
	 *
358
	 * @return \OC\Preview
359
	 */
360
	public function setKeepAspect($keepAspect) {
361
		$this->keepAspect = $keepAspect;
362
363
		return $this;
364
	}
365
366
	/**
367
	 * Makes sure we were given a file to preview and that it exists in the filesystem
368
	 *
369
	 * @return bool
370
	 */
371
	public function isFileValid() {
372
		$file = $this->getFile();
373
		if ($file === '') {
374
			\OCP\Util::writeLog('core', 'No filename passed', \OCP\Util::DEBUG);
375
376
			return false;
377
		}
378
379
		if (!$this->getFileInfo() instanceof FileInfo) {
380
			\OCP\Util::writeLog('core', 'File:"' . $file . '" not found', \OCP\Util::DEBUG);
381
382
			return false;
383
		}
384
385
		return true;
386
	}
387
388
	/**
389
	 * Deletes the preview of a file with specific width and height
390
	 *
391
	 * This should never delete the max preview, use deleteAllPreviews() instead
392
	 *
393
	 * @return bool
394
	 */
395
	public function deletePreview() {
396
		$fileInfo = $this->getFileInfo();
397
		if ($fileInfo !== null && $fileInfo !== false) {
398
			$fileId = $fileInfo->getId();
399
400
			$previewPath = $this->buildCachePath($fileId);
401
			if (!strpos($previewPath, 'max')) {
402
				return $this->userView->unlink($previewPath);
403
			}
404
		}
405
406
		return false;
407
	}
408
409
	/**
410
	 * Deletes all previews of a file
411
	 */
412
	public function deleteAllPreviews() {
413
		$thumbnailMount = $this->userView->getMount($this->getThumbnailsFolder());
414
		$propagator = $thumbnailMount->getStorage()->getPropagator();
415
		$propagator->beginBatch();
416
417
		$toDelete = $this->getChildren();
418
		$toDelete[] = $this->getFileInfo();
419
420
		foreach ($toDelete as $delete) {
421
			if ($delete instanceof FileInfo) {
422
				/** @var \OCP\Files\FileInfo $delete */
423
				$fileId = $delete->getId();
424
425
				// getId() might return null, e.g. when the file is a
426
				// .ocTransferId*.part file from chunked file upload.
427
				if (!empty($fileId)) {
428
					$previewPath = $this->getPreviewPath($fileId);
429
					$this->userView->rmdir($previewPath);
430
				}
431
			}
432
		}
433
434
		$propagator->commitBatch();
435
	}
436
437
	/**
438
	 * Checks if a preview matching the asked dimensions or a bigger version is already cached
439
	 *
440
	 *    * We first retrieve the size of the max preview since this is what we be used to create
441
	 * all our preview. If it doesn't exist we return false, so that it can be generated
442
	 *    * Using the dimensions of the max preview, we calculate what the size of the new
443
	 * thumbnail should be
444
	 *    * And finally, we look for a suitable candidate in the cache
445
	 *
446
	 * @param int $fileId fileId of the original file we need a preview of
447
	 *
448
	 * @return string|false path to the cached preview if it exists or false
449
	 */
450
	public function isCached($fileId) {
451
		if (is_null($fileId)) {
452
			return false;
453
		}
454
455
		/**
456
		 * Phase 1: Looking for the max preview
457
		 */
458
		$previewPath = $this->getPreviewPath($fileId);
459
		// We currently can't look for a single file due to bugs related to #16478
460
		$allThumbnails = $this->userView->getDirectoryContent($previewPath);
461
		list($maxPreviewWidth, $maxPreviewHeight) = $this->getMaxPreviewSize($allThumbnails);
462
463
		// Only use the cache if we have a max preview
464
		if (!is_null($maxPreviewWidth) && !is_null($maxPreviewHeight)) {
465
466
			/**
467
			 * Phase 2: Calculating the size of the preview we need to send back
468
			 */
469
			$this->maxPreviewWidth = $maxPreviewWidth;
470
			$this->maxPreviewHeight = $maxPreviewHeight;
471
472
			list($previewWidth, $previewHeight) = $this->simulatePreviewDimensions();
473
			if (empty($previewWidth) || empty($previewHeight)) {
474
				return false;
475
			}
476
477
			$this->previewWidth = $previewWidth;
478
			$this->previewHeight = $previewHeight;
479
480
			/**
481
			 * Phase 3: We look for a preview of the exact size
482
			 */
483
			// This gives us a calculated path to a preview of asked dimensions
484
			// thumbnailFolder/fileId/<maxX>-<maxY>(-max|-with-aspect).png
485
			$preview = $this->buildCachePath($fileId, $previewWidth, $previewHeight);
486
487
			// This checks if we have a preview of those exact dimensions in the cache
488
			if ($this->thumbnailSizeExists($allThumbnails, basename($preview))) {
489
				return $preview;
490
			}
491
492
			/**
493
			 * Phase 4: We look for a larger preview, matching the aspect ratio
494
			 */
495
			if (($this->getMaxX() >= $maxPreviewWidth)
496
				&& ($this->getMaxY() >= $maxPreviewHeight)
497
			) {
498
				// The preview we-re looking for is the exact size or larger than the max preview,
499
				// so return that
500
				return $this->buildCachePath($fileId, $maxPreviewWidth, $maxPreviewHeight);
501
			} else {
502
				// The last resort is to look for something bigger than what we've calculated,
503
				// but still smaller than the max preview
504
				return $this->isCachedBigger($fileId, $allThumbnails);
505
			}
506
		}
507
508
		return false;
509
	}
510
511
	/**
512
	 * Returns the dimensions of the max preview
513
	 *
514
	 * @param FileInfo[] $allThumbnails the list of all our cached thumbnails
515
	 *
516
	 * @return int[]
517
	 */
518
	private function getMaxPreviewSize($allThumbnails) {
519
		$maxPreviewX = null;
520
		$maxPreviewY = null;
521
522
		foreach ($allThumbnails as $thumbnail) {
523
			$name = $thumbnail['name'];
524
			if (strpos($name, 'max')) {
525
				list($maxPreviewX, $maxPreviewY) = $this->getDimensionsFromFilename($name);
526
				break;
527
			}
528
		}
529
530
		return [$maxPreviewX, $maxPreviewY];
531
	}
532
533
	/**
534
	 * Check if a specific thumbnail size is cached
535
	 *
536
	 * @param FileInfo[] $allThumbnails the list of all our cached thumbnails
537
	 * @param string $name
538
	 * @return bool
539
	 */
540
	private function thumbnailSizeExists(array $allThumbnails, $name) {
541
542
		foreach ($allThumbnails as $thumbnail) {
543
			if ($name === $thumbnail->getName()) {
544
				return true;
545
			}
546
		}
547
548
		return false;
549
	}
550
551
	/**
552
	 * Determines the size of the preview we should be looking for in the cache
553
	 *
554
	 * @return integer[]
555
	 */
556
	private function simulatePreviewDimensions() {
557
		$askedWidth = $this->getMaxX();
558
		$askedHeight = $this->getMaxY();
559
560
		if ($this->keepAspect) {
561
			list($newPreviewWidth, $newPreviewHeight) =
562
				$this->applyAspectRatio($askedWidth, $askedHeight);
563
		} else {
564
			list($newPreviewWidth, $newPreviewHeight) = $this->fixSize($askedWidth, $askedHeight);
565
		}
566
567
		return [(int)$newPreviewWidth, (int)$newPreviewHeight];
568
	}
569
570
	/**
571
	 * Resizes the boundaries to match the aspect ratio
572
	 *
573
	 * @param int $askedWidth
574
	 * @param int $askedHeight
575
	 *
576
	 * @param int $originalWidth
577
	 * @param int $originalHeight
578
	 * @return integer[]
579
	 */
580
	private function applyAspectRatio($askedWidth, $askedHeight, $originalWidth = 0, $originalHeight = 0) {
581
		if (!$originalWidth) {
582
			$originalWidth = $this->maxPreviewWidth;
583
		}
584
		if (!$originalHeight) {
585
			$originalHeight = $this->maxPreviewHeight;
586
		}
587
		$originalRatio = $originalWidth / $originalHeight;
588
		// Defines the box in which the preview has to fit
589
		$scaleFactor = $this->scalingUp ? $this->maxScaleFactor : 1;
590
		$askedWidth = min($askedWidth, $originalWidth * $scaleFactor);
591
		$askedHeight = min($askedHeight, $originalHeight * $scaleFactor);
592
593 View Code Duplication
		if ($askedWidth / $originalRatio < $askedHeight) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
594
			// width restricted
595
			$askedHeight = round($askedWidth / $originalRatio);
596
		} else {
597
			$askedWidth = round($askedHeight * $originalRatio);
598
		}
599
600
		return [(int)$askedWidth, (int)$askedHeight];
601
	}
602
603
	/**
604
	 * Resizes the boundaries to cover the area
605
	 *
606
	 * @param int $askedWidth
607
	 * @param int $askedHeight
608
	 * @param int $previewWidth
609
	 * @param int $previewHeight
610
	 * @return integer[]
611
	 */
612
	private function applyCover($askedWidth, $askedHeight, $previewWidth, $previewHeight) {
613
		$originalRatio = $previewWidth / $previewHeight;
614
		// Defines the box in which the preview has to fit
615
		$scaleFactor = $this->scalingUp ? $this->maxScaleFactor : 1;
616
		$askedWidth = min($askedWidth, $previewWidth * $scaleFactor);
617
		$askedHeight = min($askedHeight, $previewHeight * $scaleFactor);
618
619 View Code Duplication
		if ($askedWidth / $originalRatio > $askedHeight) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
620
			// height restricted
621
			$askedHeight = round($askedWidth / $originalRatio);
622
		} else {
623
			$askedWidth = round($askedHeight * $originalRatio);
624
		}
625
626
		return [(int)$askedWidth, (int)$askedHeight];
627
	}
628
629
	/**
630
	 * Makes sure an upscaled preview doesn't end up larger than the max dimensions defined in the
631
	 * config
632
	 *
633
	 * @param int $askedWidth
634
	 * @param int $askedHeight
635
	 *
636
	 * @return integer[]
637
	 */
638
	private function fixSize($askedWidth, $askedHeight) {
639
		if ($this->scalingUp) {
640
			$askedWidth = min($this->configMaxWidth, $askedWidth);
641
			$askedHeight = min($this->configMaxHeight, $askedHeight);
642
		}
643
644
		return [(int)$askedWidth, (int)$askedHeight];
645
	}
646
647
	/**
648
	 * Checks if a bigger version of a file preview is cached and if not
649
	 * return the preview of max allowed dimensions
650
	 *
651
	 * @param int $fileId fileId of the original image
652
	 * @param FileInfo[] $allThumbnails the list of all our cached thumbnails
653
	 *
654
	 * @return string path to bigger thumbnail
655
	 */
656
	private function isCachedBigger($fileId, $allThumbnails) {
657
		// This is used to eliminate any thumbnail narrower than what we need
658
		$maxX = $this->getMaxX();
659
660
		//array for usable cached thumbnails
661
		$possibleThumbnails = $this->getPossibleThumbnails($allThumbnails);
662
663
		foreach ($possibleThumbnails as $width => $path) {
664
			if ($width < $maxX) {
665
				continue;
666
			} else {
667
				return $path;
668
			}
669
		}
670
671
		// At this stage, we didn't find a preview, so we return the max preview
672
		return $this->buildCachePath($fileId, $this->maxPreviewWidth, $this->maxPreviewHeight);
673
	}
674
675
	/**
676
	 * Get possible bigger thumbnails of the given image with the proper aspect ratio
677
	 *
678
	 * @param FileInfo[] $allThumbnails the list of all our cached thumbnails
679
	 *
680
	 * @return string[] an array of paths to bigger thumbnails
681
	 */
682
	private function getPossibleThumbnails($allThumbnails) {
683
		if ($this->keepAspect) {
684
			$wantedAspectRatio = (float)($this->maxPreviewWidth / $this->maxPreviewHeight);
685
		} else {
686
			$wantedAspectRatio = (float)($this->getMaxX() / $this->getMaxY());
687
		}
688
689
		//array for usable cached thumbnails
690
		$possibleThumbnails = array();
691
		foreach ($allThumbnails as $thumbnail) {
692
			$name = rtrim($thumbnail['name'], '.png');
693
			list($x, $y, $aspectRatio) = $this->getDimensionsFromFilename($name);
694
			if (abs($aspectRatio - $wantedAspectRatio) >= 0.000001
695
				|| $this->unscalable($x, $y)
696
			) {
697
				continue;
698
			}
699
			$possibleThumbnails[$x] = $thumbnail['path'];
700
		}
701
702
		ksort($possibleThumbnails);
703
704
		return $possibleThumbnails;
705
	}
706
707
	/**
708
	 * Looks at the preview filename from the cache and extracts the size of the preview
709
	 *
710
	 * @param string $name
711
	 *
712
	 * @return array<int,int,float>
0 ignored issues
show
Documentation introduced by
The doc-type array<int,int,float> could not be parsed: Expected ">" at position 5, but found ",". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
713
	 */
714
	private function getDimensionsFromFilename($name) {
715
		$size = explode('-', $name);
716
		$x = (int)$size[0];
717
		$y = (int)$size[1];
718
		$aspectRatio = (float)($x / $y);
719
720
		return array($x, $y, $aspectRatio);
721
	}
722
723
	/**
724
	 * @param int $x
725
	 * @param int $y
726
	 *
727
	 * @return bool
728
	 */
729
	private function unscalable($x, $y) {
730
731
		$maxX = $this->getMaxX();
732
		$maxY = $this->getMaxY();
733
		$scalingUp = $this->getScalingUp();
734
		$maxScaleFactor = $this->getMaxScaleFactor();
735
736
		if ($x < $maxX || $y < $maxY) {
737
			if ($scalingUp) {
738
				$scaleFactor = $maxX / $x;
739
				if ($scaleFactor > $maxScaleFactor) {
740
					return true;
741
				}
742
			} else {
743
				return true;
744
			}
745
		}
746
747
		return false;
748
	}
749
750
	/**
751
	 * Returns a preview of a file
752
	 *
753
	 * The cache is searched first and if nothing usable was found then a preview is
754
	 * generated by one of the providers
755
	 *
756
	 * @return \OCP\IImage
757
	 */
758
	public function getPreview() {
759
		if (!is_null($this->preview) && $this->preview->valid()) {
760
			return $this->preview;
761
		}
762
763
		$this->preview = null;
764
		$fileInfo = $this->getFileInfo();
765
		if ($fileInfo === null || $fileInfo === false) {
766
			return new \OC_Image();
767
		}
768
769
		$fileId = $fileInfo->getId();
770
		$cached = $this->isCached($fileId);
771
		if ($cached) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $cached of type false|string 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...
772
			$this->getCachedPreview($fileId, $cached);
773
		}
774
775
		if (is_null($this->preview)) {
776
			$this->generatePreview($fileId);
777
		}
778
779
		// We still don't have a preview, so we send back an empty object
780
		if (is_null($this->preview)) {
781
			$this->preview = new \OC_Image();
782
		}
783
784
		return $this->preview;
785
	}
786
787
	/**
788
	 * Sends the preview, including the headers to client which requested it
789
	 *
790
	 * @param null|string $mimeTypeForHeaders the media type to use when sending back the reply
791
	 *
792
	 * @throws NotFoundException
793
	 */
794
	public function showPreview($mimeTypeForHeaders = null) {
795
		// Check if file is valid
796
		if ($this->isFileValid() === false) {
797
			throw new NotFoundException('File not found.');
798
		}
799
800
		if (is_null($this->preview)) {
801
			$this->getPreview();
802
		}
803
		if ($this->preview instanceof \OCP\IImage) {
804
			if ($this->preview->valid()) {
805
				\OCP\Response::enableCaching(3600 * 24); // 24 hours
806
			} else {
807
				$this->getMimeIcon();
808
			}
809
			$this->preview->show($mimeTypeForHeaders);
810
		}
811
	}
812
813
	/**
814
	 * Retrieves the preview from the cache and resizes it if necessary
815
	 *
816
	 * @param int $fileId fileId of the original image
817
	 * @param string $cached the path to the cached preview
818
	 */
819
	private function getCachedPreview($fileId, $cached) {
820
		$stream = $this->userView->fopen($cached, 'r');
821
		$this->preview = null;
822
		if ($stream) {
823
			$image = new \OC_Image();
824
			$image->loadFromFileHandle($stream);
825
826
			$this->preview = $image->valid() ? $image : null;
827
828
			if (!is_null($this->preview)) {
829
				// Size of the preview we calculated
830
				$maxX = $this->previewWidth;
831
				$maxY = $this->previewHeight;
832
				// Size of the preview we retrieved from the cache
833
				$previewX = (int)$this->preview->width();
834
				$previewY = (int)$this->preview->height();
835
836
				// We don't have an exact match
837
				if ($previewX !== $maxX || $previewY !== $maxY) {
838
					$this->resizeAndStore($fileId);
839
				}
840
			}
841
842
			fclose($stream);
843
		}
844
	}
845
846
	/**
847
	 * Resizes, crops, fixes orientation and stores in the cache
848
	 *
849
	 * @param int $fileId fileId of the original image
850
	 */
851
	private function resizeAndStore($fileId) {
852
		$image = $this->preview;
853
		if (!($image instanceof \OCP\IImage)) {
854
			\OCP\Util::writeLog(
855
				'core', '$this->preview is not an instance of \OCP\IImage', \OCP\Util::DEBUG
856
			);
857
858
			return;
859
		}
860
		$previewWidth = (int)$image->width();
861
		$previewHeight = (int)$image->height();
862
		$askedWidth = $this->getMaxX();
863
		$askedHeight = $this->getMaxY();
864
865
		if ($this->mode === self::MODE_COVER) {
866
			list($askedWidth, $askedHeight) =
867
				$this->applyCover($askedWidth, $askedHeight, $previewWidth, $previewHeight);
868
		}
869
870
		/**
871
		 * Phase 1: If required, adjust boundaries to keep aspect ratio
872
		 */
873
		if ($this->keepAspect) {
874
			list($askedWidth, $askedHeight) =
875
				$this->applyAspectRatio($askedWidth, $askedHeight, $previewWidth, $previewHeight);
876
		}
877
878
		/**
879
		 * Phase 2: Resizes preview to try and match requirements.
880
		 * Takes the scaling ratio into consideration
881
		 */
882
		list($newPreviewWidth, $newPreviewHeight) = $this->scale(
883
			$image, $askedWidth, $askedHeight, $previewWidth, $previewHeight
884
		);
885
886
		// The preview has been resized and should now have the asked dimensions
887
		if ($newPreviewWidth === $askedWidth && $newPreviewHeight === $askedHeight) {
888
			$this->storePreview($fileId, $newPreviewWidth, $newPreviewHeight);
889
890
			return;
891
		}
892
893
		/**
894
		 * Phase 3: We're still not there yet, so we're clipping and filling
895
		 * to match the asked dimensions
896
		 */
897
		// It turns out the scaled preview is now too big, so we crop the image
898
		if ($newPreviewWidth >= $askedWidth && $newPreviewHeight >= $askedHeight) {
899
			$this->crop($image, $askedWidth, $askedHeight, $newPreviewWidth, $newPreviewHeight);
900
			$this->storePreview($fileId, $askedWidth, $askedHeight);
901
902
			return;
903
		}
904
905
		// At least one dimension of the scaled preview is too small,
906
		// so we fill the space with a transparent background
907
		if (($newPreviewWidth < $askedWidth || $newPreviewHeight < $askedHeight)) {
908
			$this->cropAndFill(
909
				$image, $askedWidth, $askedHeight, $newPreviewWidth, $newPreviewHeight
910
			);
911
			$this->storePreview($fileId, $askedWidth, $askedHeight);
912
913
			return;
914
		}
915
916
		// The preview is smaller, but we can't touch it
917
		$this->storePreview($fileId, $newPreviewWidth, $newPreviewHeight);
918
	}
919
920
	/**
921
	 * Calculates the new dimensions of the preview
922
	 *
923
	 * The new dimensions can be larger or smaller than the ones of the preview we have to resize
924
	 *
925
	 * @param \OCP\IImage $image
926
	 * @param int $askedWidth
927
	 * @param int $askedHeight
928
	 * @param int $previewWidth
929
	 * @param int $previewHeight
930
	 *
931
	 * @return int[]
932
	 */
933
	private function scale($image, $askedWidth, $askedHeight, $previewWidth, $previewHeight) {
934
		$scalingUp = $this->getScalingUp();
935
		$maxScaleFactor = $this->getMaxScaleFactor();
936
937
		$factorX = $askedWidth / $previewWidth;
938
		$factorY = $askedHeight / $previewHeight;
939
940
		if ($factorX >= $factorY) {
941
			$factor = $factorX;
942
		} else {
943
			$factor = $factorY;
944
		}
945
946
		if ($scalingUp === false) {
947
			if ($factor > 1) {
948
				$factor = 1;
949
			}
950
		}
951
952
		// We cap when upscaling
953
		if (!is_null($maxScaleFactor)) {
954
			if ($factor > $maxScaleFactor) {
955
				\OCP\Util::writeLog(
956
					'core', 'scale factor reduced from ' . $factor . ' to ' . $maxScaleFactor,
957
					\OCP\Util::DEBUG
958
				);
959
				$factor = $maxScaleFactor;
960
			}
961
		}
962
963
		$newPreviewWidth = round($previewWidth * $factor);
964
		$newPreviewHeight = round($previewHeight * $factor);
965
966
		$image->preciseResize($newPreviewWidth, $newPreviewHeight);
967
		$this->preview = $image;
968
969
		return [$newPreviewWidth, $newPreviewHeight];
970
	}
971
972
	/**
973
	 * Crops a preview which is larger than the dimensions we've received
974
	 *
975
	 * @param \OCP\IImage $image
976
	 * @param int $askedWidth
977
	 * @param int $askedHeight
978
	 * @param int $previewWidth
979
	 * @param int $previewHeight
980
	 */
981
	private function crop($image, $askedWidth, $askedHeight, $previewWidth, $previewHeight = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $previewHeight is not used and could be removed.

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

Loading history...
982
		$cropX = floor(abs($askedWidth - $previewWidth) * 0.5);
983
		//don't crop previews on the Y axis, this sucks if it's a document.
984
		//$cropY = floor(abs($y - $newPreviewHeight) * 0.5);
985
		$cropY = 0;
986
		$image->crop($cropX, $cropY, $askedWidth, $askedHeight);
987
		$this->preview = $image;
988
	}
989
990
	/**
991
	 * Crops an image if it's larger than the dimensions we've received and fills the empty space
992
	 * with a transparent background
993
	 *
994
	 * @param \OCP\IImage $image
995
	 * @param int $askedWidth
996
	 * @param int $askedHeight
997
	 * @param int $previewWidth
998
	 * @param int $previewHeight
999
	 */
1000
	private function cropAndFill($image, $askedWidth, $askedHeight, $previewWidth, $previewHeight) {
1001 View Code Duplication
		if ($previewWidth > $askedWidth) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1002
			$cropX = floor(($previewWidth - $askedWidth) * 0.5);
1003
			$image->crop($cropX, 0, $askedWidth, $previewHeight);
1004
			$previewWidth = $askedWidth;
1005
		}
1006
1007 View Code Duplication
		if ($previewHeight > $askedHeight) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1008
			$cropY = floor(($previewHeight - $askedHeight) * 0.5);
1009
			$image->crop(0, $cropY, $previewWidth, $askedHeight);
1010
			$previewHeight = $askedHeight;
1011
		}
1012
1013
		// Creates a transparent background
1014
		$backgroundLayer = imagecreatetruecolor($askedWidth, $askedHeight);
1015
		imagealphablending($backgroundLayer, false);
1016
		$transparency = imagecolorallocatealpha($backgroundLayer, 0, 0, 0, 127);
1017
		imagefill($backgroundLayer, 0, 0, $transparency);
1018
		imagesavealpha($backgroundLayer, true);
1019
1020
		$image = $image->resource();
1021
1022
		$mergeX = floor(abs($askedWidth - $previewWidth) * 0.5);
1023
		$mergeY = floor(abs($askedHeight - $previewHeight) * 0.5);
1024
1025
		// Pastes the preview on top of the background
1026
		imagecopy(
1027
			$backgroundLayer, $image, $mergeX, $mergeY, 0, 0, $previewWidth,
1028
			$previewHeight
1029
		);
1030
1031
		$image = new \OC_Image($backgroundLayer);
1032
1033
		$this->preview = $image;
1034
	}
1035
1036
	/**
1037
	 * Saves a preview in the cache to speed up future calls
1038
	 *
1039
	 * Do not nullify the preview as it might send the whole process in a loop
1040
	 *
1041
	 * @param int $fileId fileId of the original image
1042
	 * @param int $previewWidth
1043
	 * @param int $previewHeight
1044
	 */
1045
	private function storePreview($fileId, $previewWidth, $previewHeight) {
1046
		if (empty($previewWidth) || empty($previewHeight)) {
1047
			\OCP\Util::writeLog(
1048
				'core', 'Cannot save preview of dimension ' . $previewWidth . 'x' . $previewHeight,
1049
				\OCP\Util::DEBUG
1050
			);
1051
1052
		} else {
1053
			$cachePath = $this->buildCachePath($fileId, $previewWidth, $previewHeight);
1054
			$this->userView->file_put_contents($cachePath, $this->preview->data());
1055
		}
1056
	}
1057
1058
	/**
1059
	 * Returns the path to a preview based on its dimensions and aspect
1060
	 *
1061
	 * @param int $fileId
1062
	 * @param int|null $maxX
1063
	 * @param int|null $maxY
1064
	 *
1065
	 * @return string
1066
	 */
1067
	private function buildCachePath($fileId, $maxX = null, $maxY = null) {
1068
		if (is_null($maxX)) {
1069
			$maxX = $this->getMaxX();
1070
		}
1071
		if (is_null($maxY)) {
1072
			$maxY = $this->getMaxY();
1073
		}
1074
1075
		$previewPath = $this->getPreviewPath($fileId);
1076
		$previewPath = $previewPath . strval($maxX) . '-' . strval($maxY);
1077
		$isMaxPreview =
1078
			($maxX === $this->maxPreviewWidth && $maxY === $this->maxPreviewHeight) ? true : false;
1079
		if ($isMaxPreview) {
1080
			$previewPath .= '-max';
1081
		}
1082
		if ($this->keepAspect && !$isMaxPreview) {
1083
			$previewPath .= '-with-aspect';
1084
		}
1085
		if ($this->mode === self::MODE_COVER) {
1086
			$previewPath .= '-cover';
1087
		}
1088
		$previewPath .= '.png';
1089
1090
		return $previewPath;
1091
	}
1092
1093
	/**
1094
	 * Returns the path to the folder where the previews are stored, identified by the fileId
1095
	 *
1096
	 * @param int $fileId
1097
	 *
1098
	 * @return string
1099
	 */
1100
	private function getPreviewPath($fileId) {
1101
		return $this->getThumbnailsFolder() . '/' . $fileId . '/';
1102
	}
1103
1104
	/**
1105
	 * Asks the provider to send a preview of the file which respects the maximum dimensions
1106
	 * defined in the configuration and after saving it in the cache, it is then resized to the
1107
	 * asked dimensions
1108
	 *
1109
	 * This is only called once in order to generate a large PNG of dimensions defined in the
1110
	 * configuration file. We'll be able to quickly resize it later on.
1111
	 * We never upscale the original conversion as this will be done later by the resizing
1112
	 * operation
1113
	 *
1114
	 * @param int $fileId fileId of the original image
1115
	 */
1116
	private function generatePreview($fileId) {
1117
		$file = $this->getFile();
1118
		$preview = null;
1119
1120
		$previewProviders = \OC::$server->getPreviewManager()
1121
			->getProviders();
1122
		foreach ($previewProviders as $supportedMimeType => $providers) {
1123
			if (!preg_match($supportedMimeType, $this->mimeType)) {
1124
				continue;
1125
			}
1126
1127
			foreach ($providers as $closure) {
1128
				$provider = $closure();
1129
				if (!($provider instanceof \OCP\Preview\IProvider)) {
1130
					continue;
1131
				}
1132
1133
				\OCP\Util::writeLog(
1134
					'core', 'Generating preview for "' . $file . '" with "' . get_class($provider)
1135
					. '"', \OCP\Util::DEBUG
1136
				);
1137
1138
				/** @var $provider Provider */
1139
				$preview = $provider->getThumbnail(
1140
					$file, $this->configMaxWidth, $this->configMaxHeight, $scalingUp = false,
1141
					$this->fileView
1142
				);
1143
1144
				if (!($preview instanceof \OCP\IImage)) {
1145
					continue;
1146
				}
1147
1148
				$this->preview = $preview;
1149
				$previewPath = $this->getPreviewPath($fileId);
1150
1151
				if ($this->userView->is_dir($this->getThumbnailsFolder() . '/') === false) {
1152
					$this->userView->mkdir($this->getThumbnailsFolder() . '/');
1153
				}
1154
1155
				if ($this->userView->is_dir($previewPath) === false) {
1156
					$this->userView->mkdir($previewPath);
1157
				}
1158
1159
				// This stores our large preview so that it can be used in subsequent resizing requests
1160
				$this->storeMaxPreview($previewPath);
1161
1162
				break 2;
1163
			}
1164
		}
1165
1166
		// The providers have been kind enough to give us a preview
1167
		if ($preview) {
1168
			$this->resizeAndStore($fileId);
1169
		}
1170
	}
1171
1172
	/**
1173
	 * Defines the media icon, for the media type of the original file, as the preview
1174
	 */
1175
	private function getMimeIcon() {
1176
		$image = new \OC_Image();
1177
		$mimeIconWebPath = \OC::$server->getMimeTypeDetector()->mimeTypeIcon($this->mimeType);
1178
		if (empty(\OC::$WEBROOT)) {
1179
			$mimeIconServerPath = \OC::$SERVERROOT . $mimeIconWebPath;
1180
		} else {
1181
			$mimeIconServerPath = str_replace(\OC::$WEBROOT, \OC::$SERVERROOT, $mimeIconWebPath);
1182
		}
1183
		$image->loadFromFile($mimeIconServerPath);
1184
1185
		$this->preview = $image;
1186
	}
1187
1188
	/**
1189
	 * Stores the max preview in the cache
1190
	 *
1191
	 * @param string $previewPath path to the preview
1192
	 */
1193
	private function storeMaxPreview($previewPath) {
1194
		$maxPreviewExists = false;
1195
		$preview = $this->preview;
1196
1197
		$allThumbnails = $this->userView->getDirectoryContent($previewPath);
1198
		// This is so that the cache doesn't need emptying when upgrading
1199
		// Can be replaced by an upgrade script...
1200
		foreach ($allThumbnails as $thumbnail) {
1201
			$name = rtrim($thumbnail['name'], '.png');
1202
			if (strpos($name, 'max')) {
1203
				$maxPreviewExists = true;
1204
				break;
1205
			}
1206
		}
1207
		// We haven't found the max preview, so we create it
1208
		if (!$maxPreviewExists) {
1209
			$previewWidth = $preview->width();
1210
			$previewHeight = $preview->height();
1211
			$previewPath = $previewPath . strval($previewWidth) . '-' . strval($previewHeight);
1212
			$previewPath .= '-max.png';
1213
			$this->userView->file_put_contents($previewPath, $preview->data());
1214
			$this->maxPreviewWidth = $previewWidth;
1215
			$this->maxPreviewHeight = $previewHeight;
1216
		}
1217
	}
1218
1219
	/**
1220
	 * Limits a dimension to the maximum dimension provided as argument
1221
	 *
1222
	 * @param int $dim
1223
	 * @param int $maxDim
1224
	 * @param string $dimName
1225
	 *
1226
	 * @return integer
1227
	 */
1228
	private function limitMaxDim($dim, $maxDim, $dimName) {
1229
		if (!is_null($maxDim)) {
1230
			if ($dim > $maxDim) {
1231
				\OCP\Util::writeLog(
1232
					'core', $dimName . ' reduced from ' . $dim . ' to ' . $maxDim, \OCP\Util::DEBUG
1233
				);
1234
				$dim = $maxDim;
1235
			}
1236
		}
1237
1238
		return $dim;
1239
	}
1240
1241
	/**
1242
	 * @param array $args
1243
	 */
1244
	public static function post_write($args) {
1245
		self::post_delete($args, 'files/');
1246
	}
1247
1248
	/**
1249
	 * @param array $args
1250
	 */
1251
	public static function prepare_delete_files($args) {
1252
		self::prepare_delete($args, 'files/');
1253
	}
1254
1255
	/**
1256
	 * @param array $args
1257
	 * @param string $prefix
1258
	 */
1259
	public static function prepare_delete(array $args, $prefix = '') {
1260
		$path = $args['path'];
1261
		if (substr($path, 0, 1) === '/') {
1262
			$path = substr($path, 1);
1263
		}
1264
1265
		$view = new \OC\Files\View('/' . \OC_User::getUser() . '/' . $prefix);
1266
1267
		$absPath = Files\Filesystem::normalizePath($view->getAbsolutePath($path));
1268
		$fileInfo = $view->getFileInfo($path);
1269
		if ($fileInfo === false) {
1270
			return;
1271
		}
1272
		self::addPathToDeleteFileMapper($absPath, $fileInfo);
1273
		if ($view->is_dir($path)) {
1274
			$children = self::getAllChildren($view, $path);
1275
			self::$deleteChildrenMapper[$absPath] = $children;
1276
		}
1277
	}
1278
1279
	/**
1280
	 * @param string $absolutePath
1281
	 * @param \OCP\Files\FileInfo $info
1282
	 */
1283
	private static function addPathToDeleteFileMapper($absolutePath, $info) {
1284
		self::$deleteFileMapper[$absolutePath] = $info;
1285
	}
1286
1287
	/**
1288
	 * @param \OC\Files\View $view
1289
	 * @param string $path
1290
	 *
1291
	 * @return array
1292
	 */
1293
	private static function getAllChildren($view, $path) {
1294
		$children = $view->getDirectoryContent($path);
1295
		$childrensFiles = array();
1296
1297
		$fakeRootLength = strlen($view->getRoot());
1298
1299
		for ($i = 0; $i < count($children); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1300
			$child = $children[$i];
1301
1302
			$childsPath = substr($child->getPath(), $fakeRootLength);
1303
1304
			if ($view->is_dir($childsPath)) {
1305
				$children = array_merge(
1306
					$children,
1307
					$view->getDirectoryContent($childsPath)
1308
				);
1309
			} else {
1310
				$childrensFiles[] = $child;
1311
			}
1312
		}
1313
1314
		return $childrensFiles;
1315
	}
1316
1317
	/**
1318
	 * @param array $args
1319
	 */
1320
	public static function post_delete_files($args) {
1321
		self::post_delete($args, 'files/');
1322
	}
1323
1324
	/**
1325
	 * @param array $args
1326
	 */
1327
	public static function post_delete_versions($args) {
1328
		self::post_delete($args, 'files/');
1329
	}
1330
1331
	/**
1332
	 * @param array $args
1333
	 * @param string $prefix
1334
	 */
1335
	public static function post_delete($args, $prefix = '') {
1336
		$path = Files\Filesystem::normalizePath($args['path']);
1337
1338
		$preview = new Preview(\OC_User::getUser(), $prefix, $path);
1339
		$preview->deleteAllPreviews();
1340
	}
1341
1342
}
1343