Completed
Push — stable10 ( a2a17f...3d4368 )
by Morris
128:05 queued 117:23
created

Preview::getPreview()   D

Complexity

Conditions 9
Paths 10

Size

Total Lines 28
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 16
nc 10
nop 0
dl 0
loc 28
rs 4.909
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Björn Schießle <[email protected]>
6
 * @author Frank Karlitschek <[email protected]>
7
 * @author Georg Ehrke <[email protected]>
8
 * @author Joas Schilling <[email protected]>
9
 * @author Jörn Friedrich Dreyer <[email protected]>
10
 * @author Lukas Reschke <[email protected]>
11
 * @author Morris Jobke <[email protected]>
12
 * @author Olivier Paroz <[email protected]>
13
 * @author Robin Appelman <[email protected]>
14
 * @author Roeland Jago Douma <[email protected]>
15
 * @author Thomas Müller <[email protected]>
16
 * @author Tobias Kaminsky <[email protected]>
17
 *
18
 * @license AGPL-3.0
19
 *
20
 * This code is free software: you can redistribute it and/or modify
21
 * it under the terms of the GNU Affero General Public License, version 3,
22
 * as published by the Free Software Foundation.
23
 *
24
 * This program is distributed in the hope that it will be useful,
25
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27
 * GNU Affero General Public License for more details.
28
 *
29
 * You should have received a copy of the GNU Affero General Public License, version 3,
30
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
31
 *
32
 */
