Completed
Push — stable8 ( 42720e...c45eda )
by
unknown
35s
created

Preview   F

Complexity

Total Complexity 148

Size/Duplication

Total Lines 977
Duplicated Lines 2.87 %

Coupling/Cohesion

Components 1
Dependencies 18

Importance

Changes 0
Metric Value
dl 28
loc 977
rs 1.263
c 0
b 0
f 0
wmc 148
lcom 1
cbo 18

42 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 31 5
A getFile() 0 3 1
A getMaxX() 0 3 1
A getMaxY() 0 3 1
A getScalingUp() 0 3 1
A getThumbnailsFolder() 0 3 1
A getMaxScaleFactor() 0 3 1
A getConfigMaxX() 0 3 1
A getConfigMaxY() 0 3 1
A getFileInfo() 0 10 3
A getChildren() 0 10 2
A setFile() 0 12 3
A setMimetype() 0 3 1
A setMaxX() 14 14 4
A setMaxY() 14 14 4
A setScalingup() 0 7 2
A setKeepAspect() 0 4 1
A isFileValid() 0 14 3
A deletePreview() 0 12 3
A deleteAllPreviews() 0 19 4
A isCached() 0 14 3
B isCachedBigger() 0 26 5
B getPossibleThumbnails() 0 30 5
A getDimensionsFromFilename() 0 7 1
B unscalable() 0 19 5
C getPreview() 0 74 15
A showPreview() 0 14 4
F resizeAndCrop() 0 110 20
B registerProvider() 0 37 3
B initProviders() 0 23 4
C registerCoreProviders() 0 67 14
A post_write() 0 3 1
A prepare_delete_files() 0 3 1
A prepare_delete() 0 15 3
A addPathToDeleteFileMapper() 0 3 1
A getAllChildren() 0 23 3
A post_delete_files() 0 3 1
A post_delete() 0 6 1
C isAvailable() 0 25 7
B isMimeSupported() 0 17 5
A buildCachePath() 0 13 2
A getPreviewPath() 0 3 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Preview often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Preview, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Copyright (c) 2013 Frank Karlitschek [email protected]
4
 * Copyright (c) 2013 Georg Ehrke [email protected]
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later.
7
 * See the COPYING-README file.
8
 *
9
 * Thumbnails:
10
 * structure of filename:
11
 * /data/user/thumbnails/pathhash/x-y.png
12
 *
13
 */
