Passed
Push — master ( 29d5f6...586595 )
by Aimeos
04:48
created

common/src/Controller/Common/Media/Standard.php (3 issues)

1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2015-2018
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
0 ignored issues
show
The type Aimeos\Controller\Common\Media\Uploaded was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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();
0 ignored issues
show
The assignment to $config is dead and can be removed.
Loading history...
128
		$media = $this->getMediaFile( $this->getFileContent( $path, $fsname ) );
129
130
		if( !( $media instanceof \Aimeos\MW\Media\Image\Iface ) ) {
131
			return;
132
		}
133
134
		$this->scaleImage( $media, 'files' );
135
		$mimetype = $this->getMimeType( $media, 'files' );
136
		$filepath = $this->getFilePath( $path, 'files', $mimetype );
137
		$this->storeFile( $media->save( null, $mimetype ), $fsname, $filepath, $path );
138
		$item->setUrl( $filepath );
139
140
		$this->scaleImage( $media, 'preview' );
141
		$mimetype = $this->getMimeType( $media, 'preview' );
142
		$filepath = $this->getFilePath( $path, 'preview', $mimetype );
143
		$this->storeFile( $media->save( null, $mimetype ), $fsname, $filepath, $item->getPreview() );
144
		$item->setPreview( $filepath );
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( 'Downloading file "%1$s" 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
		/** controller/common/media/standard/extensions
225
		 * Available files extensions for mime types of uploaded files
226
		 *
227
		 * Uploaded files should have the right file extension (e.g. ".jpg" for
228
		 * JPEG images) so files are recognized correctly if downloaded by users.
229
		 * The extension of the uploaded file can't be trusted and only its mime
230
		 * type can be determined automatically. This configuration setting
231
		 * provides the file extensions for the configured mime types. You can
232
		 * add more mime type / file extension combinations if required.
233
		 *
234
		 * @param array Associative list of mime types as keys and file extensions as values
235
		 * @since 2018.04
236
		 * @category Developer
237
		 */
238
		$list = $this->context->getConfig()->get( 'controller/common/media/standard/extensions', [] );
239
		$ext = '';
240
241
		if( isset( $list[$mimetype] ) ) {
242
			$ext = '.' . $list[$mimetype];
243
		}
244
245
		$filename = md5( $filename . getmypid() . microtime( true ) );
246
247
		return "${type}/${filename[0]}/${filename[1]}/${filename}${ext}";
248
	}
249
250
251
	/**
252
	 * Returns the media object for the given file name
253
	 *
254
	 * @param string $file Path to the file or file content
255
	 * @return \Aimeos\MW\Media\Iface Media object
256
	 */
257
	protected function getMediaFile( $file )
258
	{
259
		/** controller/common/media/standard/options
260
		 * Options used for processing the uploaded media files
261
		 *
262
		 * When uploading a file, a preview image for that file is generated if
263
		 * possible (especially for images). You can configure certain options
264
		 * for the generated images, namely the implementation of the scaling
265
		 * algorithm and the quality of the resulting images with
266
		 *
267
		 *  array(
268
		 *  	'image' => array(
269
		 *  		'name' => 'Imagick',
270
		 *  		'quality' => 75,
271
		 *  	)
272
		 *  )
273
		 *
274
		 * @param array Multi-dimendional list of configuration options
275
		 * @since 2016.01
276
		 * @category Developer
277
		 * @category User
278
		 */
279
		$options = $this->context->getConfig()->get( 'controller/common/media/standard/options', [] );
280
281
		return \Aimeos\MW\Media\Factory::get( $file, $options );
282
	}
283
284
285
	/**
286
	 * Returns the relative path to the mime icon for the given mime type.
287
	 *
288
	 * @param string $mimetype Mime type like "image/png"
289
	 * @return string Relative path to the mime icon
290
	 */
291
	protected function getMimeIcon( $mimetype )
