Passed
Push — master ( 842212...224e9f )
by Aimeos
04:39
created

Standard::create()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 10
nc 2
nop 0
dl 0
loc 17
rs 9.9332
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), 2017-2022
6
 * @package Admin
7
 * @subpackage JQAdm
8
 */
9
10
11
namespace Aimeos\Admin\JQAdm\Product\Price;
12
13
sprintf( 'price' ); // for translation
14
15
16
/**
17
 * Default implementation of product price 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/product/price/name
27
	 * Name of the price subpart used by the JQAdm product implementation
28
	 *
29
	 * Use "Myname" if your class is named "\Aimeos\Admin\Jqadm\Product\Price\Myname".
30
	 * The name is case-sensitive and you should avoid camel case names like "MyName".
31
	 *
32
	 * @param string Last part of the JQAdm class name
33
	 * @since 2017.07
34
	 * @category Developer
35
	 */
36
37
38
	/**
39
	 * Adds the required data used in the price template
40
	 *
41
	 * @param \Aimeos\Base\View\Iface $view View object
42
	 * @return \Aimeos\Base\View\Iface View object with assigned parameters
43
	 */
44
	public function data( \Aimeos\Base\View\Iface $view ) : \Aimeos\Base\View\Iface
45
	{
46
		$context = $this->context();
47
48
		$priceTypeManager = \Aimeos\MShop::create( $context, 'price/type' );
49
		$listTypeManager = \Aimeos\MShop::create( $context, 'product/lists/type' );
50
		$currencyManager = \Aimeos\MShop::create( $context, 'locale/currency' );
51
52
		$search = $priceTypeManager->filter( true )->slice( 0, 10000 );
53
		$search->setConditions( $search->compare( '==', 'price.type.domain', 'product' ) );
54
		$search->setSortations( array( $search->sort( '+', 'price.type.position' ) ) );
55
56
		$listSearch = $listTypeManager->filter( true )->slice( 0, 10000 );
57
		$listSearch->setConditions( $listSearch->compare( '==', 'product.lists.type.domain', 'price' ) );
58
		$listSearch->setSortations( array( $listSearch->sort( '+', 'product.lists.type.position' ) ) );
59
60
		$view->priceTypes = $priceTypeManager->search( $search );
61
		$view->priceListTypes = $listTypeManager->search( $listSearch );
62
		$view->priceCurrencies = $currencyManager->search( $currencyManager->filter( true )->slice( 0, 10000 ) );
63
64
		if( $view->priceCurrencies->isEmpty() )
65
		{
66
			$msg = $context->translate( 'admin', 'No currencies available. Please enable at least one currency' );
67
			throw new \Aimeos\Admin\JQAdm\Exception( $msg );
68
		}
69
70
		return $view;
71
	}
72
73
74
	/**
75
	 * Batch update of a resource
76
	 *
77
	 * @return string|null Output to display
78
	 */
79
	public function batch() : ?string
80
	{
81
		$view = $this->view();
82
		$data = $view->param( 'price', [] );
83
84
		foreach( $view->get( 'items', [] ) as $item )
85
		{
86
			foreach( $item->getRefItems( 'price' ) as $price )
87
			{
88
				$temp = $data; $price->fromArray( $temp, true );
89
90
				if( isset( $data['valuepercent'] ) ) {
91
					$price->setValue( $price->getValue() + $price->getValue() * $data['valuepercent'] / 100 );
92
				}
93
94
				if( isset( $data['rebatepercent'] ) ) {
95
					$price->setRebate( $price->getRebate() + $price->getRebate() * $data['rebatepercent'] / 100 );
96
				}
97
98
				if( isset( $data['costspercent'] ) ) {
99
					$price->setCosts( $price->getCosts() + $price->getCosts() * $data['costspercent'] / 100 );
100
				}
101
			}
102
		}
103
104
		return null;
105
	}
106
107
108
	/**
109
	 * Copies a resource
110
	 *
111
	 * @return string|null HTML output
112
	 */