14
namespace OC;
15
16
use OC\Preview\Provider;
17
use OCP\Files\FileInfo;
18
use OCP\Files\NotFoundException;
19
20
class Preview {
21
	//the thumbnail folder
22
	const THUMBNAILS_FOLDER = 'thumbnails';
23
24
	//config
25
	private $maxScaleFactor;
26
	private $configMaxX;
27
	private $configMaxY;
28
29
	//fileview object
30
	private $fileView = null;
31
	private $userView = null;
32
33
	//vars
34
	private $file;
35
	private $maxX;
36
	private $maxY;
37
	private $scalingUp;
38
	private $mimeType;
39
	private $keepAspect = false;
40
41
	//filemapper used for deleting previews
42
	// index is path, value is fileinfo
43
	static public $deleteFileMapper = array();
44
	static public $deleteChildrenMapper = array();
45
46
	/**
47
	 * preview images object
48
	 *
49
	 * @var \OC_Image
50
	 */
51
	private $preview;
52
53
	//preview providers
54
	static private $providers = array();
55
	static private $registeredProviders = array();
56
	static private $enabledProviders = array();
57
58
	/**
59
	 * @var \OCP\Files\FileInfo
60
	 */
61
	protected $info;
62
63
	/**
64
	 * check if thumbnail or bigger version of thumbnail of file is cached
65
	 * @param string $user userid - if no user is given, OC_User::getUser will be used
66
	 * @param string $root path of root
67
	 * @param string $file The path to the file where you want a thumbnail from
68
	 * @param int $maxX The maximum X size of the thumbnail. It can be smaller depending on the shape of the image
69
	 * @param int $maxY The maximum Y size of the thumbnail. It can be smaller depending on the shape of the image
70
	 * @param bool $scalingUp Disable/Enable upscaling of previews
71
	 * @throws \Exception
72
	 * @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...
73
	 *                    false if thumbnail does not exist
74
	 *                    path to thumbnail if thumbnail exists
75
	 */
76
	public function __construct($user = '', $root = '/', $file = '', $maxX = 1, $maxY = 1, $scalingUp = true) {
77
		//init fileviews
78
		if ($user === '') {
79
			$user = \OC_User::getUser();
80
		}
81
		$this->fileView = new \OC\Files\View('/' . $user . '/' . $root);
82
		$this->userView = new \OC\Files\View('/' . $user);
83
84
		//set config
85
		$this->configMaxX = \OC_Config::getValue('preview_max_x', null);
86
		$this->configMaxY = \OC_Config::getValue('preview_max_y', null);
87
		$this->maxScaleFactor = \OC_Config::getValue('preview_max_scale_factor', 2);
88
89
		//save parameters
90
		$this->setFile($file);
91
		$this->setMaxX($maxX);
92
		$this->setMaxY($maxY);
93
		$this->setScalingUp($scalingUp);
94
95
		$this->preview = null;
96
97
		//check if there are preview backends
98
		if (empty(self::$providers)) {
99
			self::initProviders();
100
		}
101
102
		if (empty(self::$providers) && \OC::$server->getConfig()->getSystemValue('enable_previews', true)) {
103
			\OC_Log::write('core', 'No preview providers exist', \OC_Log::ERROR);
104
			throw new \Exception('No preview providers');
105
		}
106
	}
107
108
	/**
109
	 * returns the path of the file you want a thumbnail from
110
	 * @return string
111
	 */
112
	public function getFile() {
113
		return $this->file;
114
	}
115
116
	/**
117
	 * returns the max width of the preview
118
	 * @return integer
119
	 */
120
	public function getMaxX() {
121
		return $this->maxX;
122
	}
123
124
	/**
125
	 * returns the max height of the preview
126
	 * @return integer
127
	 */
128
	public function getMaxY() {
129
		return $this->maxY;
130
	}
131
132
	/**
133
	 * returns whether or not scalingup is enabled
134
	 * @return bool
135
	 */
136
	public function getScalingUp() {
137
		return $this->scalingUp;
138
	}
139
140
	/**
141
	 * returns the name of the thumbnailfolder
142
	 * @return string
143
	 */
144
	public function getThumbnailsFolder() {
145
		return self::THUMBNAILS_FOLDER;
146
	}
147
148
	/**
149
	 * returns the max scale factor
150
	 * @return string
151
	 */
152
	public function getMaxScaleFactor() {
153
		return $this->maxScaleFactor;
154
	}
155
156
	/**
157
	 * returns the max width set in ownCloud's config
158
	 * @return string
159
	 */
160
	public function getConfigMaxX() {
161
		return $this->configMaxX;
162
	}
163
164
	/**
165
	 * returns the max height set in ownCloud's config
166
	 * @return string
167
	 */
168
	public function getConfigMaxY() {
169
		return $this->configMaxY;
170
	}
171
172
	/**
173
	 * @return false|Files\FileInfo|\OCP\Files\FileInfo
174
	 */
175
	protected function getFileInfo() {
176
		$absPath = $this->fileView->getAbsolutePath($this->file);
177
		$absPath = Files\Filesystem::normalizePath($absPath);
178
		if(array_key_exists($absPath, self::$deleteFileMapper)) {
179
			$this->info = self::$deleteFileMapper[$absPath];
180
		} else if (!$this->info) {
181
			$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 array or 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...
182
		}
183
		return $this->info;
184
	}
185
186
187
	/**
188
	 * @return array|null
189
	 */
190
	private function getChildren() {
191
		$absPath = $this->fileView->getAbsolutePath($this->file);
192
		$absPath = Files\Filesystem::normalizePath($absPath);
193
194
		if (array_key_exists($absPath, self::$deleteChildrenMapper)) {
195
			return self::$deleteChildrenMapper[$absPath];
196
		}
197
198
		return null;
199
	}
200
201
	/**
202
	 * set the path of the file you want a thumbnail from
203
	 * @param string $file
204
	 * @return $this
205
	 */
206
	public function setFile($file) {
207
		$this->file = $file;
208
		$this->info = null;
209
210
		if ($file !== '') {
211
			$this->getFileInfo();
212
			if($this->info instanceof \OCP\Files\FileInfo) {
213
				$this->mimeType = $this->info->getMimetype();
214
			}
215
		}
216
		return $this;
217
	}
218
219
	/**
220
	 * set mime type explicitly
221
	 * @param string $mimeType
222
	 */
223
	public function setMimetype($mimeType) {
224
		$this->mimeType = $mimeType;
225
	}
226
227
	/**
228
	 * set the the max width of the preview
229
	 * @param int $maxX
230
	 * @throws \Exception
231
	 * @return \OC\Preview $this
232
	 */
233 View Code Duplication
	public function setMaxX($maxX = 1) {
234
		if ($maxX <= 0) {
235
			throw new \Exception('Cannot set width of 0 or smaller!');
236
		}
237
		$configMaxX = $this->getConfigMaxX();
238
		if (!is_null($configMaxX)) {
239
			if ($maxX > $configMaxX) {
240
				\OC_Log::write('core', 'maxX reduced from ' . $maxX . ' to ' . $configMaxX, \OC_Log::DEBUG);
241
				$maxX = $configMaxX;
242
			}
243
		}
244
		$this->maxX = $maxX;
245
		return $this;
246
	}
247
248
	/**
249
	 * set the the max height of the preview
250
	 * @param int $maxY
251
	 * @throws \Exception
252
	 * @return \OC\Preview $this
253
	 */
254 View Code Duplication
	public function setMaxY($maxY = 1) {
255
		if ($maxY <= 0) {
256
			throw new \Exception('Cannot set height of 0 or smaller!');
257
		}
258
		$configMaxY = $this->getConfigMaxY();
259
		if (!is_null($configMaxY)) {
260
			if ($maxY > $configMaxY) {
261
				\OC_Log::write('core', 'maxX reduced from ' . $maxY . ' to ' . $configMaxY, \OC_Log::DEBUG);
262
				$maxY = $configMaxY;
263
			}
264
		}
265
		$this->maxY = $maxY;
266
		return $this;
267
	}
268
269
	/**
270
	 * set whether or not scalingup is enabled
271
	 * @param bool $scalingUp
272
	 * @return \OC\Preview $this
273
	 */
274
	public function setScalingup($scalingUp) {
275
		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...
276
			$scalingUp = false;
277
		}
278
		$this->scalingUp = $scalingUp;
279
		return $this;
280
	}