33
namespace OC;
34
35
use OC\Preview\Provider;
36
use OCP\Files\FileInfo;
37
use OCP\Files\NotFoundException;
38
39
class Preview {
40
	//the thumbnail folder
41
	const THUMBNAILS_FOLDER = 'thumbnails';
42
43
	const MODE_FILL = 'fill';
44
	const MODE_COVER = 'cover';
45
46
	//config
47
	private $maxScaleFactor;
48
	/** @var int maximum width allowed for a preview */
49
	private $configMaxWidth;
50
	/** @var int maximum height allowed for a preview */
51
	private $configMaxHeight;
52
53
	//fileview object
54
	private $fileView = null;
55
	private $userView = null;
56
57
	//vars
58
	private $file;
59
	private $maxX;
60
	private $maxY;
61
	private $scalingUp;
62
	private $mimeType;
63
	private $keepAspect = false;
64
	private $mode = self::MODE_FILL;
65
66
	//used to calculate the size of the preview to generate
67
	/** @var int $maxPreviewWidth max width a preview can have */
68
	private $maxPreviewWidth;
69
	/** @var int $maxPreviewHeight max height a preview can have */
70
	private $maxPreviewHeight;
71
	/** @var int $previewWidth calculated width of the preview we're looking for */
72
	private $previewWidth;
73
	/** @var int $previewHeight calculated height of the preview we're looking for */
74
	private $previewHeight;
75
76
	//filemapper used for deleting previews
77
	// index is path, value is fileinfo
78
	static public $deleteFileMapper = array();
79
	static public $deleteChildrenMapper = array();
80
81
	/**
82
	 * preview images object
83
	 *
84
	 * @var \OCP\IImage
85
	 */
86
	private $preview;
87
88
	/**
89
	 * @var \OCP\Files\FileInfo
90
	 */
91
	protected $info;
92
93
	/**
94
	 * check if thumbnail or bigger version of thumbnail of file is cached
95
	 *
96
	 * @param string $user userid - if no user is given, OC_User::getUser will be used
97
	 * @param string $root path of root
98
	 * @param string $file The path to the file where you want a thumbnail from
99
	 * @param int $maxX The maximum X size of the thumbnail. It can be smaller depending on the
100
	 *     shape of the image
101
	 * @param int $maxY The maximum Y size of the thumbnail. It can be smaller depending on the
102
	 *     shape of the image
103
	 * @param bool $scalingUp Disable/Enable upscaling of previews
104
	 *
105
	 * @throws \Exception
106
	 * @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...
107
	 *                    false if thumbnail does not exist
108
	 *                    path to thumbnail if thumbnail exists
109
	 */
110
	public function __construct(
111
		$user = '',
112
		$root = '/',
113
		$file = '', $maxX = 1,
114
		$maxY = 1,
115
		$scalingUp = true
116
	) {
117
		//init fileviews
118
		if ($user === '') {
119
			$user = \OC_User::getUser();
120
		}
121
		$this->fileView = new \OC\Files\View('/' . $user . '/' . $root);
122
		$this->userView = new \OC\Files\View('/' . $user);
123
124
		//set config
125
		$sysConfig = \OC::$server->getConfig();
126
		$this->configMaxWidth = $sysConfig->getSystemValue('preview_max_x', 2048);
127
		$this->configMaxHeight = $sysConfig->getSystemValue('preview_max_y', 2048);
128
		$this->maxScaleFactor = $sysConfig->getSystemValue('preview_max_scale_factor', 2);
129
130
		//save parameters
131
		$this->setFile($file);
132
		$this->setMaxX((int)$maxX);
133
		$this->setMaxY((int)$maxY);
134
		$this->setScalingUp($scalingUp);
135
136
		$this->preview = null;
137
138
		//check if there are preview backends
139
		if (!\OC::$server->getPreviewManager()
140
				->hasProviders()
141
			&& \OC::$server->getConfig()
142
				->getSystemValue('enable_previews', true)
143
		) {
144
			\OCP\Util::writeLog('core', 'No preview providers exist', \OCP\Util::ERROR);
145
			throw new \Exception('No preview providers');
146
		}
147
	}
148
149
	/**
150
	 * returns the path of the file you want a thumbnail from
151
	 *
152
	 * @return string
153
	 */
154
	public function getFile() {
155
		return $this->file;
156
	}
157
158
	/**
159
	 * returns the max width of the preview
160
	 *
161
	 * @return integer
162
	 */
163
	public function getMaxX() {
164
		return $this->maxX;
165
	}
166
167
	/**
168
	 * returns the max height of the preview
169
	 *
170
	 * @return integer
171
	 */
172
	public function getMaxY() {
173
		return $this->maxY;
174
	}
175
176
	/**
177
	 * returns whether or not scalingup is enabled
178
	 *
179
	 * @return bool
180
	 */
181
	public function getScalingUp() {
182
		return $this->scalingUp;
183
	}
184
185
	/**
186
	 * returns the name of the thumbnailfolder
187
	 *
188
	 * @return string
189
	 */
190
	public function getThumbnailsFolder() {
191
		return self::THUMBNAILS_FOLDER;
192
	}
193
194
	/**
195
	 * returns the max scale factor
196
	 *
197
	 * @return string
198
	 */
199
	public function getMaxScaleFactor() {
200
		return $this->maxScaleFactor;
201
	}
202
203
	/**
204
	 * returns the max width set in ownCloud's config
205
	 *
206
	 * @return integer
207
	 */
208
	public function getConfigMaxX() {
209
		return $this->configMaxWidth;
210
	}
211
212
	/**
213
	 * returns the max height set in ownCloud's config
214
	 *
215
	 * @return integer
216
	 */
217
	public function getConfigMaxY() {
218
		return $this->configMaxHeight;
219
	}
220
221
	/**
222
	 * Returns the FileInfo object associated with the file to preview
223
	 *
224
	 * @return false|Files\FileInfo|\OCP\Files\FileInfo
225
	 */
226
	protected function getFileInfo() {
227
		$absPath = $this->fileView->getAbsolutePath($this->file);
228
		$absPath = Files\Filesystem::normalizePath($absPath);
229
		if (array_key_exists($absPath, self::$deleteFileMapper)) {
230
			$this->info = self::$deleteFileMapper[$absPath];
231
		} else if (!$this->info) {
232
			$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...
233
		}
234
235
		return $this->info;
236
	}
237
238
239
	/**
240
	 * @return array|null
241
	 */
242
	private function getChildren() {
243
		$absPath = $this->fileView->getAbsolutePath($this->file);
244
		$absPath = Files\Filesystem::normalizePath($absPath);
245
246
		if (array_key_exists($absPath, self::$deleteChildrenMapper)) {
247
			return self::$deleteChildrenMapper[$absPath];
248
		}
249
250
		return null;
251
	}
252
253
	/**
254
	 * Sets the path of the file you want a preview of
255
	 *
256
	 * @param string $file
257
	 * @param \OCP\Files\FileInfo|null $info
258
	 *
259
	 * @return \OC\Preview
260
	 */
261
	public function setFile($file, $info = null) {
262
		$this->file = $file;
263
		$this->info = $info;
264
265
		if ($file !== '') {
266
			$this->getFileInfo();
267
			if ($this->info instanceof \OCP\Files\FileInfo) {
268
				$this->mimeType = $this->info->getMimetype();
269
			}
270
		}
271
272
		return $this;
273
	}
274
275
	/**
276
	 * Forces the use of a specific media type
277
	 *
278
	 * @param string $mimeType
279
	 */
280
	public function setMimetype($mimeType) {
281
		$this->mimeType = $mimeType;
282
	}
283
284
	/**
285
	 * Sets the max width of the preview. It's capped by the maximum allowed size set in the
286
	 * configuration
287
	 *
288
	 * @param int $maxX
289
	 *
290
	 * @throws \Exception
291
	 * @return \OC\Preview
292
	 */
293
	public function setMaxX($maxX = 1) {
294
		if ($maxX <= 0) {
295
			throw new \Exception('Cannot set width of 0 or smaller!');
296
		}
297
		$configMaxX = $this->getConfigMaxX();
298
		$maxX = $this->limitMaxDim($maxX, $configMaxX, 'maxX');
299
		$this->maxX = $maxX;
300
301
		return $this;
302
	}
303
304
	/**
305
	 * Sets the max height of the preview. It's capped by the maximum allowed size set in the
306
	 * configuration
307
	 *
308
	 * @param int $maxY
309
	 *
310
	 * @throws \Exception
311
	 * @return \OC\Preview
312
	 */
313
	public function setMaxY($maxY = 1) {
314
		if ($maxY <= 0) {
315
			throw new \Exception('Cannot set height of 0 or smaller!');
316
		}
317
		$configMaxY = $this->getConfigMaxY();
318
		$maxY = $this->limitMaxDim($maxY, $configMaxY, 'maxY');
319
		$this->maxY = $maxY;
320
321
		return $this;
322
	}
323
324
	/**
325
	 * Sets whether we're allowed to scale up when generating a preview. It's capped by the maximum
326
	 * allowed scale factor set in the configuration
327
	 *
328
	 * @param bool $scalingUp
329
	 *
330
	 * @return \OC\Preview
331
	 */
332
	public function setScalingup($scalingUp) {
333
		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...
334
			$scalingUp = false;
335
		}
336
		$this->scalingUp = $scalingUp;
337
338
		return $this;
339
	}
340
341
	/**
342
	 * Set whether to cover or fill the specified dimensions
343
	 *
344
	 * @param string $mode
345
	 *
346
	 * @return \OC\Preview
347
	 */
348
	public function setMode($mode) {
349
		$this->mode = $mode;
350
351
		return $this;
352
	}
353
354
	/**
355
	 * Sets whether we need to generate a preview which keeps the aspect ratio of the original file
356
	 *
357
	 * @param bool $keepAspect
358
	 *
359
	 * @return \OC\Preview
360
	 */
361
	public function setKeepAspect($keepAspect) {
362
		$this->keepAspect = $keepAspect;
363
364
		return $this;
365
	}
366
367
	/**
368
	 * Makes sure we were given a file to preview and that it exists in the filesystem
369
	 *
370
	 * @return bool
371
	 */
