Completed
Push — master ( 42a997...23dd52 )
by Aimeos
12:12
created

Standard::getFilePath()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 27
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 3
dl 0
loc 27
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2015-2017
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( !( $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
		 *  		'jpeg' => array(
271
		 *  			'quality' => 75
272
		 *  		),
273
		 *  		'png' => array(
274
		 *  			'quality' => 9
275
		 *  		),
276
		 *  	)
277
		 *  )
278
		 *
279
		 * @param array Multi-dimendional list of configuration options
280
		 * @since 2016.01
281
		 * @category Developer
282
		 * @category User
283
		 */
284
		$options = $this->context->getConfig()->get( 'controller/common/media/standard/options', [] );
285
286
		return \Aimeos\MW\Media\Factory::get( $file, $options );
287
	}
288
289
290
	/**
291
	 * Returns the relative path to the mime icon for the given mime type.
292
	 *
293
	 * @param string $mimetype Mime type like "image/png"
294
	 * @return string Relative path to the mime icon
295
	 */
296
	protected function getMimeIcon( $mimetype )
297
	{
298
		$config = $this->context->getConfig();
299
300
		/** controller/common/media/standard/mimeicon/directory
301
		 * Directory that contains the icons for the different mime types
302
		 *
303
		 * If no preview image can be generated from an uploaded file, an icon
304
		 * for its mime type is displayed instead. The directory for the mime
305
		 * icons is structured by the general mime type (e.g. "image") as
306
		 * sub-directory and the specific name of the mime type (e.g. "jpeg")
307
		 * as file name.
308
		 *
309
		 * Avoid leading and trailing slashes for the upload directory string!
310
		 *
311
		 * @param string Path or URL to the base directory
312
		 * @since 2016.01
313
		 * @category Developer
314
		 */
315
		if( ( $mimedir = $config->get( 'controller/common/media/standard/mimeicon/directory' ) ) == null ) {
316
			return '';
317
		}
318
319
		/** controller/common/media/standard/mimeicon/extension
320
		 * File extension of the mime icon images
321
		 *
322
		 * If you would like to use different mime icons that are available in
323
		 * another file format, you have to change the file extension for the
324
		 * mime icons to the actual ones.
325
		 *
326
		 * Note: The configured file extension needs a leading dot!
327
		 *
328
		 * @param string File extension including a leading dot, e.g ".jpg"
329
		 * @since 2016.01
330
		 * @category Developer
331
		 */
332
		$ext = $config->get( 'controller/common/media/standard/mimeicon/extension', '.png' );
333
334
		return $mimedir . DIRECTORY_SEPARATOR . $mimetype . $ext;
335
	}
336
337
338
	/**
339
	 * Returns the mime type for the new image
340
	 *
341
	 * @param \Aimeos\MW\Media\Image\Iface $media Media object
342
	 * @param string $type Type of the image like "preview" or "files"
343
	 * @return string New mime type
344
	 * @throws \Aimeos\Controller\Common\Exception If no mime types are configured
345
	 */
346
	protected function getMimeType( \Aimeos\MW\Media\Image\Iface $media, $type )
347
	{
348
		$mimetype = $media->getMimetype();
349
		$config = $this->context->getConfig();
350
351
		/** controller/common/media/standard/files/allowedtypes
352
		 * A list of image mime types that are allowed for uploaded image files
353
		 *
354
		 * The list of allowed image types must be explicitly configured for the
355
		 * uploaded image files. Trying to upload and store an image file not
356
		 * available in the list of allowed mime types will result in an exception.
357
		 *
358
		 * @param array List of image mime types
359
		 * @since 2016.01
360
		 * @category Developer
361
		 * @category User
362
		 */
363
364
		/** controller/common/media/standard/preview/allowedtypes
365
		 * A list of image mime types that are allowed for preview image files
366
		 *
367
		 * The list of allowed image types must be explicitly configured for the
368
		 * preview image files. Trying to create a preview image whose mime type
369
		 * is not available in the list of allowed mime types will result in an
370
		 * exception.
371
		 *
372
		 * @param array List of image mime types
373
		 * @since 2016.01
374
		 * @category Developer
375
		 * @category User
376
		 */
377
		$default = array( 'image/jpeg', 'image/png', 'image/gif' );
378
		$allowed = $config->get( 'controller/common/media/standard/' . $type . '/allowedtypes', $default );
379
380
		if( in_array( $mimetype, $allowed ) === false )
381
		{
382
			if( ( $defaulttype = reset( $allowed ) ) === false ) {
383
				throw new \Aimeos\Controller\Common\Exception( sprintf( 'No allowed image types configured for "%1$s"', $type ) );
384
			}
385
386
			return $defaulttype;
387
		}
388
389
		return $mimetype;
390
	}