281
282
	/**
283
	 * @param bool $keepAspect
284
	 * @return $this
285
	 */
286
	public function setKeepAspect($keepAspect) {
287
		$this->keepAspect = $keepAspect;
288
		return $this;
289
	}
290
291
	/**
292
	 * check if all parameters are valid
293
	 * @return bool
294
	 */
295
	public function isFileValid() {
296
		$file = $this->getFile();
297
		if ($file === '') {
298
			\OC_Log::write('core', 'No filename passed', \OC_Log::DEBUG);
299
			return false;
300
		}
301
302
		if (!$this->fileView->file_exists($file)) {
303
			\OC_Log::write('core', 'File:"' . $file . '" not found', \OC_Log::DEBUG);
304
			return false;
305
		}
306
307
		return true;
308
	}
309
310
	/**
311
	 * deletes previews of a file with specific x and y
312
	 * @return bool
313
	 */
314
	public function deletePreview() {
315
		$file = $this->getFile();
316
317
		$fileInfo = $this->getFileInfo($file);
0 ignored issues
show
Unused Code introduced by
The call to Preview::getFileInfo() has too many arguments starting with $file.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
318
		if($fileInfo !== null && $fileInfo !== false) {
319
			$fileId = $fileInfo->getId();
320
321
			$previewPath = $this->buildCachePath($fileId);
322
			return $this->userView->unlink($previewPath);
323
		}
324
		return false;
325
	}
326
327
	/**
328
	 * deletes all previews of a file
329
	 */
330
	public function deleteAllPreviews() {
331
		$toDelete = $this->getChildren();
332
		$toDelete[] = $this->getFileInfo();
333
334
		foreach ($toDelete as $delete) {
335
			if ($delete instanceof FileInfo) {
336
				/** @var \OCP\Files\FileInfo $delete */
337
				$fileId = $delete->getId();
338
339
				// getId() might return null, e.g. when the file is a
340
				// .ocTransferId*.part file from chunked file upload.
341
				if (!empty($fileId)) {
342
					$previewPath = $this->getPreviewPath($fileId);
343
					$this->userView->deleteAll($previewPath);
344
					$this->userView->rmdir($previewPath);
345
				}
346
			}
347
		}
348
	}
349
350
	/**
351
	 * check if thumbnail or bigger version of thumbnail of file is cached
352
	 * @param int $fileId fileId of the original image
353
	 * @return string|false path to thumbnail if it exists or false
354
	 */