372
	public function isFileValid() {
373
		$file = $this->getFile();
374
		if ($file === '') {
375
			\OCP\Util::writeLog('core', 'No filename passed', \OCP\Util::DEBUG);
376
377
			return false;
378
		}
379
380
		if (!$this->getFileInfo() instanceof FileInfo) {
381
			\OCP\Util::writeLog('core', 'File:"' . $file . '" not found', \OCP\Util::DEBUG);
382
383
			return false;
384
		}
385
386
		return true;
387
	}
388
389
	/**
390
	 * Deletes the preview of a file with specific width and height
391
	 *
392
	 * This should never delete the max preview, use deleteAllPreviews() instead
393
	 *
394
	 * @return bool
395
	 */
396
	public function deletePreview() {
397
		$fileInfo = $this->getFileInfo();
398
		if ($fileInfo !== null && $fileInfo !== false) {
399
			$fileId = $fileInfo->getId();
400
401
			$previewPath = $this->buildCachePath($fileId);
402
			if (!strpos($previewPath, 'max')) {
403
				return $this->userView->unlink($previewPath);
404
			}
405
		}
406
407
		return false;
408
	}
409
410
	/**
411
	 * Deletes all previews of a file
412
	 */
413
	public function deleteAllPreviews() {
414
		$thumbnailMount = $this->userView->getMount($this->getThumbnailsFolder());
415
		$propagator = $thumbnailMount->getStorage()->getPropagator();
416
		$propagator->beginBatch();
417
418
		$toDelete = $this->getChildren();
419
		$toDelete[] = $this->getFileInfo();
420
421
		foreach ($toDelete as $delete) {
422
			if ($delete instanceof FileInfo) {
423
				/** @var \OCP\Files\FileInfo $delete */
424
				$fileId = $delete->getId();
425
426
				// getId() might return null, e.g. when the file is a
427
				// .ocTransferId*.part file from chunked file upload.
428
				if (!empty($fileId)) {
429
					$previewPath = $this->getPreviewPath($fileId);
430
					$this->userView->rmdir($previewPath);
431
				}
432
			}
433
		}
434
435
		$propagator->commitBatch();
436
	}
437
438
	/**
439
	 * Checks if a preview matching the asked dimensions or a bigger version is already cached
440
	 *
441
	 *    * We first retrieve the size of the max preview since this is what we be used to create
442
	 * all our preview. If it doesn't exist we return false, so that it can be generated
443
	 *    * Using the dimensions of the max preview, we calculate what the size of the new
444
	 * thumbnail should be
445
	 *    * And finally, we look for a suitable candidate in the cache
446
	 *
447
	 * @param int $fileId fileId of the original file we need a preview of
448
	 *
449
	 * @return string|false path to the cached preview if it exists or false
450
	 */
451
	public function isCached($fileId) {
452
		if (is_null($fileId)) {
453
			return false;
454
		}
455
456
		/**
457
		 * Phase 1: Looking for the max preview
458
		 */
459
		$previewPath = $this->getPreviewPath($fileId);
460
		// We currently can't look for a single file due to bugs related to #16478
461
		$allThumbnails = $this->userView->getDirectoryContent($previewPath);
462
		list($maxPreviewWidth, $maxPreviewHeight) = $this->getMaxPreviewSize($allThumbnails);
463
464
		// Only use the cache if we have a max preview
465
		if (!is_null($maxPreviewWidth) && !is_null($maxPreviewHeight)) {
466
467
			/**
468
			 * Phase 2: Calculating the size of the preview we need to send back
469
			 */
470
			$this->maxPreviewWidth = $maxPreviewWidth;
471
			$this->maxPreviewHeight = $maxPreviewHeight;
472
473
			list($previewWidth, $previewHeight) = $this->simulatePreviewDimensions();
474
			if (empty($previewWidth) || empty($previewHeight)) {
475
				return false;
476
			}
477
478
			$this->previewWidth = $previewWidth;
479
			$this->previewHeight = $previewHeight;
480
481
			/**
482
			 * Phase 3: We look for a preview of the exact size
483
			 */
484
			// This gives us a calculated path to a preview of asked dimensions
485
			// thumbnailFolder/fileId/<maxX>-<maxY>(-max|-with-aspect).png
486
			$preview = $this->buildCachePath($fileId, $previewWidth, $previewHeight);
487
488
			// This checks if we have a preview of those exact dimensions in the cache
489
			if ($this->thumbnailSizeExists($allThumbnails, basename($preview))) {
490
				return $preview;
491
			}
492
493
			/**
494
			 * Phase 4: We look for a larger preview, matching the aspect ratio
495
			 */
496
			if (($this->getMaxX() >= $maxPreviewWidth)
497
				&& ($this->getMaxY() >= $maxPreviewHeight)
498
			) {
499
				// The preview we-re looking for is the exact size or larger than the max preview,
500
				// so return that
501
				return $this->buildCachePath($fileId, $maxPreviewWidth, $maxPreviewHeight);
502
			} else {
503
				// The last resort is to look for something bigger than what we've calculated,
504
				// but still smaller than the max preview
505
				return $this->isCachedBigger($fileId, $allThumbnails);
506
			}
507
		}
508
509
		return false;
510
	}
511
512
	/**
513
	 * Returns the dimensions of the max preview
514
	 *
515
	 * @param FileInfo[] $allThumbnails the list of all our cached thumbnails
516
	 *
517
	 * @return int[]
518
	 */
519
	private function getMaxPreviewSize($allThumbnails) {
520
		$maxPreviewX = null;
521
		$maxPreviewY = null;
522
523
		foreach ($allThumbnails as $thumbnail) {
524
			$name = $thumbnail['name'];
525
			if (strpos($name, 'max')) {
526
				list($maxPreviewX, $maxPreviewY) = $this->getDimensionsFromFilename($name);
527
				break;
528
			}
529
		}
530
531
		return [$maxPreviewX, $maxPreviewY];
532
	}