292
	{
293
		$config = $this->context->getConfig();
294
295
		/** controller/common/media/standard/mimeicon/directory
296
		 * Directory that contains the icons for the different mime types
297
		 *
298
		 * If no preview image can be generated from an uploaded file, an icon
299
		 * for its mime type is displayed instead. The directory for the mime
300
		 * icons is structured by the general mime type (e.g. "image") as
301
		 * sub-directory and the specific name of the mime type (e.g. "jpeg")
302
		 * as file name.
303
		 *
304
		 * Avoid leading and trailing slashes for the upload directory string!
305
		 *
306
		 * @param string Path or URL to the base directory
307
		 * @since 2016.01
308
		 * @category Developer
309
		 */
310
		if( ( $mimedir = $config->get( 'controller/common/media/standard/mimeicon/directory' ) ) == null ) {
311
			return '';
312
		}
313
314
		/** controller/common/media/standard/mimeicon/extension
315
		 * File extension of the mime icon images
316
		 *
317
		 * If you would like to use different mime icons that are available in
318
		 * another file format, you have to change the file extension for the
319
		 * mime icons to the actual ones.
320
		 *
321
		 * Note: The configured file extension needs a leading dot!
322
		 *
323
		 * @param string File extension including a leading dot, e.g ".jpg"
324
		 * @since 2016.01
325
		 * @category Developer
326
		 */
327
		$ext = $config->get( 'controller/common/media/standard/mimeicon/extension', '.png' );
328
329
		return $mimedir . DIRECTORY_SEPARATOR . $mimetype . $ext;
330
	}
331
332
333
	/**
334
	 * Returns the mime type for the new image
335
	 *
336
	 * @param \Aimeos\MW\Media\Image\Iface $media Media object
337
	 * @param string $type Type of the image like "preview" or "files"
338
	 * @return string New mime type
339
	 * @throws \Aimeos\Controller\Common\Exception If no mime types are configured
340
	 */
341
	protected function getMimeType( \Aimeos\MW\Media\Image\Iface $media, $type )
342
	{
343
		$mimetype = $media->getMimetype();
344
		$config = $this->context->getConfig();
345
346
		/** controller/common/media/standard/files/allowedtypes
347
		 * A list of image mime types that are allowed for uploaded image files
348
		 *
349
		 * The list of allowed image types must be explicitly configured for the
350
		 * uploaded image files. Trying to upload and store an image file not
351
		 * available in the list of allowed mime types will result in an exception.
352
		 *
353
		 * @param array List of image mime types
354
		 * @since 2016.01
355
		 * @category Developer
356
		 * @category User
357
		 */
358
359
		/** controller/common/media/standard/preview/allowedtypes
360
		 * A list of image mime types that are allowed for preview image files
361
		 *
362
		 * The list of allowed image types must be explicitly configured for the
363
		 * preview image files. Trying to create a preview image whose mime type
364
		 * is not available in the list of allowed mime types will result in an
365
		 * exception.
366
		 *
367
		 * @param array List of image mime types
368
		 * @since 2016.01
369
		 * @category Developer
370
		 * @category User
371
		 */
372
		$default = array( 'image/jpeg', 'image/png', 'image/gif' );
373
		$allowed = $config->get( 'controller/common/media/standard/' . $type . '/allowedtypes', $default );
374
375
		if( in_array( $mimetype, $allowed ) === false )
376
		{
377
			if( ( $defaulttype = reset( $allowed ) ) === false ) {
378
				throw new \Aimeos\Controller\Common\Exception( sprintf( 'No allowed image types configured for "%1$s"', $type ) );
379
			}
380
381
			return $defaulttype;
382
		}
383
384
		return $mimetype;
385
	}
386
387
388
	/**
389
	 * Scales the image according to the configuration settings
390
	 *
391
	 * @param \Aimeos\MW\Media\Image\Iface $media Media object
392
	 * @param string $type Type of the image like "preview" or "files"
393
	 * @param \Aimeos\MW\Media\Image\Iface Scaled media object
0 ignored issues
show
The type Aimeos\Controller\Common\Media\Scaled was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
394
	 */