355
	public function isCached($fileId) {
356
		if (is_null($fileId)) {
357
			return false;
358
		}
359
360
		$preview = $this->buildCachePath($fileId);
361
362
		//does a preview with the wanted height and width already exist?
363
		if ($this->userView->file_exists($preview)) {
364
			return $preview;
365
		}
366
367
		return $this->isCachedBigger($fileId);
368
	}
369
370
	/**
371
	 * check if a bigger version of thumbnail of file is cached
372
	 * @param int $fileId fileId of the original image
373
	 * @return string|false path to bigger thumbnail if it exists or false
374
	*/
375
	private function isCachedBigger($fileId) {
376
377
		if (is_null($fileId)) {
378
			return false;
379
		}
380
381
		// in order to not loose quality we better generate aspect preserving previews from the original file
382
		if ($this->keepAspect) {
383
			return false;
384
		}
385
386
		$maxX = $this->getMaxX();
387
388
		//array for usable cached thumbnails
389
		$possibleThumbnails = $this->getPossibleThumbnails($fileId);
390
391
		foreach ($possibleThumbnails as $width => $path) {
392
			if ($width < $maxX) {
393
				continue;
394
			} else {
395
				return $path;
396
			}
397
		}
398
399
		return false;
400
	}
401
402
	/**
403
	 * get possible bigger thumbnails of the given image
404
	 * @param int $fileId fileId of the original image
405
	 * @return array an array of paths to bigger thumbnails
406
	*/
407
	private function getPossibleThumbnails($fileId) {
408
409
		if (is_null($fileId)) {
410
			return array();
411
		}
412
413
		$previewPath = $this->getPreviewPath($fileId);
414
415
		$wantedAspectRatio = (float) ($this->getMaxX() / $this->getMaxY());
416
417
		//array for usable cached thumbnails
418
		$possibleThumbnails = array();
419
420
		$allThumbnails = $this->userView->getDirectoryContent($previewPath);
421
		foreach ($allThumbnails as $thumbnail) {
422
			$name = rtrim($thumbnail['name'], '.png');
423
			list($x, $y, $aspectRatio) = $this->getDimensionsFromFilename($name);
424
425
			if (abs($aspectRatio - $wantedAspectRatio) >= 0.000001
426
				|| $this->unscalable($x, $y)
427
			) {
428
				continue;
429
			}
430
			$possibleThumbnails[$x] = $thumbnail['path'];
431
		}
432
433
		ksort($possibleThumbnails);
434
435
		return $possibleThumbnails;
436
	}
437
438
	/**
439
	 * @param string $name
440
	 * @return array
441
	 */
442
	private function getDimensionsFromFilename($name) {
443
			$size = explode('-', $name);
444
			$x = (int) $size[0];
445
			$y = (int) $size[1];
446
			$aspectRatio = (float) ($x / $y);
447
			return array($x, $y, $aspectRatio);
448
	}
449
450
	/**
451
	 * @param int $x
452
	 * @param int $y
453
	 * @return bool
454
	 */
455
	private function unscalable($x, $y) {
456
457
		$maxX = $this->getMaxX();
458
		$maxY = $this->getMaxY();
459
		$scalingUp = $this->getScalingUp();
460
		$maxScaleFactor = $this->getMaxScaleFactor();
461
462
		if ($x < $maxX || $y < $maxY) {
463
			if ($scalingUp) {
464
				$scalefactor = $maxX / $x;
465
				if ($scalefactor > $maxScaleFactor) {
466
					return true;
467
				}
468
			} else {
469
				return true;
470
			}
471
		}
472
		return false;
473
	}
474
475
	/**
476
	 * return a preview of a file
477
	 * @return \OC_Image
478
	 */
479
	public function getPreview() {
480
		if (!is_null($this->preview) && $this->preview->valid()) {
481
			return $this->preview;
482
		}
483
484
		$this->preview = null;
485
		$file = $this->getFile();
486
		$maxX = $this->getMaxX();
487
		$maxY = $this->getMaxY();
488
		$scalingUp = $this->getScalingUp();
489
490
		$fileInfo = $this->getFileInfo($file);
0 ignored issues
show
Unused Code introduced by
The call to Preview::getFileInfo() has too many arguments starting with $file.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
491
		if($fileInfo === null || $fileInfo === false) {
492
			return new \OC_Image();
493
		}
494
		$fileId = $fileInfo->getId();
495
496
		$cached = $this->isCached($fileId);
497
		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...
498
			$stream = $this->userView->fopen($cached, 'r');
499
			$this->preview = null;
500
			if ($stream) {
501
				$image = new \OC_Image();
502
				$image->loadFromFileHandle($stream);
0 ignored issues
show
Documentation introduced by
$stream is of type string, but the function expects a resource.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
503
				$this->preview = $image->valid() ? $image : null;
504
505
				$this->resizeAndCrop();
506
				fclose($stream);
507
			}
508
		}