533
534
	/**
535
	 * Check if a specific thumbnail size is cached
536
	 *
537
	 * @param FileInfo[] $allThumbnails the list of all our cached thumbnails
538
	 * @param string $name
539
	 * @return bool
540
	 */
541
	private function thumbnailSizeExists(array $allThumbnails, $name) {
542
543
		foreach ($allThumbnails as $thumbnail) {
544
			if ($name === $thumbnail->getName()) {
545
				return true;
546
			}
547
		}
548
549
		return false;
550
	}
551
552
	/**
553
	 * Determines the size of the preview we should be looking for in the cache
554
	 *
555
	 * @return integer[]
556
	 */
557
	private function simulatePreviewDimensions() {
558
		$askedWidth = $this->getMaxX();
559
		$askedHeight = $this->getMaxY();
560
561
		if ($this->keepAspect) {
562
			list($newPreviewWidth, $newPreviewHeight) =
563
				$this->applyAspectRatio($askedWidth, $askedHeight);
564
		} else {
565
			list($newPreviewWidth, $newPreviewHeight) = $this->fixSize($askedWidth, $askedHeight);
566
		}
567
568
		return [(int)$newPreviewWidth, (int)$newPreviewHeight];
569
	}
570
571
	/**
572
	 * Resizes the boundaries to match the aspect ratio
573
	 *
574
	 * @param int $askedWidth
575
	 * @param int $askedHeight
576
	 *
577
	 * @param int $originalWidth
578
	 * @param int $originalHeight
579
	 * @return integer[]
580
	 */
581
	private function applyAspectRatio($askedWidth, $askedHeight, $originalWidth = 0, $originalHeight = 0) {
582
		if (!$originalWidth) {
583
			$originalWidth = $this->maxPreviewWidth;
584
		}
585
		if (!$originalHeight) {
586
			$originalHeight = $this->maxPreviewHeight;
587
		}
588
		$originalRatio = $originalWidth / $originalHeight;
589
		// Defines the box in which the preview has to fit
590
		$scaleFactor = $this->scalingUp ? $this->maxScaleFactor : 1;
591
		$askedWidth = min($askedWidth, $originalWidth * $scaleFactor);
592
		$askedHeight = min($askedHeight, $originalHeight * $scaleFactor);
593
594 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...
595
			// width restricted
596
			$askedHeight = round($askedWidth / $originalRatio);
597
		} else {
598
			$askedWidth = round($askedHeight * $originalRatio);
599
		}
600
601
		return [(int)$askedWidth, (int)$askedHeight];
602
	}
603
604
	/**
605
	 * Resizes the boundaries to cover the area
606
	 *
607
	 * @param int $askedWidth
608
	 * @param int $askedHeight
609
	 * @param int $previewWidth
610
	 * @param int $previewHeight
611
	 * @return integer[]
612
	 */
613
	private function applyCover($askedWidth, $askedHeight, $previewWidth, $previewHeight) {
614
		$originalRatio = $previewWidth / $previewHeight;
615
		// Defines the box in which the preview has to fit
616
		$scaleFactor = $this->scalingUp ? $this->maxScaleFactor : 1;
617
		$askedWidth = min($askedWidth, $previewWidth * $scaleFactor);
618
		$askedHeight = min($askedHeight, $previewHeight * $scaleFactor);
619
620 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...
621
			// height restricted
622
			$askedHeight = round($askedWidth / $originalRatio);
623
		} else {
624
			$askedWidth = round($askedHeight * $originalRatio);
625
		}
626
627
		return [(int)$askedWidth, (int)$askedHeight];
628
	}
629
630
	/**
631
	 * Makes sure an upscaled preview doesn't end up larger than the max dimensions defined in the
632
	 * config
633
	 *
634
	 * @param int $askedWidth
635
	 * @param int $askedHeight
636
	 *
637
	 * @return integer[]
638
	 */
639
	private function fixSize($askedWidth, $askedHeight) {
640
		if ($this->scalingUp) {
641
			$askedWidth = min($this->configMaxWidth, $askedWidth);
642
			$askedHeight = min($this->configMaxHeight, $askedHeight);
643
		}
644
645
		return [(int)$askedWidth, (int)$askedHeight];
646
	}
647
648
	/**
649
	 * Checks if a bigger version of a file preview is cached and if not
650
	 * return the preview of max allowed dimensions
651
	 *
652
	 * @param int $fileId fileId of the original image
653
	 * @param FileInfo[] $allThumbnails the list of all our cached thumbnails
654
	 *
655
	 * @return string path to bigger thumbnail
656
	 */
657
	private function isCachedBigger($fileId, $allThumbnails) {
658
		// This is used to eliminate any thumbnail narrower than what we need
659
		$maxX = $this->getMaxX();
660
661
		//array for usable cached thumbnails
662
		$possibleThumbnails = $this->getPossibleThumbnails($allThumbnails);
663
664
		foreach ($possibleThumbnails as $width => $path) {
665
			if ($width < $maxX) {
666
				continue;
667
			} else {
668
				return $path;
669
			}
670
		}
671
672
		// At this stage, we didn't find a preview, so we return the max preview
673
		return $this->buildCachePath($fileId, $this->maxPreviewWidth, $this->maxPreviewHeight);
674
	}
675
676
	/**
677
	 * Get possible bigger thumbnails of the given image with the proper aspect ratio
678
	 *
679
	 * @param FileInfo[] $allThumbnails the list of all our cached thumbnails
680
	 *
681
	 * @return string[] an array of paths to bigger thumbnails
682
	 */