113
	public function copy() : ?string
114
	{
115
		$view = $this->object()->data( $this->view() );
116
		$view->priceCustom = $this->isCustom( $view->item );
117
		$view->priceData = $this->toArray( $view->item, true );
118
		$view->priceBody = parent::copy();
119
120
		return $this->render( $view );
121
	}
122
123
124
	/**
125
	 * Creates a new resource
126
	 *
127
	 * @return string|null HTML output
128
	 */
129
	public function create() : ?string
130
	{
131
		$view = $this->object()->data( $this->view() );
132
		$siteid = $this->context()->locale()->getSiteId();
133
		$data = $view->param( 'price', [] );
134
135
		foreach( $data as $idx => $entry )
136
		{
137
			$data[$idx]['product.lists.siteid'] = $siteid;
138
			$data[$idx]['price.siteid'] = $siteid;
139
		}
140
141
		$view->priceCustom = $this->isCustom( $view->item );
142
		$view->priceBody = parent::create();
143
		$view->priceData = $data;
144
145
		return $this->render( $view );
146
	}
147
148
149
	/**
150
	 * Deletes a resource
151
	 *
152
	 * @return string|null HTML output
153
	 */
154
	public function delete() : ?string
155
	{
156
		parent::delete();
157
158
		$item = $this->view()->item;
159
		$item->deleteListItems( $item->getListItems( 'price', null, null, false )->toArray(), true );
160
161
		return null;
162
	}
163
164
165
	/**
166
	 * Returns a single resource
167
	 *
168
	 * @return string|null HTML output
169
	 */
170
	public function get() : ?string
171
	{
172
		$view = $this->object()->data( $this->view() );
173
		$view->priceCustom = $this->isCustom( $view->item );
174
		$view->priceData = $this->toArray( $view->item );
175
		$view->priceBody = parent::get();
176
177
		return $this->render( $view );
178
	}
179
180
181
	/**
182
	 * Saves the data
183
	 *
184
	 * @return string|null HTML output
185
	 */
186
	public function save() : ?string
187
	{
188
		$view = $this->view();
189
190
		$view->item = $this->setCustom( $view->item, $view->param( 'pricecustom' ) );
191
		$view->item = $this->fromArray( $view->item, $view->param( 'price', [] ) );
192
		$view->priceBody = parent::save();
193
194
		return null;
195
	}
196
197
198
	/**
199
	 * Returns the sub-client given by its name.
200
	 *
201
	 * @param string $type Name of the client type
202
	 * @param string|null $name Name of the sub-client (Default if null)
203
	 * @return \Aimeos\Admin\JQAdm\Iface Sub-client object
204
	 */
205
	public function getSubClient( string $type, string $name = null ) : \Aimeos\Admin\JQAdm\Iface