509
510
		if (is_null($this->preview)) {
511
			$preview = null;
0 ignored issues
show
Unused Code introduced by
$preview is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
512
513
			foreach (self::$providers as $supportedMimeType => $provider) {
514
				if (!preg_match($supportedMimeType, $this->mimeType)) {
515
					continue;
516
				}
517
518
				\OC_Log::write('core', 'Generating preview for "' . $file . '" with "' . get_class($provider) . '"', \OC_Log::DEBUG);
519
520
				/** @var $provider Provider */
521
				$preview = $provider->getThumbnail($file, $maxX, $maxY, $scalingUp, $this->fileView);
522
523
				if (!($preview instanceof \OC_Image)) {
524
					continue;
525
				}
526
527
				$this->preview = $preview;
528
				$this->resizeAndCrop();
529
530
				$previewPath = $this->getPreviewPath($fileId);
531
				$cachePath = $this->buildCachePath($fileId);
532
533
				if ($this->userView->is_dir($this->getThumbnailsFolder() . '/') === false) {
534
					$this->userView->mkdir($this->getThumbnailsFolder() . '/');
535
				}
536
537
				if ($this->userView->is_dir($previewPath) === false) {
538
					$this->userView->mkdir($previewPath);
539
				}
540
541
				$this->userView->file_put_contents($cachePath, $preview->data());
542
543
				break;
544
			}
545
		}
546
547
		if (is_null($this->preview)) {
548
			$this->preview = new \OC_Image();
549
		}
550
551
		return $this->preview;
552
	}
553
554
	/**
555
	 * @param null|string $mimeType
556
	 * @throws NotFoundException
557
	 */
558
	public function showPreview($mimeType = null) {
559
		// Check if file is valid
560
		if($this->isFileValid() === false) {
561
			throw new NotFoundException('File not found.');
562
		}
563
564
		\OCP\Response::enableCaching(3600 * 24); // 24 hours
565
		if (is_null($this->preview)) {
566
			$this->getPreview();
567
		}
568
		if ($this->preview instanceof \OC_Image) {
569
			$this->preview->show($mimeType);
570
		}
571
	}
572
573
	/**
574
	 * resize, crop and fix orientation
575
	 * @return void
576
	 */