683
	private function getPossibleThumbnails($allThumbnails) {
684
		if ($this->keepAspect) {
685
			$wantedAspectRatio = (float)($this->maxPreviewWidth / $this->maxPreviewHeight);
686
		} else {
687
			$wantedAspectRatio = (float)($this->getMaxX() / $this->getMaxY());
688
		}
689
690
		//array for usable cached thumbnails
691
		$possibleThumbnails = array();
692
		foreach ($allThumbnails as $thumbnail) {
693
			$name = rtrim($thumbnail['name'], '.png');
694
			list($x, $y, $aspectRatio) = $this->getDimensionsFromFilename($name);
695
			if (abs($aspectRatio - $wantedAspectRatio) >= 0.000001
696
				|| $this->unscalable($x, $y)
697
			) {
698
				continue;
699
			}
700
			$possibleThumbnails[$x] = $thumbnail['path'];
701
		}
702
703
		ksort($possibleThumbnails);
704
705
		return $possibleThumbnails;
706
	}
707
708
	/**
709
	 * Looks at the preview filename from the cache and extracts the size of the preview
710
	 *
711
	 * @param string $name
712
	 *
713
	 * @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...
714
	 */
715
	private function getDimensionsFromFilename($name) {
716
		$size = explode('-', $name);
717
		$x = (int)$size[0];
718
		$y = (int)$size[1];
719
		$aspectRatio = (float)($x / $y);
720
721
		return array($x, $y, $aspectRatio);
722
	}
723
724
	/**
725
	 * @param int $x
726
	 * @param int $y
727
	 *
728
	 * @return bool
729
	 */
730
	private function unscalable($x, $y) {
731
732
		$maxX = $this->getMaxX();
733
		$maxY = $this->getMaxY();
734
		$scalingUp = $this->getScalingUp();
735
		$maxScaleFactor = $this->getMaxScaleFactor();
736
737
		if ($x < $maxX || $y < $maxY) {
738
			if ($scalingUp) {
739
				$scaleFactor = $maxX / $x;
740
				if ($scaleFactor > $maxScaleFactor) {
741
					return true;
742
				}
743
			} else {
744
				return true;
745
			}
746
		}
747
748
		return false;
749
	}
750
751
	/**
752
	 * Returns a preview of a file
753
	 *
754
	 * The cache is searched first and if nothing usable was found then a preview is
755
	 * generated by one of the providers
756
	 *
757
	 * @return \OCP\IImage
758
	 */
759
	public function getPreview() {
760
		if (!is_null($this->preview) && $this->preview->valid()) {
761
			return $this->preview;
762
		}
763
764
		$this->preview = null;
765
		$fileInfo = $this->getFileInfo();
766
		if ($fileInfo === null || $fileInfo === false || !$fileInfo->isReadable()) {
767
			return new \OC_Image();
768
		}
769
770
		$fileId = $fileInfo->getId();
771
		$cached = $this->isCached($fileId);
772
		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...
773
			$this->getCachedPreview($fileId, $cached);
774
		}
775
776
		if (is_null($this->preview)) {
777
			$this->generatePreview($fileId);
778
		}
779
780
		// We still don't have a preview, so we send back an empty object
781
		if (is_null($this->preview)) {
782
			$this->preview = new \OC_Image();
783
		}
784
785
		return $this->preview;
786
	}
787
788
	/**
789
	 * Sends the preview, including the headers to client which requested it
790
	 *
791
	 * @param null|string $mimeTypeForHeaders the media type to use when sending back the reply
792
	 *
793
	 * @throws NotFoundException
794
	 */
795
	public function showPreview($mimeTypeForHeaders = null) {
796
		// Check if file is valid
797
		if ($this->isFileValid() === false) {
798
			throw new NotFoundException('File not found.');
799
		}
800
801
		if (is_null($this->preview)) {
802
			$this->getPreview();
803
		}
804
		if ($this->preview instanceof \OCP\IImage) {
805
			if ($this->preview->valid()) {
806
				\OCP\Response::enableCaching(3600 * 24); // 24 hours
807
			} else {
808
				$this->getMimeIcon();
809
			}
810
			$this->preview->show($mimeTypeForHeaders);
811
		}
812
	}
813
814
	/**
815
	 * Retrieves the preview from the cache and resizes it if necessary
816
	 *
817
	 * @param int $fileId fileId of the original image
818
	 * @param string $cached the path to the cached preview
819
	 */
820
	private function getCachedPreview($fileId, $cached) {
821
		$stream = $this->userView->fopen($cached, 'r');
822
		$this->preview = null;
823
		if ($stream) {
824
			$image = new \OC_Image();
825
			$image->loadFromFileHandle($stream);
826
827
			$this->preview = $image->valid() ? $image : null;
828
829
			if (!is_null($this->preview)) {
830
				// Size of the preview we calculated
831
				$maxX = $this->previewWidth;
832
				$maxY = $this->previewHeight;
833
				// Size of the preview we retrieved from the cache
834
				$previewX = (int)$this->preview->width();
835
				$previewY = (int)$this->preview->height();
836
837
				// We don't have an exact match
838
				if ($previewX !== $maxX || $previewY !== $maxY) {
839
					$this->resizeAndStore($fileId);
840
				}
841
			}
842
843
			fclose($stream);
844
		}
845
	}
846
847
	/**
848
	 * Resizes, crops, fixes orientation and stores in the cache
849
	 *
850
	 * @param int $fileId fileId of the original image
851
	 */