206
	{
207
		/** admin/jqadm/product/price/decorators/excludes
208
		 * Excludes decorators added by the "common" option from the product JQAdm client
209
		 *
210
		 * Decorators extend the functionality of a class by adding new aspects
211
		 * (e.g. log what is currently done), executing the methods of the underlying
212
		 * class only in certain conditions (e.g. only for logged in users) or
213
		 * modify what is returned to the caller.
214
		 *
215
		 * This option allows you to remove a decorator added via
216
		 * "admin/jqadm/common/decorators/default" before they are wrapped
217
		 * around the JQAdm client.
218
		 *
219
		 *  admin/jqadm/product/price/decorators/excludes = array( 'decorator1' )
220
		 *
221
		 * This would remove the decorator named "decorator1" from the list of
222
		 * common decorators ("\Aimeos\Admin\JQAdm\Common\Decorator\*") added via
223
		 * "admin/jqadm/common/decorators/default" to the JQAdm client.
224
		 *
225
		 * @param array List of decorator names
226
		 * @since 2016.01
227
		 * @category Developer
228
		 * @see admin/jqadm/common/decorators/default
229
		 * @see admin/jqadm/product/price/decorators/global
230
		 * @see admin/jqadm/product/price/decorators/local
231
		 */
232
233
		/** admin/jqadm/product/price/decorators/global
234
		 * Adds a list of globally available decorators only to the product JQAdm client
235
		 *
236
		 * Decorators extend the functionality of a class by adding new aspects
237
		 * (e.g. log what is currently done), executing the methods of the underlying
238
		 * class only in certain conditions (e.g. only for logged in users) or
239
		 * modify what is returned to the caller.
240
		 *
241
		 * This option allows you to wrap global decorators
242
		 * ("\Aimeos\Admin\JQAdm\Common\Decorator\*") around the JQAdm client.
243
		 *
244
		 *  admin/jqadm/product/price/decorators/global = array( 'decorator1' )
245
		 *
246
		 * This would add the decorator named "decorator1" defined by
247
		 * "\Aimeos\Admin\JQAdm\Common\Decorator\Decorator1" only to the JQAdm client.
248
		 *
249
		 * @param array List of decorator names
250
		 * @since 2016.01
251
		 * @category Developer
252
		 * @see admin/jqadm/common/decorators/default
253
		 * @see admin/jqadm/product/price/decorators/excludes
254
		 * @see admin/jqadm/product/price/decorators/local
255
		 */
256
257
		/** admin/jqadm/product/price/decorators/local
258
		 * Adds a list of local decorators only to the product JQAdm client
259
		 *
260
		 * Decorators extend the functionality of a class by adding new aspects
261
		 * (e.g. log what is currently done), executing the methods of the underlying
262
		 * class only in certain conditions (e.g. only for logged in users) or
263
		 * modify what is returned to the caller.
264
		 *
265
		 * This option allows you to wrap local decorators
266
		 * ("\Aimeos\Admin\JQAdm\Product\Decorator\*") around the JQAdm client.
267
		 *
268
		 *  admin/jqadm/product/price/decorators/local = array( 'decorator2' )
269
		 *
270
		 * This would add the decorator named "decorator2" defined by
271
		 * "\Aimeos\Admin\JQAdm\Product\Decorator\Decorator2" only to the JQAdm client.
272
		 *
273
		 * @param array List of decorator names
274
		 * @since 2016.01
275
		 * @category Developer
276
		 * @see admin/jqadm/common/decorators/default
277
		 * @see admin/jqadm/product/price/decorators/excludes
278
		 * @see admin/jqadm/product/price/decorators/global
279
		 */
280
		return $this->createSubClient( 'product/price/' . $type, $name );
281
	}
282
283
284
	/**
285
	 * Returns the list of sub-client names configured for the client.
286
	 *
287
	 * @return array List of JQAdm client names
288
	 */
289
	protected function getSubClientNames() : array
290
	{
291
		/** admin/jqadm/product/price/subparts
292
		 * List of JQAdm sub-clients rendered within the product price section
293
		 *
294
		 * The output of the frontend is composed of the code generated by the JQAdm
295
		 * clients. Each JQAdm client can consist of serveral (or none) sub-clients
296
		 * that are responsible for rendering certain sub-parts of the output. The
297
		 * sub-clients can contain JQAdm clients themselves and therefore a
298
		 * hierarchical tree of JQAdm clients is composed. Each JQAdm client creates
299
		 * the output that is placed inside the container of its parent.
300
		 *
301
		 * At first, always the JQAdm code generated by the parent is printed, then
302
		 * the JQAdm code of its sub-clients. The order of the JQAdm sub-clients
303
		 * determines the order of the output of these sub-clients inside the parent
304
		 * container. If the configured list of clients is
305
		 *
306
		 *  array( "subclient1", "subclient2" )
307
		 *
308
		 * you can easily change the order of the output by reordering the subparts:
309
		 *
310
		 *  admin/jqadm/<clients>/subparts = array( "subclient1", "subclient2" )
311
		 *
312
		 * You can also remove one or more parts if they shouldn't be rendered:
313
		 *
314
		 *  admin/jqadm/<clients>/subparts = array( "subclient1" )
315
		 *
316
		 * As the clients only generates structural JQAdm, the layout defined via CSS
317
		 * should support adding, removing or reordering content by a fluid like
318
		 * design.
319
		 *
320
		 * @param array List of sub-client names
321
		 * @since 2016.01
322
		 * @category Developer
323
		 */
324
		return $this->context()->config()->get( 'admin/jqadm/product/price/subparts', [] );
325
	}