577
	private function resizeAndCrop() {
578
		$image = $this->preview;
579
		$x = $this->getMaxX();
580
		$y = $this->getMaxY();
581
		$scalingUp = $this->getScalingUp();
582
		$maxScaleFactor = $this->getMaxScaleFactor();
583
584
		if (!($image instanceof \OC_Image)) {
585
			\OC_Log::write('core', '$this->preview is not an instance of OC_Image', \OC_Log::DEBUG);
586
			return;
587
		}
588
589
		$realX = (int)$image->width();
590
		$realY = (int)$image->height();
591
592
		// compute $maxY and $maxX using the aspect of the generated preview
593
		if ($this->keepAspect) {
594
			$ratio = $realX / $realY;
595
			if($x / $ratio < $y) {
596
				// width restricted
597
				$y = $x / $ratio;
598
			} else {
599
				$x = $y * $ratio;
600
			}
601
		}
602
603
		if ($x === $realX && $y === $realY) {
604
			$this->preview = $image;
605
			return;
606
		}
607
608
		$factorX = $x / $realX;
609
		$factorY = $y / $realY;
610
611
		if ($factorX >= $factorY) {
612
			$factor = $factorX;
613
		} else {
614
			$factor = $factorY;
615
		}
616
617
		if ($scalingUp === false) {
618
			if ($factor > 1) {
619
				$factor = 1;
620
			}
621
		}
622
623
		if (!is_null($maxScaleFactor)) {
624
			if ($factor > $maxScaleFactor) {
625
				\OC_Log::write('core', 'scale factor reduced from ' . $factor . ' to ' . $maxScaleFactor, \OC_Log::DEBUG);
626
				$factor = $maxScaleFactor;
627
			}
628
		}
629
630
		$newXSize = (int)($realX * $factor);
631
		$newYSize = (int)($realY * $factor);
632
633
		$image->preciseResize($newXSize, $newYSize);
634
635
		if ($newXSize === $x && $newYSize === $y) {
636
			$this->preview = $image;
637
			return;
638
		}
639
640
		if ($newXSize >= $x && $newYSize >= $y) {
641
			$cropX = floor(abs($x - $newXSize) * 0.5);
642
			//don't crop previews on the Y axis, this sucks if it's a document.
643
			//$cropY = floor(abs($y - $newYsize) * 0.5);
644
			$cropY = 0;
645
646
			$image->crop($cropX, $cropY, $x, $y);
647
648
			$this->preview = $image;
649
			return;
650
		}
651
652
		if (($newXSize < $x || $newYSize < $y) && $scalingUp) {
653
			if ($newXSize > $x) {
654
				$cropX = floor(($newXSize - $x) * 0.5);
655
				$image->crop($cropX, 0, $x, $newYSize);
656
			}
657
658
			if ($newYSize > $y) {
659
				$cropY = floor(($newYSize - $y) * 0.5);
660
				$image->crop(0, $cropY, $newXSize, $y);
661
			}
662
663
			$newXSize = (int)$image->width();
664
			$newYSize = (int)$image->height();
665
666
			//create transparent background layer
667
			$backgroundLayer = imagecreatetruecolor($x, $y);
668
			$white = imagecolorallocate($backgroundLayer, 255, 255, 255);
669
			imagefill($backgroundLayer, 0, 0, $white);
670
671
			$image = $image->resource();
672
673
			$mergeX = floor(abs($x - $newXSize) * 0.5);
674
			$mergeY = floor(abs($y - $newYSize) * 0.5);
675
676
			imagecopy($backgroundLayer, $image, $mergeX, $mergeY, 0, 0, $newXSize, $newYSize);
677
678
			//$black = imagecolorallocate(0,0,0);
679
			//imagecolortransparent($transparentlayer, $black);
680
681
			$image = new \OC_Image($backgroundLayer);
682
683
			$this->preview = $image;
684
			return;
685
		}
686
	}
687
688
	/**
689
	 * register a new preview provider to be used
690
	 * @param string $class
691
	 * @param array $options
692
	 */