852
	private function resizeAndStore($fileId) {
853
		$image = $this->preview;
854
		if (!($image instanceof \OCP\IImage)) {
855
			\OCP\Util::writeLog(
856
				'core', '$this->preview is not an instance of \OCP\IImage', \OCP\Util::DEBUG
857
			);
858
859
			return;
860
		}
861
		$previewWidth = (int)$image->width();
862
		$previewHeight = (int)$image->height();
863
		$askedWidth = $this->getMaxX();
864
		$askedHeight = $this->getMaxY();
865
866
		if ($this->mode === self::MODE_COVER) {
867
			list($askedWidth, $askedHeight) =
868
				$this->applyCover($askedWidth, $askedHeight, $previewWidth, $previewHeight);
869
		}
870
871
		/**
872
		 * Phase 1: If required, adjust boundaries to keep aspect ratio
873
		 */
874
		if ($this->keepAspect) {
875
			list($askedWidth, $askedHeight) =
876
				$this->applyAspectRatio($askedWidth, $askedHeight, $previewWidth, $previewHeight);
877
		}
878
879
		/**
880
		 * Phase 2: Resizes preview to try and match requirements.
881
		 * Takes the scaling ratio into consideration
882
		 */
883
		list($newPreviewWidth, $newPreviewHeight) = $this->scale(
884
			$image, $askedWidth, $askedHeight, $previewWidth, $previewHeight
885
		);
886
887
		// The preview has been resized and should now have the asked dimensions
888
		if ($newPreviewWidth === $askedWidth && $newPreviewHeight === $askedHeight) {
889
			$this->storePreview($fileId, $newPreviewWidth, $newPreviewHeight);
890
891
			return;
892
		}
893
894
		/**
895
		 * Phase 3: We're still not there yet, so we're clipping and filling
896
		 * to match the asked dimensions
897
		 */
898
		// It turns out the scaled preview is now too big, so we crop the image
899
		if ($newPreviewWidth >= $askedWidth && $newPreviewHeight >= $askedHeight) {
900
			$this->crop($image, $askedWidth, $askedHeight, $newPreviewWidth, $newPreviewHeight);
901
			$this->storePreview($fileId, $askedWidth, $askedHeight);
902
903
			return;
904
		}
905
906
		// At least one dimension of the scaled preview is too small,
907
		// so we fill the space with a transparent background
908
		if (($newPreviewWidth < $askedWidth || $newPreviewHeight < $askedHeight)) {
909
			$this->cropAndFill(
910
				$image, $askedWidth, $askedHeight, $newPreviewWidth, $newPreviewHeight
911
			);
912
			$this->storePreview($fileId, $askedWidth, $askedHeight);
913
914
			return;
915
		}
916
917
		// The preview is smaller, but we can't touch it
918
		$this->storePreview($fileId, $newPreviewWidth, $newPreviewHeight);
919
	}
920
921
	/**
922
	 * Calculates the new dimensions of the preview
923
	 *
924
	 * The new dimensions can be larger or smaller than the ones of the preview we have to resize
925
	 *
926
	 * @param \OCP\IImage $image
927
	 * @param int $askedWidth
928
	 * @param int $askedHeight
929
	 * @param int $previewWidth
930
	 * @param int $previewHeight
931
	 *
932
	 * @return int[]
933
	 */
934
	private function scale($image, $askedWidth, $askedHeight, $previewWidth, $previewHeight) {
935
		$scalingUp = $this->getScalingUp();
936
		$maxScaleFactor = $this->getMaxScaleFactor();
937
938
		$factorX = $askedWidth / $previewWidth;
939
		$factorY = $askedHeight / $previewHeight;
940
941
		if ($factorX >= $factorY) {
942
			$factor = $factorX;
943
		} else {
944
			$factor = $factorY;
945
		}
946
947
		if ($scalingUp === false) {
948
			if ($factor > 1) {
949
				$factor = 1;
950
			}
951
		}
952
953
		// We cap when upscaling
954
		if (!is_null($maxScaleFactor)) {
955
			if ($factor > $maxScaleFactor) {
956
				\OCP\Util::writeLog(
957
					'core', 'scale factor reduced from ' . $factor . ' to ' . $maxScaleFactor,
958
					\OCP\Util::DEBUG
959
				);
960
				$factor = $maxScaleFactor;
961
			}
962
		}
963
964
		$newPreviewWidth = round($previewWidth * $factor);
965
		$newPreviewHeight = round($previewHeight * $factor);
966
967
		$image->preciseResize($newPreviewWidth, $newPreviewHeight);
968
		$this->preview = $image;
969
970
		return [$newPreviewWidth, $newPreviewHeight];
971
	}
972
973
	/**
974
	 * Crops a preview which is larger than the dimensions we've received
975
	 *
976
	 * @param \OCP\IImage $image
977
	 * @param int $askedWidth
978
	 * @param int $askedHeight
979
	 * @param int $previewWidth
980
	 * @param int $previewHeight
981
	 */
982
	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...
983
		$cropX = floor(abs($askedWidth - $previewWidth) * 0.5);
984
		//don't crop previews on the Y axis, this sucks if it's a document.
985
		//$cropY = floor(abs($y - $newPreviewHeight) * 0.5);
986
		$cropY = 0;
987
		$image->crop($cropX, $cropY, $askedWidth, $askedHeight);
988
		$this->preview = $image;
989
	}
990
991
	/**
992
	 * Crops an image if it's larger than the dimensions we've received and fills the empty space
993
	 * with a transparent background
994
	 *
995
	 * @param \OCP\IImage $image
996
	 * @param int $askedWidth
997
	 * @param int $askedHeight
998
	 * @param int $previewWidth
999
	 * @param int $previewHeight
1000
	 */
1001
	private function cropAndFill($image, $askedWidth, $askedHeight, $previewWidth, $previewHeight) {
1002 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...
1003
			$cropX = floor(($previewWidth - $askedWidth) * 0.5);
1004
			$image->crop($cropX, 0, $askedWidth, $previewHeight);
1005
			$previewWidth = $askedWidth;
1006
		}
1007
1008 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...
1009
			$cropY = floor(($previewHeight - $askedHeight) * 0.5);
1010
			$image->crop(0, $cropY, $previewWidth, $askedHeight);
1011
			$previewHeight = $askedHeight;
1012
		}
1013
1014
		// Creates a transparent background
1015
		$backgroundLayer = imagecreatetruecolor($askedWidth, $askedHeight);
1016
		imagealphablending($backgroundLayer, false);
1017
		$transparency = imagecolorallocatealpha($backgroundLayer, 0, 0, 0, 127);
1018
		imagefill($backgroundLayer, 0, 0, $transparency);
1019
		imagesavealpha($backgroundLayer, true);
1020
1021
		$image = $image->resource();