326
327
328
	/**
329
	 * Creates new and updates existing items using the data array
330
	 *
331
	 * @param \Aimeos\MShop\Product\Item\Iface $item Product item object without referenced domain items
332
	 * @param array $data Data array
333
	 * @return \Aimeos\MShop\Product\Item\Iface Modified product item
334
	 */
335
	protected function fromArray( \Aimeos\MShop\Product\Item\Iface $item, array $data ) : \Aimeos\MShop\Product\Item\Iface
336
	{
337
		$context = $this->context();
338
339
		$priceManager = \Aimeos\MShop::create( $context, 'price' );
340
		$listManager = \Aimeos\MShop::create( $context, 'product/lists' );
341
342
		$listItems = $item->getListItems( 'price', null, null, false );
343
344
345
		foreach( $data as $idx => $entry )
346
		{
347
			$id = $this->val( $entry, 'price.id', '' );
348
			$type = $this->val( $entry, 'product.lists.type', 'default' );
349
350
			$listItem = $item->getListItem( 'price', $type, $id, false ) ?: $listManager->create();
351
			$refItem = $listItem->getRefItem() ?: $priceManager->create();
352
353
			$refItem->fromArray( $entry, true );
354
			$listItem->fromArray( $entry, true )->setPosition( $idx )->setConfig( [] );
355
356
			foreach( (array) $this->val( $entry, 'config', [] ) as $cfg )
357
			{
358
				if( ( $key = trim( $cfg['key'] ?? '' ) ) !== '' ) {
359
					$listItem->setConfigValue( $key, trim( $cfg['val'] ?? '' ) );
360
				}
361
			}
362
363
			$item->addListItem( 'price', $listItem, $refItem );
364
			unset( $listItems[$listItem->getId()] );
365
		}
366
367
		return $item->deleteListItems( $listItems->toArray(), true );
368
	}
369
370
371
	/**
372
	 * Constructs the data array for the view from the given item
373
	 *
374
	 * @param \Aimeos\MShop\Product\Item\Iface $item Product item object including referenced domain items
375
	 * @param bool $copy True if items should be copied, false if not
376
	 * @return string[] Multi-dimensional associative list of item data
377
	 */
378
	protected function toArray( \Aimeos\MShop\Product\Item\Iface $item, bool $copy = false ) : array
379
	{
380
		$data = [];
381
		$siteId = $this->context()->locale()->getSiteId();
382
383
		foreach( $item->getListItems( 'price', null, null, false ) as $listItem )
384
		{
385
			if( ( $refItem = $listItem->getRefItem() ) === null ) {
386
				continue;
387
			}
388
389
			$list = $listItem->toArray( true ) + $refItem->toArray( true );
390
391
			if( $copy === true )
392
			{
393
				$list['product.lists.siteid'] = $siteId;
394
				$list['product.lists.id'] = '';
395
				$list['price.siteid'] = $siteId;
396
				$list['price.id'] = null;
397
			}
398
399
			$list['product.lists.datestart'] = str_replace( ' ', 'T', $list['product.lists.datestart'] ?? '' );
400
			$list['product.lists.dateend'] = str_replace( ' ', 'T', $list['product.lists.dateend'] ?? '' );
401
			$list['config'] = [];
402
403
			foreach( $listItem->getConfig() as $key => $value ) {
404
				$list['config'][] = ['key' => $key, 'val' => $value];
405
			}
406
407
			if( empty( $refItem->getTaxRates() ) ) {
408
				$list['price.taxrates'] = ['' => ''];
409
			}
410
411
			$data[] = $list;
412
		}
413
414
		return $data;
415
	}