693
	public static function registerProvider($class, $options = array()) {
694
		/**
695
		 * Only register providers that have been explicitly enabled
696
		 *
697
		 * The following providers are enabled by default:
698
		 *  - OC\Preview\Image
699
		 *  - OC\Preview\MP3
700
		 *  - OC\Preview\TXT
701
		 *  - OC\Preview\MarkDown
702
		 *
703
		 * The following providers are disabled by default due to performance or privacy concerns:
704
		 *  - OC\Preview\MSOfficeDoc
705
		 *  - OC\Preview\MSOffice2003
706
		 *  - OC\Preview\MSOffice2007
707
		 *  - OC\Preview\OpenDocument
708
		 *  - OC\Preview\StarOffice
709
 		 *  - OC\Preview\SVG
710
		 *  - OC\Preview\Movie
711
		 *  - OC\Preview\PDF
712
		 *  - OC\Preview\TIFF
713
		 *  - OC\Preview\Illustrator
714
		 *  - OC\Preview\Postscript
715
		 *  - OC\Preview\Photoshop
716
		 */
717
		if(empty(self::$enabledProviders)) {
718
			self::$enabledProviders = \OC::$server->getConfig()->getSystemValue('enabledPreviewProviders', array(
0 ignored issues
show
Documentation Bug introduced by
It seems like \OC::$server->getConfig(...C\\Preview\\MarkDown')) of type * is incompatible with the declared type array of property $enabledProviders.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
719
				'OC\Preview\Image',
720
				'OC\Preview\MP3',
721
				'OC\Preview\TXT',
722
				'OC\Preview\MarkDown',
723
			));
724
		}
725
726
		if(in_array($class, self::$enabledProviders)) {
727
			self::$registeredProviders[] = array('class' => $class, 'options' => $options);
728
		}
729
	}
730
731
	/**
732
	 * create instances of all the registered preview providers
733
	 * @return void
734
	 */
735
	private static function initProviders() {
736
		if (!\OC::$server->getConfig()->getSystemValue('enable_previews', true)) {
737
			self::$providers = array();
738
			return;
739
		}
740
741
		if (!empty(self::$providers)) {
742
			return;
743
		}
744
745
		self::registerCoreProviders();
746
		foreach (self::$registeredProviders as $provider) {
747
			$class = $provider['class'];
748
			$options = $provider['options'];
749
750
			/** @var $object Provider */
751
			$object = new $class($options);
752
			self::$providers[$object->getMimeType()] = $object;
753
		}
754
755
		$keys = array_map('strlen', array_keys(self::$providers));
756
		array_multisort($keys, SORT_DESC, self::$providers);
757
	}
758
759
	protected static function registerCoreProviders() {
760
		self::registerProvider('OC\Preview\TXT');
761
		self::registerProvider('OC\Preview\MarkDown');
762
		self::registerProvider('OC\Preview\Image');
763
		self::registerProvider('OC\Preview\MP3');
764
765
		// SVG, Office and Bitmap require imagick
766
		if (extension_loaded('imagick')) {
767
			$checkImagick = new \Imagick();
768
769
			$imagickProviders = array(
770
				'SVG'	=> 'OC\Preview\SVG',
771
				'TIFF'	=> 'OC\Preview\TIFF',
772
				'PDF'	=> 'OC\Preview\PDF',
773
				'AI'	=> 'OC\Preview\Illustrator',
774
				'PSD'	=> 'OC\Preview\Photoshop',
775
				// Requires adding 'eps' => array('application/postscript', null), to lib/private/mimetypes.list.php
776
				'EPS'	=> 'OC\Preview\Postscript',
777
			);
778
779
			foreach ($imagickProviders as $queryFormat => $provider) {
780
				if (count($checkImagick->queryFormats($queryFormat)) === 1) {
781
					self::registerProvider($provider);
782
				}
783
			}
784
785
			if (count($checkImagick->queryFormats('PDF')) === 1) {
786
				// Office previews are currently not supported on Windows
787
				if (!\OC_Util::runningOnWindows() && \OC_Helper::is_function_enabled('shell_exec')) {
788
					$officeFound = is_string(\OC::$server->getConfig()->getSystemValue('preview_libreoffice_path', null));
789
790
					if (!$officeFound) {
791
						//let's see if there is libreoffice or openoffice on this machine
792
						$whichLibreOffice = shell_exec('command -v libreoffice');
793
						$officeFound = !empty($whichLibreOffice);
794
						if (!$officeFound) {
795
							$whichOpenOffice = shell_exec('command -v openoffice');
796
							$officeFound = !empty($whichOpenOffice);
797
						}
798
					}
799
800
					if ($officeFound) {
801
						self::registerProvider('OC\Preview\MSOfficeDoc');
802
						self::registerProvider('OC\Preview\MSOffice2003');
803
						self::registerProvider('OC\Preview\MSOffice2007');
804
						self::registerProvider('OC\Preview\OpenDocument');
805
						self::registerProvider('OC\Preview\StarOffice');
806
					}
807
				}
808
			}
809
		}
810
811
		// Video requires avconv or ffmpeg and is therefor
812
		// currently not supported on Windows.
813
		if (!\OC_Util::runningOnWindows()) {
814
			$avconvBinary = \OC_Helper::findBinaryPath('avconv');
815
			$ffmpegBinary = ($avconvBinary) ? null : \OC_Helper::findBinaryPath('ffmpeg');
816
817
			if ($avconvBinary || $ffmpegBinary) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $avconvBinary of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null 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...
Bug Best Practice introduced by
The expression $ffmpegBinary of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null 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...
818
				// FIXME // a bit hacky but didn't want to use subclasses
819
				\OC\Preview\Movie::$avconvBinary = $avconvBinary;
820
				\OC\Preview\Movie::$ffmpegBinary = $ffmpegBinary;
821
822
				self::registerProvider('OC\Preview\Movie');
823
			}
824
		}
825
	}
826
827
	/**
828
	 * @param array $args
829
	 */
830
	public static function post_write($args) {
831
		self::post_delete($args, 'files/');
832
	}
833
834
	/**
835
	 * @param array $args
836
	 */
837
	public static function prepare_delete_files($args) {
838
		self::prepare_delete($args, 'files/');
839
	}
840
841
	/**
842
	 * @param array $args
843
	 * @param string $prefix
844
	 */
