Completed
Push — master ( 34d177...077d24 )
by Aimeos
09:11
created

Standard::storeFile()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 7
nc 4
nop 4
dl 0
loc 14
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
		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', [] );
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 the file content
473
	 *
474
	 * @param string $content File content
475
	 * @param string $fsname Name of the file system to store the files at
476
	 * @param string $filepath Path of the new file
477
	 * @param string $oldpath Path of the old file
478
	 */
479
	protected function storeFile( $content, $fsname, $filepath, $oldpath )
480
	{
481
		$fs = $this->context->getFilesystemManager()->get( $fsname );
482
483
		try
484
		{
485
			if( $oldpath !== '' && $oldpath !== $filepath && $fs->has( $oldpath ) ) {
486
				$fs->rm( $oldpath );
487
			}
488
		}
489
		catch( \Aimeos\MW\Filesystem\Exception $e ) {} // continue if removing file fails
490
491
		$fs->write( $filepath, $content );
492
	}
493
}
494