1022
1023
		$mergeX = floor(abs($askedWidth - $previewWidth) * 0.5);
1024
		$mergeY = floor(abs($askedHeight - $previewHeight) * 0.5);
1025
1026
		// Pastes the preview on top of the background
1027
		imagecopy(
1028
			$backgroundLayer, $image, $mergeX, $mergeY, 0, 0, $previewWidth,
1029
			$previewHeight
1030
		);
1031
1032
		$image = new \OC_Image($backgroundLayer);
1033
1034
		$this->preview = $image;
1035
	}
1036
1037
	/**
1038
	 * Saves a preview in the cache to speed up future calls
1039
	 *
1040
	 * Do not nullify the preview as it might send the whole process in a loop
1041
	 *
1042
	 * @param int $fileId fileId of the original image
1043
	 * @param int $previewWidth
1044
	 * @param int $previewHeight
1045
	 */
1046
	private function storePreview($fileId, $previewWidth, $previewHeight) {
1047
		if (empty($previewWidth) || empty($previewHeight)) {
1048
			\OCP\Util::writeLog(
1049
				'core', 'Cannot save preview of dimension ' . $previewWidth . 'x' . $previewHeight,
1050
				\OCP\Util::DEBUG
1051
			);
1052
1053
		} else {
1054
			$cachePath = $this->buildCachePath($fileId, $previewWidth, $previewHeight);
1055
			$this->userView->file_put_contents($cachePath, $this->preview->data());
1056
		}
1057
	}
1058
1059
	/**
1060
	 * Returns the path to a preview based on its dimensions and aspect
1061
	 *
1062
	 * @param int $fileId
1063
	 * @param int|null $maxX
1064
	 * @param int|null $maxY
1065
	 *
1066
	 * @return string
1067
	 */
1068
	private function buildCachePath($fileId, $maxX = null, $maxY = null) {
1069
		if (is_null($maxX)) {
1070
			$maxX = $this->getMaxX();
1071
		}
1072
		if (is_null($maxY)) {
1073
			$maxY = $this->getMaxY();
1074
		}
1075
1076
		$previewPath = $this->getPreviewPath($fileId);
1077
		$previewPath = $previewPath . strval($maxX) . '-' . strval($maxY);
1078
		$isMaxPreview =
1079
			($maxX === $this->maxPreviewWidth && $maxY === $this->maxPreviewHeight) ? true : false;
1080
		if ($isMaxPreview) {
1081
			$previewPath .= '-max';
1082
		}
1083
		if ($this->keepAspect && !$isMaxPreview) {
1084
			$previewPath .= '-with-aspect';
1085
		}
1086
		if ($this->mode === self::MODE_COVER) {
1087
			$previewPath .= '-cover';
1088
		}
1089
		$previewPath .= '.png';
1090
1091
		return $previewPath;
1092
	}
1093
1094
	/**
1095
	 * Returns the path to the folder where the previews are stored, identified by the fileId
1096
	 *
1097
	 * @param int $fileId
1098
	 *
1099
	 * @return string
1100
	 */
1101
	private function getPreviewPath($fileId) {
1102
		return $this->getThumbnailsFolder() . '/' . $fileId . '/';
1103
	}
1104
1105
	/**
1106
	 * Asks the provider to send a preview of the file which respects the maximum dimensions
1107
	 * defined in the configuration and after saving it in the cache, it is then resized to the
1108
	 * asked dimensions
1109
	 *
1110
	 * This is only called once in order to generate a large PNG of dimensions defined in the
1111
	 * configuration file. We'll be able to quickly resize it later on.
1112
	 * We never upscale the original conversion as this will be done later by the resizing
1113
	 * operation
1114
	 *
1115
	 * @param int $fileId fileId of the original image
1116
	 */
1117
	private function generatePreview($fileId) {
1118
		$file = $this->getFile();
1119
		$preview = null;
1120
1121
		$previewProviders = \OC::$server->getPreviewManager()
1122
			->getProviders();
1123
		foreach ($previewProviders as $supportedMimeType => $providers) {
1124
			if (!preg_match($supportedMimeType, $this->mimeType)) {
1125
				continue;
1126
			}
1127
1128
			foreach ($providers as $closure) {
1129
				$provider = $closure();
1130
				if (!($provider instanceof \OCP\Preview\IProvider)) {
1131
					continue;
1132
				}
1133
1134
				\OCP\Util::writeLog(
1135
					'core', 'Generating preview for "' . $file . '" with "' . get_class($provider)
1136
					. '"', \OCP\Util::DEBUG
1137
				);
1138
1139
				/** @var $provider Provider */
1140
				$preview = $provider->getThumbnail(
1141
					$file, $this->configMaxWidth, $this->configMaxHeight, $scalingUp = false,
1142
					$this->fileView
1143
				);
1144
1145
				if (!($preview instanceof \OCP\IImage)) {
1146
					continue;
1147
				}
1148
1149
				$this->preview = $preview;
1150
				$previewPath = $this->getPreviewPath($fileId);
1151
1152
				if ($this->userView->is_dir($this->getThumbnailsFolder() . '/') === false) {
1153
					$this->userView->mkdir($this->getThumbnailsFolder() . '/');
1154
				}
1155
1156
				if ($this->userView->is_dir($previewPath) === false) {
1157
					$this->userView->mkdir($previewPath);
1158
				}
1159
1160
				// This stores our large preview so that it can be used in subsequent resizing requests
1161
				$this->storeMaxPreview($previewPath);
1162
1163
				break 2;
1164
			}
1165
		}
1166
1167
		// The providers have been kind enough to give us a preview
1168
		if ($preview) {
1169
			$this->resizeAndStore($fileId);
1170
		}
1171
	}
1172
1173
	/**
1174
	 * Defines the media icon, for the media type of the original file, as the preview
1175
	 */
