Passed
Push — master ( 179271...9dc280 )
by Aimeos
04:18
created

Base::path()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 30
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 9
c 1
b 0
f 0
dl 0
loc 30
rs 9.9666
cc 3
nc 2
nop 3
1
<?php
2
3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2023
6
 * @package MShop
7
 * @subpackage Media
8
 */
9
10
11
namespace Aimeos\MShop\Media\Manager;
12
13
use enshrined\svgSanitize\Sanitizer;
14
use \Intervention\Image\Interfaces\ImageInterface;
0 ignored issues
show
Bug introduced by
The type \Intervention\Image\Interfaces\ImageInterface 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...
15
16
17
/**
18
 * Base media manager implementation
19
 *
20
 * @package MShop
21
 * @subpackage Media
22
 */
23
abstract class Base extends \Aimeos\MShop\Common\Manager\Base
24
{
25
	private ?\Intervention\Image\ImageManager $driver = null;
26
27
28
	/**
29
	 * Returns the media object for the given file name
30
	 *
31
	 * @param \Aimeos\Base\Filesystem\Iface $fs File system where the file is stored
32
	 * @param string $file URL or relative path to the file
33
	 * @return \Intervention\Image\Interfaces\ImageInterface Image object
34
	 */
35
	protected function image( \Aimeos\Base\Filesystem\Iface $fs, string $file ) : ImageInterface
36
	{
37
		if( !isset( $this->driver ) )
38
		{
39
			if( class_exists( '\Intervention\Image\Vips\Driver' ) ) {
40
				$driver = new \Intervention\Image\Vips\Driver();
0 ignored issues
show
Bug introduced by
The type Intervention\Image\Vips\Driver 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...
41
			} elseif( class_exists( 'Imagick' ) ) {
42
				$driver = new \Intervention\Image\Drivers\Imagick\Driver();
43
			} else {
44
				$driver = new Intervention\Image\Drivers\Gd\Driver();
0 ignored issues
show
Bug introduced by
The type Aimeos\MShop\Media\Manag...Image\Drivers\Gd\Driver was not found. Did you mean Intervention\Image\Drivers\Gd\Driver? If so, make sure to prefix the type with \.
Loading history...
45
			}
46
47
			$this->driver = new \Intervention\Image\ImageManager( $driver );
48
		}
49
50
		if( preg_match( '#^[a-zA-Z]{1,10}://#', $file ) === 1 )
51
		{
52
			if( ( $fh = fopen( $file, 'r' ) ) === false )
53
			{
54
				$msg = $this->context()->translate( 'mshop', 'Unable to open file "%1$s"' );
55
				throw new \Aimeos\MShop\Media\Exception( sprintf( $msg, $file ) );
56
			}
57
		}
58
		else
59
		{
60
			$fh = $this->context()->fs( 'fs-media' )->reads( $file );
61
		}
62
63
		$image = $this->driver->read( $fh );
0 ignored issues
show
Bug introduced by
The method read() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

63
		/** @scrutinizer ignore-call */ 
64
  $image = $this->driver->read( $fh );

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
64
		fclose( $fh );
65
66
		return $image;
67
	}
68
69
70
	/**
71
	 * Checks if the mime type is allowed
72
	 *
73
	 * @param string $mimetype Mime type
74
	 * @return bool TRUE if mime type is allowed
75
	 * @throws \Aimeos\MShop\Media\Exception If mime type is not allowed
76
	 */
77
	protected function isAllowed( string $mimetype ) : bool
78
	{
79
		$context = $this->context();
80
81
		/** mshop/media/manager/allowedtypes
82
		 * A list of mime types that are allowed for uploaded files
83
		 *
84
		 * The list of allowed mime types must be explicitly configured for the
85
		 * uploaded files. Trying to upload and store a file not available in
86
		 * the list of allowed mime types will result in an exception.
87
		 *
88
		 * @param array List of image mime types
89
		 * @since 2024.01
90
		 */
91
		$default = [
92
			'image/webp', 'image/jpeg', 'image/png', 'image/gif', 'image/svg+xml',
93
			'application/epub+zip', 'application/pdf', 'application/zip',
94
			'video/mp4', 'video/webm',
95
			'audio/mpeg', 'audio/ogg', 'audio/weba'
96
		];
97
		$allowed = $context->config()->get( 'mshop/media/manager/allowedtypes', $default );
98
99
		if( !in_array( $mimetype, $allowed ) )
100
		{
101
			$msg = sprintf( $context->translate( 'mshop', 'Uploading mimetype "%1$s" is not allowed' ), $mimetype );
102
			throw new \Aimeos\MShop\Media\Exception( $msg, 406 );
103
		}
104
105
		return true;
106
	}
107
108
109
	/**
110
	 * Creates a new file path from the given arguments
111
	 *
112
	 * @param string $filepath Original file name, can contain the path as well
113
	 * @param string $mimetype Mime type
114
	 * @param string $domain data domain
115
	 * @return string New file name including the file path
116
	 */
117
	protected function path( string $filepath, string $mimetype, string $domain ) : string
118
	{
119
		$context = $this->context();
120
121
		/** mshop/media/manager/extensions
122
		 * Available files extensions for mime types of uploaded files
123
		 *
124
		 * Uploaded files should have the right file extension (e.g. ".jpg" for
125
		 * JPEG images) so files are recognized correctly if downloaded by users.
126
		 * The extension of the uploaded file can't be trusted and only its mime
127
		 * type can be determined automatically. This configuration setting
128
		 * provides the file extensions for the configured mime types. You can
129
		 * add more mime type / file extension combinations if required.
130
		 *
131
		 * @param array Associative list of mime types as keys and file extensions as values
132
		 * @since 2018.04
133
		 * @category Developer
134
		 */
135
		$default = ['image/gif' => 'gif', 'image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp'];
136
		$list = $context->config()->get( 'mshop/media/manager/extensions', $default );
137
138
		$filename = basename( $filepath );
139
		$filename = \Aimeos\Base\Str::slug( substr( $filename, 0, strrpos( $filename, '.' ) ?: null ) );
140
		$filename = substr( md5( $filename . getmypid() . microtime( true ) ), -8 ) . '_' . $filename;
141
142
		$ext = isset( $list[$mimetype] ) ? '.' . $list[$mimetype] : '';
143
		$siteid = $context->locale()->getSiteId();
144
145
		// the "d" after {siteid} is the required extension for Windows (no dots at the end allowed)
146
		return "{$siteid}d/{$domain}/{$filename[0]}/{$filename[1]}/{$filename}{$ext}";
147
	}
148
149
150
	/**
151
	 * Returns the quality level of the resized images
152
	 *
153
	 * @return int Quality level from 0 to 100
154
	 */
155
	protected function quality() : int
156
	{
157
		/** mshop/media/manager/quality
158
		 * Quality level of saved images
159
		 *
160
		 * Qualitity level must be an integer from 0 (worst) to 100 (best).
161
		 * The higher the quality, the bigger the file size.
162
		 *
163
		 * @param int Quality level from 0 to 100
164
		 * @since 2024.01
165
		 */
166
		return $this->context()->config()->get( 'mshop/media/manager/quality', 75 );
167
	}
168
169
170
	/**
171
	 * Sanitizes the uploaded file
172
	 *
173
	 * @param string $content File content
174
	 * @param string $mimetype File mime type
175
	 * @return string Sanitized content
176
	 */
177
	protected function sanitize( string $content, string $mimetype ) : string
178
	{
179
		if( strncmp( 'image/svg', $mimetype, 9 ) === 0 )
180
		{
181
			$sanitizer = new Sanitizer();
182
			$sanitizer->removeRemoteReferences( true );
183
184
			if( ( $content = $sanitizer->sanitize( $content ) ) === false )
185
			{
186
				$msg = $this->context()->translate( 'mshop', 'Invalid SVG file: %1$s' );
187
				throw new \Aimeos\MShop\Media\Exception( sprintf( $msg, print_r( $sanitizer->getXmlIssues(), true ) ) );
188
			}
189
		}
190
191
		if( $fcn = self::macro( 'sanitize' ) ) {
192
			$content = $fcn( $content, $mimetype );
193
		}
194
195
		return $content;
196
	}
197
198
199
	/**
200
	 * Called after the image has been scaled
201
	 * Can be used to update the media item with image information.
202
	 *
203
	 * @param \Aimeos\MShop\Media\Item\Iface $item Media item with new preview URLs
204
	 * @param \Intervention\Image\Interfaces\ImageInterface $image Media object
205
	 */
206
	protected function scaled( \Aimeos\MShop\Media\Item\Iface $item, ImageInterface $image )
207
	{
208
	}
209
}
210