845
	public static function prepare_delete($args, $prefix='') {
846
		$path = $args['path'];
847
		if (substr($path, 0, 1) === '/') {
848
			$path = substr($path, 1);
849
		}
850
851
		$view = new \OC\Files\View('/' . \OC_User::getUser() . '/' . $prefix);
852
853
		$absPath = Files\Filesystem::normalizePath($view->getAbsolutePath($path));
854
		self::addPathToDeleteFileMapper($absPath, $view->getFileInfo($path));
855
		if ($view->is_dir($path)) {
856
			$children = self::getAllChildren($view, $path);
857
			self::$deleteChildrenMapper[$absPath] = $children;
858
		}
859
	}
860
861
	/**
862
	 * @param string $absolutePath
863
	 * @param \OCP\Files\FileInfo $info
864
	 */
865
	private static function addPathToDeleteFileMapper($absolutePath, $info) {
866
		self::$deleteFileMapper[$absolutePath] = $info;
867
	}
868
869
	/**
870
	 * @param \OC\Files\View $view
871
	 * @param string $path
872
	 * @return array
873
	 */
874
	private static function getAllChildren($view, $path) {
875
		$children = $view->getDirectoryContent($path);
876
		$childrensFiles = array();
877
878
		$fakeRootLength = strlen($view->getRoot());
879
880
		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...
881
			$child = $children[$i];
882
883
			$childsPath = substr($child->getPath(), $fakeRootLength);
884
885
			if ($view->is_dir($childsPath)) {
886
				$children = array_merge(
887
					$children,
888
					$view->getDirectoryContent($childsPath)
889
				);
890
			} else {
891
				$childrensFiles[] = $child;
892
			}
893
		}
894
895
		return $childrensFiles;
896
	}
897
898
	/**
899
	 * @param array $args
900
	 */
901
	public static function post_delete_files($args) {
902
		self::post_delete($args, 'files/');
903
	}
904
905
	/**
906
	 * @param array $args
907
	 * @param string $prefix
908
	 */
909
	public static function post_delete($args, $prefix='') {
910
		$path = Files\Filesystem::normalizePath($args['path']);
911
912
		$preview = new Preview(\OC_User::getUser(), $prefix, $path);
913
		$preview->deleteAllPreviews();
914
	}
915
916
	/**
917
	 * Check if a preview can be generated for a file
918
	 *
919
	 * @param \OC\Files\FileInfo $file
920
	 * @return bool
921
	 */
922
	public static function isAvailable(\OC\Files\FileInfo $file) {
923
		if (!\OC_Config::getValue('enable_previews', true)) {
924
			return false;
925
		}
926
927
		$mount = $file->getMountPoint();
928
		if ($mount and !$mount->getOption('previews', true)){
929
			return false;
930
		}
931
932
		//check if there are preview backends
933
		if (empty(self::$providers)) {
934
			self::initProviders();
935
		}
936
937
		foreach (self::$providers as $supportedMimeType => $provider) {
938
			/**
939
			 * @var \OC\Preview\Provider $provider
940
			 */
941
			if (preg_match($supportedMimeType, $file->getMimetype())) {
942
				return $provider->isAvailable($file);
943
			}
944
		}
945
		return false;
946
	}
947
948
	/**
949
	 * @param string $mimeType
950
	 * @return bool
951
	 */
952
	public static function isMimeSupported($mimeType) {
953
		if (!\OC_Config::getValue('enable_previews', true)) {
954
			return false;
955
		}
956
957
		//check if there are preview backends
958
		if (empty(self::$providers)) {
959
			self::initProviders();
960
		}
961
962
		foreach(self::$providers as $supportedMimetype => $provider) {
963
			if(preg_match($supportedMimetype, $mimeType)) {
964
				return true;
965
			}
966
		}
967
		return false;
968
	}
969
970
	/**
971
	 * @param int $fileId
972
	 * @return string
973
	 */
974
	private function buildCachePath($fileId) {
975
		$maxX = $this->getMaxX();
976
		$maxY = $this->getMaxY();
977
978
		$previewPath = $this->getPreviewPath($fileId);
979
		$preview = $previewPath . strval($maxX) . '-' . strval($maxY);
980
		if ($this->keepAspect) {
981
			$preview .= '-with-aspect';
982
		}
983
		$preview .= '.png';
984
985
		return $preview;
986
	}
987
988
989
	/**
990
	 * @param int $fileId
991
	 * @return string
992
	 */
993
	private function getPreviewPath($fileId) {
994
		return $this->getThumbnailsFolder() . '/' . $fileId . '/';
995
	}
996
}
997