Completed
Push — master ( 4d9bb9...61cd79 )
by Aimeos
09:47
created

Standard::getFilePath()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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