1176
	private function getMimeIcon() {
1177
		$image = new \OC_Image();
1178
		$mimeIconWebPath = \OC::$server->getMimeTypeDetector()->mimeTypeIcon($this->mimeType);
1179
		if (empty(\OC::$WEBROOT)) {
1180
			$mimeIconServerPath = \OC::$SERVERROOT . $mimeIconWebPath;
1181
		} else {
1182
			$mimeIconServerPath = str_replace(\OC::$WEBROOT, \OC::$SERVERROOT, $mimeIconWebPath);
1183
		}
1184
		$image->loadFromFile($mimeIconServerPath);
1185
1186
		$this->preview = $image;
1187
	}
1188
1189
	/**
1190
	 * Stores the max preview in the cache
1191
	 *
1192
	 * @param string $previewPath path to the preview
1193
	 */
1194
	private function storeMaxPreview($previewPath) {
1195
		$maxPreviewExists = false;
1196
		$preview = $this->preview;
1197
1198
		$allThumbnails = $this->userView->getDirectoryContent($previewPath);
1199
		// This is so that the cache doesn't need emptying when upgrading
1200
		// Can be replaced by an upgrade script...
1201
		foreach ($allThumbnails as $thumbnail) {
1202
			$name = rtrim($thumbnail['name'], '.png');
1203
			if (strpos($name, 'max')) {
1204
				$maxPreviewExists = true;
1205
				break;
1206
			}
1207
		}
1208
		// We haven't found the max preview, so we create it
1209
		if (!$maxPreviewExists) {
1210
			$previewWidth = $preview->width();
1211
			$previewHeight = $preview->height();
1212
			$previewPath = $previewPath . strval($previewWidth) . '-' . strval($previewHeight);
1213
			$previewPath .= '-max.png';
1214
			$this->userView->file_put_contents($previewPath, $preview->data());
1215
			$this->maxPreviewWidth = $previewWidth;
1216
			$this->maxPreviewHeight = $previewHeight;
1217
		}
1218
	}
1219
1220
	/**
1221
	 * Limits a dimension to the maximum dimension provided as argument
1222
	 *
1223
	 * @param int $dim
1224
	 * @param int $maxDim
1225
	 * @param string $dimName
1226
	 *
1227
	 * @return integer
1228
	 */
1229
	private function limitMaxDim($dim, $maxDim, $dimName) {
1230
		if (!is_null($maxDim)) {
1231
			if ($dim > $maxDim) {
1232
				\OCP\Util::writeLog(
1233
					'core', $dimName . ' reduced from ' . $dim . ' to ' . $maxDim, \OCP\Util::DEBUG
1234
				);
1235
				$dim = $maxDim;
1236
			}
1237
		}
1238
1239
		return $dim;
1240
	}
1241
1242
	/**
1243
	 * @param array $args
1244
	 */
1245
	public static function post_write($args) {
1246
		self::post_delete($args, 'files/');
1247
	}
1248
1249
	/**
1250
	 * @param array $args
1251
	 */
1252
	public static function prepare_delete_files($args) {
1253
		self::prepare_delete($args, 'files/');
1254
	}
1255
1256
	/**
1257
	 * @param array $args
1258
	 * @param string $prefix
1259
	 */
1260
	public static function prepare_delete(array $args, $prefix = '') {
1261
		$path = $args['path'];
1262
		if (substr($path, 0, 1) === '/') {
1263
			$path = substr($path, 1);
1264
		}
1265
1266
		$view = new \OC\Files\View('/' . \OC_User::getUser() . '/' . $prefix);
1267
1268
		$absPath = Files\Filesystem::normalizePath($view->getAbsolutePath($path));
1269
		$fileInfo = $view->getFileInfo($path);
1270
		if ($fileInfo === false) {
1271
			return;
1272
		}
1273
		self::addPathToDeleteFileMapper($absPath, $fileInfo);
1274
		if ($view->is_dir($path)) {
1275
			$children = self::getAllChildren($view, $path);
1276
			self::$deleteChildrenMapper[$absPath] = $children;
1277
		}
1278
	}
1279
1280
	/**
1281
	 * @param string $absolutePath
1282
	 * @param \OCP\Files\FileInfo $info
1283
	 */
1284
	private static function addPathToDeleteFileMapper($absolutePath, $info) {
1285
		self::$deleteFileMapper[$absolutePath] = $info;
1286
	}
1287
1288
	/**
1289
	 * @param \OC\Files\View $view
1290
	 * @param string $path
1291
	 *
1292
	 * @return array
1293
	 */
1294
	private static function getAllChildren($view, $path) {
1295
		$children = $view->getDirectoryContent($path);
1296
		$childrensFiles = array();
1297
1298
		$fakeRootLength = strlen($view->getRoot());
1299
1300
		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...
1301
			$child = $children[$i];
1302
1303
			$childsPath = substr($child->getPath(), $fakeRootLength);
1304
1305
			if ($view->is_dir($childsPath)) {
1306
				$children = array_merge(
1307
					$children,
1308
					$view->getDirectoryContent($childsPath)
1309
				);
1310
			} else {
1311
				$childrensFiles[] = $child;
1312
			}
1313
		}
1314
1315
		return $childrensFiles;
1316
	}
1317
1318
	/**
1319
	 * @param array $args
1320
	 */
1321
	public static function post_delete_files($args) {
1322
		self::post_delete($args, 'files/');
1323
	}
1324
1325
	/**
1326
	 * @param array $args
1327
	 */
1328
	public static function post_delete_versions($args) {
1329
		self::post_delete($args, 'files/');
1330
	}
1331
1332
	/**
1333
	 * @param array $args
1334
	 * @param string $prefix
1335
	 */
1336
	public static function post_delete($args, $prefix = '') {
1337
		$path = Files\Filesystem::normalizePath($args['path']);
1338
1339
		$preview = new Preview(\OC_User::getUser(), $prefix, $path);
1340
		$preview->deleteAllPreviews();
1341
	}
1342
1343
}
1344