391
392
393
	/**
394
	 * Scales the image according to the configuration settings
395
	 *
396
	 * @param \Aimeos\MW\Media\Image\Iface $media Media object
397
	 * @param string $type Type of the image like "preview" or "files"
398
	 * @param \Aimeos\MW\Media\Image\Iface Scaled media object
399
	 */
400
	protected function scaleImage( \Aimeos\MW\Media\Image\Iface $media, $type )
401
	{
402
		$config = $this->context->getConfig();
403
404
		/** controller/common/media/standard/files/maxwidth
405
		 * Maximum width of the uploaded images
406
		 *
407
		 * The uploaded image files are scaled down if their width exceeds the
408
		 * configured width of pixels. If the image width in smaller than the
409
		 * configured one, no scaling happens. In case of a value of null or if
410
		 * no configuration for that option is available, the image width isn't
411
		 * scaled at all.
412
		 *
413
		 * The width/height ratio of the image is always kept.
414
		 *
415
		 * @param integer|null Width in pixel or null for no scaling
416
		 * @since 2016.01
417
		 * @category Developer
418
		 * @category User
419
		 */
420
421
		/** controller/common/media/standard/preview/maxwidth
422
		 * Maximum width of the preview images
423
		 *
424
		 * The preview image files are created with the configured width in
425
		 * pixel. If the original image width in smaller than the one configured
426
		 * for the preview image, the width of the original image is used. In
427
		 * case of a value of null or if no configuration for that option is
428
		 * available, the width of the preview image is the same as the width of
429
		 * the original image.
430
		 *
431
		 * The width/height ratio of the preview image is always the same as for
432
		 * the original image.
433
		 *
434
		 * @param integer|null Width in pixel or null for no scaling
435
		 * @since 2016.01
436
		 * @category Developer
437
		 * @category User
438
		 */
439
		$maxwidth = $config->get( 'controller/common/media/standard/' . $type . '/maxwidth', null );
440
441
		/** controller/common/media/standard/files/maxheight
442
		 * Maximum height of the uploaded images
443
		 *
444
		 * The uploaded image files are scaled down if their height exceeds the
445
		 * configured height of pixels. If the image height in smaller than the
446
		 * configured one, no scaling happens. In case of a value of null or if
447
		 * no configuration for that option is available, the image width isn't
448
		 * scaled at all.
449
		 *
450
		 * The width/height ratio of the image is always kept.
451
		 *
452
		 * @param integer|null Height in pixel or null for no scaling
453
		 * @since 2016.01
454
		 * @category Developer
455
		 * @category User
456
		 */
457
458
		/** controller/common/media/standard/preview/maxheight
459
		 * Maximum height of the preview images
460
		 *
461
		 * The preview image files are created with the configured width in
462
		 * pixel. If the original image height in smaller than the one configured
463
		 * for the preview image, the height of the original image is used. In
464
		 * case of a value of null or if no configuration for that option is
465
		 * available, the height of the preview image is the same as the height
466
		 * of the original image.
467
		 *
468
		 * The width/height ratio of the preview image is always the same as for
469
		 * the original image.
470
		 *
471
		 * @param integer|null Height in pixel or null for no scaling
472
		 * @since 2016.01
473
		 * @category Developer
474
		 * @category User
475
		 */
476
		$maxheight = $config->get( 'controller/common/media/standard/' . $type . '/maxheight', null );
477
478
		return $media->scale( $maxwidth, $maxheight );
479
	}
480
481
482
	/**
483
	 * Stores the file content
484
	 *
485
	 * @param string $content File content
486
	 * @param string $fsname Name of the file system to store the files at
487
	 * @param string $filepath Path of the new file
488
	 * @param string $oldpath Path of the old file
489
	 */
490
	protected function storeFile( $content, $fsname, $filepath, $oldpath )
491
	{
492
		$fs = $this->context->getFilesystemManager()->get( $fsname );
493
494
		try
495
		{
496
			if( $oldpath !== '' && $oldpath !== $filepath && $fs->has( $oldpath ) ) {
497
				$fs->rm( $oldpath );
498
			}
499
		}
500
		catch( \Aimeos\MW\Filesystem\Exception $e ) {} // continue if removing file fails
501
502
		$fs->write( $filepath, $content );
503
	}
504
}
505