395
	protected function scaleImage( \Aimeos\MW\Media\Image\Iface $media, $type )
396
	{
397
		$config = $this->context->getConfig();
398
399
		/** controller/common/media/standard/files/maxwidth
400
		 * Maximum width of the uploaded images
401
		 *
402
		 * The uploaded image files are scaled down if their width exceeds the
403
		 * configured width of pixels. If the image width in smaller than the
404
		 * configured one, no scaling happens. In case of a value of null or if
405
		 * no configuration for that option is available, the image width isn't
406
		 * scaled at all.
407
		 *
408
		 * The width/height ratio of the image is always kept.
409
		 *
410
		 * @param integer|null Width in pixel or null for no scaling
411
		 * @since 2016.01
412
		 * @category Developer
413
		 * @category User
414
		 */
415
416
		/** controller/common/media/standard/preview/maxwidth
417
		 * Maximum width of the preview images
418
		 *
419
		 * The preview image files are created with the configured width in
420
		 * pixel. If the original image width in smaller than the one configured
421
		 * for the preview image, the width of the original image is used. In
422
		 * case of a value of null or if no configuration for that option is
423
		 * available, the width of the preview image is the same as the width of
424
		 * the original image.
425
		 *
426
		 * The width/height ratio of the preview image is always the same as for
427
		 * the original image.
428
		 *
429
		 * @param integer|null Width in pixel or null for no scaling
430
		 * @since 2016.01
431
		 * @category Developer
432
		 * @category User
433
		 */
434
		$maxwidth = $config->get( 'controller/common/media/standard/' . $type . '/maxwidth', null );
435
436
		/** controller/common/media/standard/files/maxheight
437
		 * Maximum height of the uploaded images
438
		 *
439
		 * The uploaded image files are scaled down if their height exceeds the
440
		 * configured height of pixels. If the image height in smaller than the
441
		 * configured one, no scaling happens. In case of a value of null or if
442
		 * no configuration for that option is available, the image width isn't
443
		 * scaled at all.
444
		 *
445
		 * The width/height ratio of the image is always kept.
446
		 *
447
		 * @param integer|null Height in pixel or null for no scaling
448
		 * @since 2016.01
449
		 * @category Developer
450
		 * @category User
451
		 */
452
453
		/** controller/common/media/standard/preview/maxheight
454
		 * Maximum height of the preview images
455
		 *
456
		 * The preview image files are created with the configured width in
457
		 * pixel. If the original image height in smaller than the one configured
458
		 * for the preview image, the height of the original image is used. In
459
		 * case of a value of null or if no configuration for that option is
460
		 * available, the height of the preview image is the same as the height
461
		 * of the original image.
462
		 *
463
		 * The width/height ratio of the preview image is always the same as for
464
		 * the original image.
465
		 *
466
		 * @param integer|null Height in pixel or null for no scaling
467
		 * @since 2016.01
468
		 * @category Developer
469
		 * @category User
470
		 */
471
		$maxheight = $config->get( 'controller/common/media/standard/' . $type . '/maxheight', null );
472
473
		return $media->scale( $maxwidth, $maxheight );
474
	}
475
476
477
	/**
478
	 * Stores the file content
479
	 *
480
	 * @param string $content File content
481
	 * @param string $fsname Name of the file system to store the files at
482
	 * @param string $filepath Path of the new file
483
	 * @param string $oldpath Path of the old file
484
	 */
485
	protected function storeFile( $content, $fsname, $filepath, $oldpath )
486
	{
487
		$fs = $this->context->getFilesystemManager()->get( $fsname );
488
489
		try
490
		{
491
			if( $oldpath !== '' && $oldpath !== $filepath && $fs->has( $oldpath ) ) {
492
				$fs->rm( $oldpath );
493
			}
494
		}
495
		catch( \Aimeos\MW\Filesystem\Exception $e ) {} // continue if removing file fails
496
497
		$fs->write( $filepath, $content );
498
	}
499
}
500