Completed
Push — master ( 9b0aba...0a2150 )
by Aimeos
15:35
created

Standard::scale()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
c 0
b 0
f 0
cc 3
eloc 14
nc 4
nop 2
rs 9.2
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2015-2016
6
 * @package Controller
7
 * @subpackage Common
8
 */
9
10
11
namespace Aimeos\Controller\Common\Media;
12
13
14
/**
15
 * Common media controller methods
16
 *
17
 * @package Controller
18
 * @subpackage Common
19
 */
20
class Standard
21
	implements \Aimeos\Controller\Common\Media\Iface
22
{
23
	private $context;
24
25
26
	/**
27
	 * Initializes the object
28
	 *
29
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context object
30
	 */
31
	public function __construct( \Aimeos\MShop\Context\Item\Iface $context )
32
	{
33
		$this->context = $context;
34
	}
35
36
37
	/**
38
	 * Stores the uploaded file and adds the references to the media item
39
	 *
40
	 * {inheritDoc}
41
	 *
42
	 * @param \Aimeos\MShop\Media\Item\Iface $item Media item to add the file references to
43
	 * @param \Psr\Http\Message\UploadedFileInterface Uploaded file
44
	 * @param string $fsname Name of the file system to store the files at
45
	 */
46
	public function add( \Aimeos\MShop\Media\Item\Iface $item, \Psr\Http\Message\UploadedFileInterface $file, $fsname = 'fs-media' )
47
	{
48
		$this->checkFileUpload( $file );
49
		$media = $this->getMediaFile( $file->getStream() );
50
51
		if( $media instanceof \Aimeos\MW\Media\Image\Iface )
52
		{
53
			$this->scaleImage( $media, 'files' );
54
			$mimetype = $this->getMimeType( $media, 'files' );
55
			$filepath = $this->getFilePath( $file->getClientFilename(), 'files', $mimetype );
56
			$this->storeFile( $media->save( null, $mimetype ), $fsname, $filepath, $item->getUrl() );
57
			$item->setUrl( $filepath );
58
59
			$this->scaleImage( $media, 'preview' );
60
			$mimeprev = $this->getMimeType( $media, 'preview' );
61
			$filepath = $this->getFilePath( $file->getClientFilename(), 'preview', $mimeprev );
62
			$this->storeFile( $media->save( null, $mimetype ), $fsname, $filepath, $item->getPreview() );
63
			$item->setPreview( $filepath );
64
		}
65
		else
66
		{
67
			$mimetype = $media->getMimeType();
68
			$item->setPreview( $this->getMimeIcon( $mimetype ) );
69
70
			$filepath = $this->getFilePath( $file->getClientFilename(), 'files', $mimetype );
71
			$this->storeFile( $media->save(), $fsname, $filepath, $item->getPreview() );
72
			$item->setUrl( $filepath );
73
		}
74
75
		$item->setLabel( basename( $file->getClientFilename() ) );
76
		$item->setMimeType( $mimetype );
77
	}
78
79
80
	/**
81
	 * Deletes the files of the media item
82
	 *
83
	 * {inheritDoc}
84
	 *
85
	 * @param \Aimeos\MShop\Media\Item\Iface $item Media item whose files should be deleted
86
	 * @param string $fsname Name of the file system to delete the files from
87
	 */
88
	public function delete( \Aimeos\MShop\Media\Item\Iface $item, $fsname = 'fs-media' )
89
	{
90
		$fs = $this->context->getFilesystemManager()->get( $fsname );
91
92
		$path = $item->getUrl();
93
		if( $path !== '' && $fs->has( $path ) ) {
94
			$fs->rm( $path );
95
		}
96
97
		$item->setUrl( '' );
98
99
		try
100
		{
101
			$path = $item->getPreview();
102
			if( $path !== '' && $fs->has( $path ) ) {
103
				$fs->rm( $path );
104
			}
105
		}
106
		catch( \Exception $e ) { ; } // Can be a mime icon with relative path
107
108
		$item->setPreview( '' );
109
	}
110
111
112
	/**
113
	 * Rescales the files (original and preview) referenced by the media item
114
	 *
115
	 * The height/width configuration for scaling and which one should be scaled is used from
116
	 * - controller/common/media/standard/<files|preview>/maxheight
117
	 * - controller/common/media/standard/<files|preview>/maxwidth
118
	 * - controller/common/media/standard/<files|preview>/scale
119
	 *
120
	 * @param \Aimeos\MShop\Media\Item\Iface $item Media item whose files should be scaled
121
	 * @param string $fsname Name of the file system to rescale the files from
122
	 * @return void
123
	 */
124
	public function scale( \Aimeos\MShop\Media\Item\Iface $item, $fsname = 'fs-media' )
125
	{
126
		$path = $item->getUrl();
127
		$config = $this->context->getConfig();
128
		$media = $this->getMediaFile( $this->getFileContent( $path, $fsname ) );
129
130
		if( (bool) $config->get( 'controller/common/media/standard/files/scale', false ) === true )
131
		{
132
			$mimetype = $this->getMimeType( $media, 'files' );
0 ignored issues
show
Compatibility introduced by
$media of type object<Aimeos\MW\Media\Iface> is not a sub-type of object<Aimeos\MW\Media\Image\Iface>. It seems like you assume a child interface of the interface Aimeos\MW\Media\Iface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
133
			$filepath = $this->getFilePath( $path, 'files', $mimetype );
134
			$this->storeFile( $media->save( null, $mimetype ), $fsname, $filepath, $path );
135
			$item->setUrl( $filepath );
136
		}
137
138
		if( (bool) $config->get( 'controller/common/media/standard/preview/scale', true ) === true )
139
		{
140
			$mimetype = $this->getMimeType( $media, 'preview' );
0 ignored issues
show
Compatibility introduced by
$media of type object<Aimeos\MW\Media\Iface> is not a sub-type of object<Aimeos\MW\Media\Image\Iface>. It seems like you assume a child interface of the interface Aimeos\MW\Media\Iface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
141
			$filepath = $this->getFilePath( $path, 'preview', $mimetype );
142
			$this->storeFile( $media->save( null, $mimetype ), $fsname, $filepath, $item->getPreview() );
143
			$item->setPreview( $filepath );
144
		}
145
	}
146
147
148
	/**
149
	 * Checks if an error during upload occured
150
	 *
151
	 * @param \Psr\Http\Message\UploadedFileInterface $file Uploaded file
152
	 * @throws \Aimeos\Controller\Common\Exception If an error occured during upload
153
	 */
154
	protected function checkFileUpload( \Psr\Http\Message\UploadedFileInterface $file )
155
	{
156
		if( $file->getError() !== UPLOAD_ERR_OK )
157
		{
158
			switch( $file->getError() )
159
			{
160
				case UPLOAD_ERR_INI_SIZE:
161
				case UPLOAD_ERR_FORM_SIZE:
162
					throw new \Aimeos\Controller\Common\Exception( 'The uploaded file exceeds the max. allowed filesize' );
163
				case UPLOAD_ERR_PARTIAL:
164
					throw new \Aimeos\Controller\Common\Exception( 'The uploaded file was only partially uploaded' );
165
				case UPLOAD_ERR_NO_FILE:
166
					throw new \Aimeos\Controller\Common\Exception( 'No file was uploaded' );
167
				case UPLOAD_ERR_NO_TMP_DIR:
168
					throw new \Aimeos\Controller\Common\Exception( 'Temporary folder is missing' );
169
				case UPLOAD_ERR_CANT_WRITE:
170
					throw new \Aimeos\Controller\Common\Exception( 'Failed to write file to disk' );
171
				case UPLOAD_ERR_EXTENSION:
172
					throw new \Aimeos\Controller\Common\Exception( 'File upload stopped by extension' );
173
				default:
174
					throw new \Aimeos\Controller\Common\Exception( 'Unknown upload error' );
175
			}
176
		}
177
	}
178
179
180
	/**
181
	 * Returns the file content of the file or URL
182
	 *
183
	 * @param string $path Path to the file or URL
184
	 * @param string $fsname File system name the file is located at
185
	 * @return string File content
186
	 * @throws \Aimeos\Controller\Common\Exception If no file is found
187
	 */
188
	protected function getFileContent( $path, $fsname )
189
	{
190
		if( $path !== '' )
191
		{
192
			if( preg_match( '#^[a-zA-Z]{1,10}://#', $path ) === 1 )
193
			{
194
				if( ( $content = file_get_contents( $path ) ) === false )
195
				{
196
					$msg = sprintf( 'Download file "%1$s" using file_get_contents failed', $path );
197
					throw new \Aimeos\Controller\Common\Exception( $msg );
198
				}
199
200
				return $content;
201
			}
202
203
			$fs = $this->context->getFilesystemManager()->get( $fsname );
204
205
			if( $fs->has( $path ) !== false ) {
206
				return $fs->read( $path );
207
			}
208
		}
209
210
		throw new \Aimeos\Controller\Common\Exception( sprintf( 'File "%1$s" not found', $path ) );
211
	}
212
213
214
	/**
215
	 * Creates a new file path from the given arguments and random values
216
	 *
217
	 * @param string $filename Original file name, can contain the path as well
218
	 * @param string $type File type, i.e. "files" or "preview"
219
	 * @param string $mimetype Mime type of the file
220
	 * @return string New file name including the file path
221
	 */
222
	protected function getFilePath( $filename, $type, $mimetype )
223
	{
224
		switch( $mimetype )
225
		{
226
			case 'application/pdf': $ext = '.pdf'; break;
227
228
			case 'image/gif': $ext = '.gif'; break;
229
			case 'image/jpeg': $ext = '.jpg'; break;
230
			case 'image/png': $ext = '.png'; break;
231
			case 'image/tiff': $ext = '.tif'; break;
232
233
			default: $ext = '';
0 ignored issues
show
Coding Style introduced by
The default body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a default statement must start on the line immediately following the statement.

switch ($expr) {
    default:
        doSomething(); //right
        break;
}


switch ($expr) {
    default:

        doSomething(); //wrong
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
234
		}
235
236
		$filename = md5( $filename . getmypid() . microtime( true ) );
237
238
		return "${type}/${filename[0]}/${filename[1]}/${filename}${ext}";
239
	}
240
241
242
	/**
243
	 * Returns the media object for the given file name
244
	 *
245
	 * @param string $file Path to the file or file content
246
	 * @return \Aimeos\MW\Media\Iface Media object
247
	 */
248
	protected function getMediaFile( $file )
249
	{
250
		/** controller/common/media/standard/options
251
		 * Options used for processing the uploaded media files
252
		 *
253
		 * When uploading a file, a preview image for that file is generated if
254
		 * possible (especially for images). You can configure certain options
255
		 * for the generated images, namely the quality of those images with
256
		 *
257
		 *  array(
258
		 *  	'image' => array(
259
		 *  		'jpeg' => array(
260
		 *  			'quality' => 75
261
		 *  		),
262
		 *  		'png' => array(
263
		 *  			'quality' => 9
264
		 *  		),
265
		 *  	)
266
		 *  )
267
		 *
268
		 * @param array Multi-dimendional list of configuration options
269
		 * @since 2016.01
270
		 * @category Developer
271
		 * @category User
272
		 */
273
		$options = $this->context->getConfig()->get( 'controller/common/media/standard/options', array() );
274
275
		return \Aimeos\MW\Media\Factory::get( $file, $options );
276
	}
277
278
279
	/**
280
	 * Returns the relative path to the mime icon for the given mime type.
281
	 *
282
	 * @param string $mimetype Mime type like "image/png"
283
	 * @return string Relative path to the mime icon
284
	 */
285
	protected function getMimeIcon( $mimetype )
286
	{
287
		$config = $this->context->getConfig();
288
289
		/** controller/common/media/standard/mimeicon/directory
290
		 * Directory that contains the icons for the different mime types
291
		 *
292
		 * If no preview image can be generated from an uploaded file, an icon
293
		 * for its mime type is displayed instead. The directory for the mime
294
		 * icons is structured by the general mime type (e.g. "image") as
295
		 * sub-directory and the specific name of the mime type (e.g. "jpeg")
296
		 * as file name.
297
		 *
298
		 * Avoid leading and trailing slashes for the upload directory string!
299
		 *
300
		 * @param string Path or URL to the base directory
301
		 * @since 2016.01
302
		 * @category Developer
303
		 */
304
		if( ( $mimedir = $config->get( 'controller/common/media/standard/mimeicon/directory' ) ) == null ) {
305
			return '';
306
		}
307
308
		/** controller/common/media/standard/mimeicon/extension
309
		 * File extension of the mime icon images
310
		 *
311
		 * If you would like to use different mime icons that are available in
312
		 * another file format, you have to change the file extension for the
313
		 * mime icons to the actual ones.
314
		 *
315
		 * Note: The configured file extension needs a leading dot!
316
		 *
317
		 * @param string File extension including a leading dot, e.g ".jpg"
318
		 * @since 2016.01
319
		 * @category Developer
320
		 */
321
		$ext = $config->get( 'controller/common/media/standard/mimeicon/extension', '.png' );
322
323
		return $mimedir . DIRECTORY_SEPARATOR . $mimetype . $ext;
324
	}
325
326
327
	/**
328
	 * Returns the mime type for the new image
329
	 *
330
	 * @param \Aimeos\MW\Media\Image\Iface $media Media object
331
	 * @param string $type Type of the image like "preview" or "files"
332
	 * @return string New mime type
333
	 * @throws \Aimeos\Controller\Common\Exception If no mime types are configured
334
	 */
335
	protected function getMimeType( \Aimeos\MW\Media\Image\Iface $media, $type )
336
	{
337
		$mimetype = $media->getMimetype();
338
		$config = $this->context->getConfig();
339
340
		/** controller/common/media/standard/files/allowedtypes
341
		 * A list of image mime types that are allowed for uploaded image files
342
		 *
343
		 * The list of allowed image types must be explicitly configured for the
344
		 * uploaded image files. Trying to upload and store an image file not
345
		 * available in the list of allowed mime types will result in an exception.
346
		 *
347
		 * @param array List of image mime types
348
		 * @since 2016.01
349
		 * @category Developer
350
		 * @category User
351
		 */
352
353
		/** controller/common/media/standard/preview/allowedtypes
354
		 * A list of image mime types that are allowed for preview image files
355
		 *
356
		 * The list of allowed image types must be explicitly configured for the
357
		 * preview image files. Trying to create a preview image whose mime type
358
		 * is not available in the list of allowed mime types will result in an
359
		 * exception.
360
		 *
361
		 * @param array List of image mime types
362
		 * @since 2016.01
363
		 * @category Developer
364
		 * @category User
365
		 */
366
		$default = array( 'image/jpeg', 'image/png', 'image/gif' );
367
		$allowed = $config->get( 'controller/common/media/standard/' . $type . '/allowedtypes', $default );
368
369
		if( in_array( $mimetype, $allowed ) === false )
370
		{
371
			if( ( $defaulttype = reset( $allowed ) ) === false ) {
372
				throw new \Aimeos\Controller\Common\Exception( sprintf( 'No allowed image types configured for "%1$s"', $type ) );
373
			}
374
375
			return $defaulttype;
376
		}
377
378
		return $mimetype;
379
	}
380
381
382
	/**
383
	 * Scales the image according to the configuration settings
384
	 *
385
	 * @param \Aimeos\MW\Media\Image\Iface $media Media object
386
	 * @param string $type Type of the image like "preview" or "files"
387
	 * @param \Aimeos\MW\Media\Image\Iface Scaled media object
388
	 */
389
	protected function scaleImage( \Aimeos\MW\Media\Image\Iface $media, $type )
390
	{
391
		$config = $this->context->getConfig();
392
393
		/** controller/common/media/standard/files/maxwidth
394
		 * Maximum width of the uploaded images
395
		 *
396
		 * The uploaded image files are scaled down if their width exceeds the
397
		 * configured width of pixels. If the image width in smaller than the
398
		 * configured one, no scaling happens. In case of a value of null or if
399
		 * no configuration for that option is available, the image width isn't
400
		 * scaled at all.
401
		 *
402
		 * The width/height ratio of the image is always kept.
403
		 *
404
		 * @param integer|null Width in pixel or null for no scaling
405
		 * @since 2016.01
406
		 * @category Developer
407
		 * @category User
408
		 */
409
410
		/** controller/common/media/standard/preview/maxwidth
411
		 * Maximum width of the preview images
412
		 *
413
		 * The preview image files are created with the configured width in
414
		 * pixel. If the original image width in smaller than the one configured
415
		 * for the preview image, the width of the original image is used. In
416
		 * case of a value of null or if no configuration for that option is
417
		 * available, the width of the preview image is the same as the width of
418
		 * the original image.
419
		 *
420
		 * The width/height ratio of the preview image is always the same as for
421
		 * the original image.
422
		 *
423
		 * @param integer|null Width in pixel or null for no scaling
424
		 * @since 2016.01
425
		 * @category Developer
426
		 * @category User
427
		 */
428
		$maxwidth = $config->get( 'controller/common/media/standard/' . $type . '/maxwidth', null );
429
430
		/** controller/common/media/standard/files/maxheight
431
		 * Maximum height of the uploaded images
432
		 *
433
		 * The uploaded image files are scaled down if their height exceeds the
434
		 * configured height of pixels. If the image height in smaller than the
435
		 * configured one, no scaling happens. In case of a value of null or if
436
		 * no configuration for that option is available, the image width isn't
437
		 * scaled at all.
438
		 *
439
		 * The width/height ratio of the image is always kept.
440
		 *
441
		 * @param integer|null Height in pixel or null for no scaling
442
		 * @since 2016.01
443
		 * @category Developer
444
		 * @category User
445
		 */
446
447
		/** controller/common/media/standard/preview/maxheight
448
		 * Maximum height of the preview images
449
		 *
450
		 * The preview image files are created with the configured width in
451
		 * pixel. If the original image height in smaller than the one configured
452
		 * for the preview image, the height of the original image is used. In
453
		 * case of a value of null or if no configuration for that option is
454
		 * available, the height of the preview image is the same as the height
455
		 * of the original image.
456
		 *
457
		 * The width/height ratio of the preview image is always the same as for
458
		 * the original image.
459
		 *
460
		 * @param integer|null Height in pixel or null for no scaling
461
		 * @since 2016.01
462
		 * @category Developer
463
		 * @category User
464
		 */
465
		$maxheight = $config->get( 'controller/common/media/standard/' . $type . '/maxheight', null );
466
467
		return $media->scale( $maxwidth, $maxheight );
468
	}
469
470
471
	/**
472
	 * Stores a binary file and returns it's new relative file name
473
	 *
474
	 * @param \Aimeos\MW\Media\Iface $media Media object
0 ignored issues
show
Bug introduced by
There is no parameter named $media. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
475
	 * @param string $type Type of the image like "preview" or "files"
0 ignored issues
show
Bug introduced by
There is no parameter named $type. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
476
	 * @param string $filepath Path of the new file
477
	 * @param string Name of the file system to store the files at
478
	 * @return string Relative path to the new file
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
479
	 * @throws \Aimeos\Controller\Common\Exception If an error occurs
480
	 */
481
	protected function storeFile( $content, $fsname, $filepath, $oldpath )
482
	{
483
		$fs = $this->context->getFilesystemManager()->get( $fsname );
484
485
		if( $oldpath !== $filepath && $fs->has( $oldpath ) ) {
486
			$fs->rm( $oldpath );
487
		}
488
489
		$fs->write( $filepath, $content );
490
	}
491
}
492