416
417
418
	/**
419
	 * Returns if the prices can be chosen by the customers themselves
420
	 *
421
	 * @param \Aimeos\MShop\Product\Item\Iface $item Product item including attribute items
422
	 * @return bool True if price value can be entered by the customer, false if not
423
	 */
424
	protected function isCustom( \Aimeos\MShop\Product\Item\Iface $item ) : bool
425
	{
426
		return !$item->getRefItems( 'attribute', 'price', 'custom', false )->isEmpty();
427
	}
428
429
430
	/**
431
	 * Sets the flag if the price is customizable by the customer
432
	 *
433
	 * @param \Aimeos\MShop\Product\Item\Iface $item Product item including attribute items
434
	 * @param mixed $value Zero, empty, null or false to remove the flag, otherwise add the flag
435
	 * @return \Aimeos\MShop\Product\Item\Iface $item Modified product item
436
	 */
437
	protected function setCustom( \Aimeos\MShop\Product\Item\Iface $item, $value ) : \Aimeos\MShop\Product\Item\Iface
438
	{
439
		$context = $this->context();
440
441
		try
442
		{
443
			$attrManager = \Aimeos\MShop::create( $context, 'attribute' );
444
			$attrItem = $attrManager->find( 'custom', [], 'product', 'price' );
445
		}
446
		catch( \Aimeos\MShop\Exception $e )
447
		{
448
			$attrItem = $attrManager->create()->setDomain( 'product' )->setType( 'price' )->setCode( 'custom' );
449
			$attrItem = $attrManager->save( $attrItem );
450
		}
451
452
		if( $value )
453
		{
454
			if( $item->getListItem( 'attribute', 'custom', $attrItem->getId(), false ) === null )
455
			{
456
				$listItem = \Aimeos\MShop::create( $context, 'product' )->createListItem();
0 ignored issues
show
Bug introduced by
The method createListItem() does not exist on Aimeos\MShop\Common\Manager\Iface. Did you maybe mean create()? ( Ignorable by Annotation )

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

456
				$listItem = \Aimeos\MShop::create( $context, 'product' )->/** @scrutinizer ignore-call */ createListItem();

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...
457
				$item = $item->addListItem( 'attribute', $listItem->setType( 'custom' ), $attrItem );
458
			}
459
		}
460
		else
461
		{
462
			if( ( $litem = $item->getListItem( 'attribute', 'custom', $attrItem->getId(), false ) ) !== null ) {
463
				$item = $item->deleteListItem( 'attribute', $litem );
464
			}
465
		}
466
467
		return $item;
468
	}
469
470
471
	/**
472
	 * Returns the rendered template including the view data
473
	 *
474
	 * @param \Aimeos\Base\View\Iface $view View object with data assigned
475
	 * @return string HTML output
476
	 */
477
	protected function render( \Aimeos\Base\View\Iface $view ) : string
478
	{
479
		/** admin/jqadm/product/price/template-item
480
		 * Relative path to the HTML body template of the price subpart for products.
481
		 *
482
		 * The template file contains the HTML code and processing instructions
483
		 * to generate the result shown in the body of the frontend. The
484
		 * configuration string is the path to the template file relative
485
		 * to the templates directory (usually in admin/jqadm/templates).
486
		 *
487
		 * You can overwrite the template file configuration in extensions and
488
		 * provide alternative templates. These alternative templates should be
489
		 * named like the default one but with the string "default" replaced by
490
		 * an unique name. You may use the name of your project for this. If
491
		 * you've implemented an alternative client class as well, "default"
492
		 * should be replaced by the name of the new class.
493
		 *
494
		 * @param string Relative path to the template creating the HTML code
495
		 * @since 2016.04
496
		 * @category Developer
497
		 */
498
		$tplconf = 'admin/jqadm/product/price/template-item';
499
		$default = 'product/item-price';
500
501
		return $view->render( $view->config( $tplconf, $default ) );
502
	}
503
}
504