Passed
Push — master ( b3bc39...bfd722 )
by Aimeos
16:46
created

Standard::extension()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 7
c 1
b 0
f 0
nc 6
nop 1
dl 0
loc 12
rs 9.2222
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2021-2023
6
 * @package Admin
7
 * @subpackage JQAdm
8
 */
9
10
11
namespace Aimeos\Admin\JQAdm\Settings;
12
13
sprintf( 'settings' ); // for translation
14
15
16
/**
17
 * Default implementation of settings JQAdm client.
18
 *
19
 * @package Admin
20
 * @subpackage JQAdm
21
 */
22
class Standard
23
	extends \Aimeos\Admin\JQAdm\Common\Admin\Factory\Base
24
	implements \Aimeos\Admin\JQAdm\Common\Admin\Factory\Iface
25
{
26
	/** admin/jqadm/settings/name
27
	 * Class name of the used account favorite client implementation
28
	 *
29
	 * Each default admin client can be replace by an alternative imlementation.
30
	 * To use this implementation, you have to set the last part of the class
31
	 * name as configuration value so the client factory knows which class it
32
	 * has to instantiate.
33
	 *
34
	 * For example, if the name of the default class is
35
	 *
36
	 *  \Aimeos\Admin\JQAdm\Settings\Standard
37
	 *
38
	 * and you want to replace it with your own version named
39
	 *
40
	 *  \Aimeos\Admin\JQAdm\Settings\Myfavorite
41
	 *
42
	 * then you have to set the this configuration option:
43
	 *
44
	 *  admin/jqadm/settings/name = Myfavorite
45
	 *
46
	 * The value is the last part of your own class name and it's case sensitive,
47
	 * so take care that the configuration value is exactly named like the last
48
	 * part of the class name.
49
	 *
50
	 * The allowed characters of the class name are A-Z, a-z and 0-9. No other
51
	 * characters are possible! You should always start the last part of the class
52
	 * name with an upper case character and continue only with lower case characters
53
	 * or numbers. Avoid chamel case names like "MyFavorite"!
54
	 *
55
	 * @param string Last part of the class name
56
	 * @since 2021.07
57
	 */
58
59
60
	/**
61
	 * Adds the required data used in the template
62
	 *
63
	 * @param \Aimeos\Base\View\Iface $view View object
64
	 * @return \Aimeos\Base\View\Iface View object with assigned parameters
65
	 */
66
	public function data( \Aimeos\Base\View\Iface $view ) : \Aimeos\Base\View\Iface
67
	{
68
		$view->themes = $this->context()->config()->get( 'client/html/themes', [] );
69
		$view->itemSubparts = $this->getSubClientNames();
70
		return $view;
71
	}
72
73
74
	/**
75
	 * Saves the data
76
	 *
77
	 * @return string|null HTML output
78
	 */
79
	public function save() : ?string
80
	{
81
		$view = $this->object()->data( $this->view() );
82
		$context = $this->context();
83
84
		$manager = \Aimeos\MShop::create( $context, 'locale/site' );
85
		$manager->begin();
86
87
		try
88
		{
89
			$view->item = $this->fromArray( $view->param( 'item', [] ) );
90
			$view->itemBody = parent::save();
91
92
			$manager->save( clone $view->item );
93
			$manager->commit();
94
95
			$params = $this->getClientParams();
96
			$params['site'] = $view->item->getCode();
97
98
			$context->session()->set( 'info', [$context->translate( 'admin', 'Item saved successfully' )] );
99
100
			$view->response()->withStatus( 302 );
101
			$view->response()->withHeader( 'Cache-Control', 'no-store' );
102
			$view->response()->withHeader( 'Location', $view->link( 'admin/jqadm/url/search', $params ) );
103
104
			return null;
105
		}
106
		catch( \Exception $e )
107
		{
108
			$manager->rollback();
109
			$this->report( $e, 'save' );
110
		}
111
112
		return $this->search();
113
	}
114
115
116
	/**
117
	 * Returns the settings root node
118
	 *
119
	 * @return string|null HTML output
120
	 */
121
	public function search() : ?string
122
	{
123
		$view = $this->object()->data( $this->view() );
124
125
		try
126
		{
127
			$view->item = $this->context()->locale()->getSiteItem();
128
			$view->itemData = array_replace_recursive( $this->toArray( $view->item ), $view->param( 'item', [] ) );
129
			$view->itemBody = parent::search();
130
		}
131
		catch( \Exception $e )
132
		{
133
			$this->report( $e, 'search' );
134
		}
135
136
		return $this->render( $view );
137
	}
138
139
140
	/**
141
	 * Returns the sub-client given by its name.
142
	 *
143
	 * @param string $type Name of the client type
144
	 * @param string|null $name Name of the sub-client (Default if null)
145
	 * @return \Aimeos\Admin\JQAdm\Iface Sub-client object
146
	 */
147
	public function getSubClient( string $type, string $name = null ) : \Aimeos\Admin\JQAdm\Iface
148
	{
149
		/** admin/jqadm/settings/decorators/excludes
150
		 * Excludes decorators added by the "common" option from the settings JQAdm client
151
		 *
152
		 * Decorators extend the functionality of a class by adding new aspects
153
		 * (e.g. log what is currently done), executing the methods of the underlying
154
		 * class only in certain conditions (e.g. only for logged in users) or
155
		 * modify what is returned to the caller.
156
		 *
157
		 * This option allows you to remove a decorator added via
158
		 * "client/jqadm/common/decorators/default" before they are wrapped
159
		 * around the JQAdm client.
160
		 *
161
		 *  admin/jqadm/settings/decorators/excludes = array( 'decorator1' )
162
		 *
163
		 * This would remove the decorator named "decorator1" from the list of
164
		 * common decorators ("\Aimeos\Admin\JQAdm\Common\Decorator\*") added via
165
		 * "client/jqadm/common/decorators/default" to the JQAdm client.
166
		 *
167
		 * @param array List of decorator names
168
		 * @since 2021.07
169
		 * @see admin/jqadm/common/decorators/default
170
		 * @see admin/jqadm/settings/decorators/global
171
		 * @see admin/jqadm/settings/decorators/local
172
		 */
173
174
		/** admin/jqadm/settings/decorators/global
175
		 * Adds a list of globally available decorators only to the settings JQAdm client
176
		 *
177
		 * Decorators extend the functionality of a class by adding new aspects
178
		 * (e.g. log what is currently done), executing the methods of the underlying
179
		 * class only in certain conditions (e.g. only for logged in users) or
180
		 * modify what is returned to the caller.
181
		 *
182
		 * This option allows you to wrap global decorators
183
		 * ("\Aimeos\Admin\JQAdm\Common\Decorator\*") around the JQAdm client.
184
		 *
185
		 *  admin/jqadm/settings/decorators/global = array( 'decorator1' )
186
		 *
187
		 * This would add the decorator named "decorator1" defined by
188
		 * "\Aimeos\Admin\JQAdm\Common\Decorator\Decorator1" only to the JQAdm client.
189
		 *
190
		 * @param array List of decorator names
191
		 * @since 2021.07
192
		 * @see admin/jqadm/common/decorators/default
193
		 * @see admin/jqadm/settings/decorators/excludes
194
		 * @see admin/jqadm/settings/decorators/local
195
		 */
196
197
		/** admin/jqadm/settings/decorators/local
198
		 * Adds a list of local decorators only to the settings JQAdm client
199
		 *
200
		 * Decorators extend the functionality of a class by adding new aspects
201
		 * (e.g. log what is currently done), executing the methods of the underlying
202
		 * class only in certain conditions (e.g. only for logged in users) or
203
		 * modify what is returned to the caller.
204
		 *
205
		 * This option allows you to wrap local decorators
206
		 * ("\Aimeos\Admin\JQAdm\Settings\Decorator\*") around the JQAdm client.
207
		 *
208
		 *  admin/jqadm/settings/decorators/local = array( 'decorator2' )
209
		 *
210
		 * This would add the decorator named "decorator2" defined by
211
		 * "\Aimeos\Admin\JQAdm\Settings\Decorator\Decorator2" only to the JQAdm client.
212
		 *
213
		 * @param array List of decorator names
214
		 * @since 2021.07
215
		 * @see admin/jqadm/common/decorators/default
216
		 * @see admin/jqadm/settings/decorators/excludes
217
		 * @see admin/jqadm/settings/decorators/global
218
		 */
219
		return $this->createSubClient( 'settings/' . $type, $name );
220
	}
221
222
223
	/**
224
	 * Returns the list of sub-client names configured for the client.
225
	 *
226
	 * @return array List of JQAdm client names
227
	 */
228
	protected function getSubClientNames() : array
229
	{
230
		/** admin/jqadm/settings/subparts
231
		 * List of JQAdm sub-clients rendered within the settings section
232
		 *
233
		 * The output of the frontend is composed of the code generated by the JQAdm
234
		 * clients. Each JQAdm client can consist of serveral (or none) sub-clients
235
		 * that are responsible for rendering certain sub-parts of the output. The
236
		 * sub-clients can contain JQAdm clients themselves and therefore a
237
		 * hierarchical tree of JQAdm clients is composed. Each JQAdm client creates
238
		 * the output that is placed inside the container of its parent.
239
		 *
240
		 * At first, always the JQAdm code generated by the parent is printed, then
241
		 * the JQAdm code of its sub-clients. The order of the JQAdm sub-clients
242
		 * determines the order of the output of these sub-clients inside the parent
243
		 * container. If the configured list of clients is
244
		 *
245
		 *  array( "subclient1", "subclient2" )
246
		 *
247
		 * you can easily change the order of the output by reordering the subparts:
248
		 *
249
		 *  admin/jqadm/<clients>/subparts = array( "subclient1", "subclient2" )
250
		 *
251
		 * You can also remove one or more parts if they shouldn't be rendered:
252
		 *
253
		 *  admin/jqadm/<clients>/subparts = array( "subclient1" )
254
		 *
255
		 * As the clients only generates structural JQAdm, the layout defined via CSS
256
		 * should support adding, removing or reordering content by a fluid like
257
		 * design.
258
		 *
259
		 * @param array List of sub-client names
260
		 * @since 2021.07
261
		 */
262
		return $this->context()->config()->get( 'admin/jqadm/settings/subparts', [] );
263
	}
264
265
266
	/**
267
	 * Creates new and updates existing items using the data array
268
	 *
269
	 * @param array $data Data array
270
	 * @return \Aimeos\MShop\Locale\Item\Site\Iface New settings item object
271
	 */
272
	protected function fromArray( array $data ) : \Aimeos\MShop\Locale\Item\Site\Iface
273
	{
274
		$item = $this->context()->locale()->getSiteItem();
275
276
		$config = $data['locale.site.config'] ?? [];
277
		$config['resource']['email']['from-name'] = $data['locale.site.label'] ?? '';
278
279
		$files = (array) $this->view()->request()->getUploadedFiles();
280
281
		$item = $this->fromArrayIcon( $item, $files );
282
		$item = $this->fromArrayLogo( $item, $files );
283
284
		return $item->setConfig( array_replace_recursive( $item->getConfig(), $config ) )
285
			->setTheme( $data['locale.site.theme'] ?? '' )
286
			->setLabel( $data['locale.site.label'] ?? '' )
287
			->setCode( $data['locale.site.code'] ?? '' );
288
	}
289
290
291
	/**
292
	 * Creates new and updates existing items using the data array
293
	 *
294
	 * @param \Aimeos\MShop\Locale\Item\Site\Iface Site item object
0 ignored issues
show
Bug introduced by
The type Aimeos\Admin\JQAdm\Settings\Site 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...
295
	 * @param array $files Uploaded files
296
	 * @return \Aimeos\MShop\Locale\Item\Site\Iface New settings item object
297
	 */
298
	protected function fromArrayIcon( \Aimeos\MShop\Locale\Item\Site\Iface $item, array $files ) : \Aimeos\MShop\Locale\Item\Site\Iface
299
	{
300
		$file = $this->val( $files, 'media/icon' );
301
302
		if( $file && $file->getError() === UPLOAD_ERR_OK )
303
		{
304
			$context = $this->context();
305
			$siteId = $context->locale()->getSiteId();
306
307
			$options = $context->config()->get( 'controller/common/media/options', [] );
308
			$image = \Aimeos\MW\Media\Factory::get( $file->getStream(), $options );
309
310
			if( !in_array( $image->getMimetype(), ['image/webp', 'image/jpeg', 'image/png', 'image/gif'] ) )
311
			{
312
				$msg = $context->i18n()->dt( 'admin', 'Only .jpg, .png and .gif are allowed for icons' );
313
				throw new \Aimeos\Admin\JQAdm\Exception( $msg );
314
			}
315
316
			$filepath = $siteId . 'd/icon.' . $this->extension( $image->getMimetype() );
317
			$context->fs( 'fs-media' )->write( $filepath, $image->save() );
0 ignored issues
show
Bug introduced by
It seems like $image->save() can also be of type null; however, parameter $content of Aimeos\Base\Filesystem\Iface::write() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

317
			$context->fs( 'fs-media' )->write( $filepath, /** @scrutinizer ignore-type */ $image->save() );
Loading history...
318
319
			$item->setIcon( $filepath );
320
		}
321
322
		return $item;
323
	}
324
325
326
	/**
327
	 * Creates new and updates existing items using the data array
328
	 *
329
	 * @param \Aimeos\MShop\Locale\Item\Site\Iface Site item object
330
	 * @param array $files Uploaded files
331
	 * @return \Aimeos\MShop\Locale\Item\Site\Iface New settings item object
332
	 */
333
	protected function fromArrayLogo( \Aimeos\MShop\Locale\Item\Site\Iface $item, array $files ) : \Aimeos\MShop\Locale\Item\Site\Iface
334
	{
335
		$file = $this->val( $files, 'media/logo' );
336
337
		if( $file && $file->getError() === UPLOAD_ERR_OK )
338
		{
339
			$context = $this->context();
340
			$siteId = $context->locale()->getSiteId();
341
342
			$options = $context->config()->get( 'controller/common/media/options', [] );
343
			$image = \Aimeos\MW\Media\Factory::get( $file->getStream(), $options );
344
			$filepaths = [];
345
346
			if( !in_array( $image->getMimetype(), ['image/webp', 'image/jpeg', 'image/png', 'image/gif', 'image/svg+xml'] ) )
347
			{
348
				$msg = $context->i18n()->dt( 'admin', 'Only .jpg, .png, .gif or .svg are allowed for logos' );
349
				throw new \Aimeos\Admin\JQAdm\Exception( $msg );
350
			}
351
352
			foreach( $context->config()->get( 'admin/jqadm/settings/logo-size', ['maxwidth' => null, 'maxheight' => null] ) as $size )
353
			{
354
				$w = $size['maxwidth'] ?? null;
355
				$h = $size['maxheight'] ?? null;
356
357
				$filepath = $siteId . 'd/logo' . $w . '.' . $this->extension( $image->getMimetype() );
358
				$context->fs( 'fs-media' )->write( $filepath, $image->scale( $w, $h )->save() );
0 ignored issues
show
Bug introduced by
The method scale() does not exist on Aimeos\MW\Media\Iface. It seems like you code against a sub-type of Aimeos\MW\Media\Iface such as Aimeos\MW\Media\Image\Iface. ( Ignorable by Annotation )

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

358
				$context->fs( 'fs-media' )->write( $filepath, $image->/** @scrutinizer ignore-call */ scale( $w, $h )->save() );
Loading history...
359
				$filepaths[(int) $w ?: 1] = $filepath;
360
			}
361
362
			$item->setLogos( $filepaths );
363
		}
364
365
		return $item;
366
	}
367
368
369
	/**
370
	 * Returns the file extension for the passed mime type
371
	 *
372
	 * @param string $mimetype Mime type, e.g. "image/svg+xml"
373
	 * @return string File extension
374
	 */
375
	protected function extension( string $mimetype ) : string
376
	{
377
		switch( $mimetype )
378
		{
379
			case 'image/webp': return 'webp';
380
			case 'image/jpeg': return 'jpg';
381
			case 'image/png': return 'png';
382
			case 'image/gif': return 'gif';
383
			case 'image/svg+xml': return 'svg';
384
		}
385
386
		return 'txt';
387
	}
388
389
390
	/**
391
	 * Constructs the data array for the view from the given item
392
	 *
393
	 * @param \Aimeos\MShop\Locale\Item\Site\Iface $item Settings item object
394
	 * @return string[] Multi-dimensional associative list of item data
395
	 */
396
	protected function toArray( \Aimeos\MShop\Locale\Item\Site\Iface $item, bool $copy = false ) : array
397
	{
398
		$context = $this->context();
399
400
		if( $context->user() && !$item->getConfigValue( 'resource/email/from-email' ) )
401
		{
402
			$user = \Aimeos\MShop::create( $context, 'customer' )->get( $context->user() );
403
			$item->setConfigValue( 'resource/email/from-email', $user->getPaymentAddress()->getEmail() );
404
		}
405
406
		return [
407
			'locale.site.code' => $item->getCode(),
408
			'locale.site.icon' => $item->getIcon(),
409
			'locale.site.logo' => $item->getLogos(),
410
			'locale.site.label' => $item->getLabel(),
411
			'locale.site.theme' => $item->getTheme(),
412
			'locale.site.config' => $item->getConfig(),
413
			'locale.site.refid' => $item->getRefId(),
414
			'locale.site.ctime' => $item->getTimeCreated(),
415
			'locale.site.mtime' => $item->getTimeModified(),
416
			'locale.site.editor' => $item->editor(),
417
		];
418
	}
419
420
421
	/**
422
	 * Returns the rendered template including the view data
423
	 *
424
	 * @param \Aimeos\Base\View\Iface $view View object with data assigned
425
	 * @return string HTML output
426
	 */
427
	protected function render( \Aimeos\Base\View\Iface $view ) : string
428
	{
429
		/** admin/jqadm/settings/template-item
430
		 * Relative path to the HTML body template for the settings item.
431
		 *
432
		 * The template file contains the HTML code and processing instructions
433
		 * to generate the result shown in the body of the frontend. The
434
		 * configuration string is the path to the template file relative
435
		 * to the templates directory (usually in templates/admin/jqadm).
436
		 *
437
		 * You can overwrite the template file configuration in extensions and
438
		 * provide alternative templates. These alternative templates should be
439
		 * named like the default one but with the string "default" replaced by
440
		 * an unique name. You may use the name of your project for this. If
441
		 * you've implemented an alternative client class as well, "default"
442
		 * should be replaced by the name of the new class.
443
		 *
444
		 * @param string Relative path to the template creating the HTML code
445
		 * @since 2021.07
446
		 */
447
		$tplconf = 'admin/jqadm/settings/template-item';
448
		$default = 'settings/item';
449
450
		return $view->render( $view->config( $tplconf, $default ) );
451
	}
452
}
453