Passed
Push — master ( 183a45...3ef393 )
by Aimeos
12:53
created

Standard::getAttributeItem()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 0
dl 0
loc 8
rs 10
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-2023
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
	 */
35
36
37
	/**
38
	 * Adds the required data used in the price template
39
	 *
40
	 * @param \Aimeos\Base\View\Iface $view View object
41
	 * @return \Aimeos\Base\View\Iface View object with assigned parameters
42
	 */
43
	public function data( \Aimeos\Base\View\Iface $view ) : \Aimeos\Base\View\Iface
44
	{
45
		$context = $this->context();
46
47
		$priceTypeManager = \Aimeos\MShop::create( $context, 'price/type' );
48
		$listTypeManager = \Aimeos\MShop::create( $context, 'product/lists/type' );
49
		$currencyManager = \Aimeos\MShop::create( $context, 'locale/currency' );
50
51
		$search = $priceTypeManager->filter( true )->slice( 0, 10000 );
52
		$search->setConditions( $search->compare( '==', 'price.type.domain', 'product' ) );
53
		$search->setSortations( array( $search->sort( '+', 'price.type.position' ) ) );
54
55
		$listSearch = $listTypeManager->filter( true )->slice( 0, 10000 );
56
		$listSearch->setConditions( $listSearch->compare( '==', 'product.lists.type.domain', 'price' ) );
57
		$listSearch->setSortations( array( $listSearch->sort( '+', 'product.lists.type.position' ) ) );
58
59
		$view->priceTypes = $priceTypeManager->search( $search );
60
		$view->priceListTypes = $listTypeManager->search( $listSearch );
61
		$view->priceCurrencies = $currencyManager->search( $currencyManager->filter( true )->slice( 0, 10000 ) );
62
		$view->priceCustomItem = $this->getAttributeItem();
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
		 * @see admin/jqadm/common/decorators/default
228
		 * @see admin/jqadm/product/price/decorators/global
229
		 * @see admin/jqadm/product/price/decorators/local
230
		 */
231
232
		/** admin/jqadm/product/price/decorators/global
233
		 * Adds a list of globally available decorators only to the product JQAdm client
234
		 *
235
		 * Decorators extend the functionality of a class by adding new aspects
236
		 * (e.g. log what is currently done), executing the methods of the underlying
237
		 * class only in certain conditions (e.g. only for logged in users) or
238
		 * modify what is returned to the caller.
239
		 *
240
		 * This option allows you to wrap global decorators
241
		 * ("\Aimeos\Admin\JQAdm\Common\Decorator\*") around the JQAdm client.
242
		 *
243
		 *  admin/jqadm/product/price/decorators/global = array( 'decorator1' )
244
		 *
245
		 * This would add the decorator named "decorator1" defined by
246
		 * "\Aimeos\Admin\JQAdm\Common\Decorator\Decorator1" only to the JQAdm client.
247
		 *
248
		 * @param array List of decorator names
249
		 * @since 2016.01
250
		 * @see admin/jqadm/common/decorators/default
251
		 * @see admin/jqadm/product/price/decorators/excludes
252
		 * @see admin/jqadm/product/price/decorators/local
253
		 */
254
255
		/** admin/jqadm/product/price/decorators/local
256
		 * Adds a list of local decorators only to the product JQAdm client
257
		 *
258
		 * Decorators extend the functionality of a class by adding new aspects
259
		 * (e.g. log what is currently done), executing the methods of the underlying
260
		 * class only in certain conditions (e.g. only for logged in users) or
261
		 * modify what is returned to the caller.
262
		 *
263
		 * This option allows you to wrap local decorators
264
		 * ("\Aimeos\Admin\JQAdm\Product\Decorator\*") around the JQAdm client.
265
		 *
266
		 *  admin/jqadm/product/price/decorators/local = array( 'decorator2' )
267
		 *
268
		 * This would add the decorator named "decorator2" defined by
269
		 * "\Aimeos\Admin\JQAdm\Product\Decorator\Decorator2" only to the JQAdm client.
270
		 *
271
		 * @param array List of decorator names
272
		 * @since 2016.01
273
		 * @see admin/jqadm/common/decorators/default
274
		 * @see admin/jqadm/product/price/decorators/excludes
275
		 * @see admin/jqadm/product/price/decorators/global
276
		 */
277
		return $this->createSubClient( 'product/price/' . $type, $name );
278
	}
279
280
281
	/**
282
	 * Returns the custom price attribute item
283
	 *
284
	 * @return \Aimeos\MShop\Attribute\Item\Iface Custom price attribute item
285
	 */
286
	protected function getAttributeItem() : \Aimeos\MShop\Attribute\Item\Iface
287
	{
288
		$manager = \Aimeos\MShop::create( $this->context(), 'attribute' );
289
290
		try {
291
			return $manager->find( 'custom', [], 'product', 'price' );
292
		} catch( \Aimeos\MShop\Exception $e ) {
293
			return $manager->save( $manager->create()->setDomain( 'product' )->setType( 'price' )->setCode( 'custom' ) );
294
		}
295
	}
296
297
298
	/**
299
	 * Returns the list of sub-client names configured for the client.
300
	 *
301
	 * @return array List of JQAdm client names
302
	 */
303
	protected function getSubClientNames() : array
304
	{
305
		/** admin/jqadm/product/price/subparts
306
		 * List of JQAdm sub-clients rendered within the product price section
307
		 *
308
		 * The output of the frontend is composed of the code generated by the JQAdm
309
		 * clients. Each JQAdm client can consist of serveral (or none) sub-clients
310
		 * that are responsible for rendering certain sub-parts of the output. The
311
		 * sub-clients can contain JQAdm clients themselves and therefore a
312
		 * hierarchical tree of JQAdm clients is composed. Each JQAdm client creates
313
		 * the output that is placed inside the container of its parent.
314
		 *
315
		 * At first, always the JQAdm code generated by the parent is printed, then
316
		 * the JQAdm code of its sub-clients. The order of the JQAdm sub-clients
317
		 * determines the order of the output of these sub-clients inside the parent
318
		 * container. If the configured list of clients is
319
		 *
320
		 *  array( "subclient1", "subclient2" )
321
		 *
322
		 * you can easily change the order of the output by reordering the subparts:
323
		 *
324
		 *  admin/jqadm/<clients>/subparts = array( "subclient1", "subclient2" )
325
		 *
326
		 * You can also remove one or more parts if they shouldn't be rendered:
327
		 *
328
		 *  admin/jqadm/<clients>/subparts = array( "subclient1" )
329
		 *
330
		 * As the clients only generates structural JQAdm, the layout defined via CSS
331
		 * should support adding, removing or reordering content by a fluid like
332
		 * design.
333
		 *
334
		 * @param array List of sub-client names
335
		 * @since 2016.01
336
		 */
337
		return $this->context()->config()->get( 'admin/jqadm/product/price/subparts', [] );
338
	}
339
340
341
	/**
342
	 * Creates new and updates existing items using the data array
343
	 *
344
	 * @param \Aimeos\MShop\Product\Item\Iface $item Product item object without referenced domain items
345
	 * @param array $data Data array
346
	 * @return \Aimeos\MShop\Product\Item\Iface Modified product item
347
	 */
348
	protected function fromArray( \Aimeos\MShop\Product\Item\Iface $item, array $data ) : \Aimeos\MShop\Product\Item\Iface
349
	{
350
		$context = $this->context();
351
352
		$priceManager = \Aimeos\MShop::create( $context, 'price' );
353
		$listManager = \Aimeos\MShop::create( $context, 'product/lists' );
354
355
		$listItems = $item->getListItems( 'price', null, null, false );
356
357
358
		foreach( $data as $idx => $entry )
359
		{
360
			$id = $this->val( $entry, 'price.id', '' );
361
			$type = $this->val( $entry, 'product.lists.type', 'default' );
362
363
			$listItem = $item->getListItem( 'price', $type, $id, false ) ?: $listManager->create();
364
			$refItem = $listItem->getRefItem() ?: $priceManager->create();
365
366
			$refItem->fromArray( $entry, true );
367
			$listItem->fromArray( $entry, true )->setPosition( $idx )->setConfig( [] );
368
369
			foreach( (array) $this->val( $entry, 'config', [] ) as $cfg )
370
			{
371
				if( ( $key = trim( $cfg['key'] ?? '' ) ) !== '' ) {
372
					$listItem->setConfigValue( $key, trim( $cfg['val'] ?? '' ) );
373
				}
374
			}
375
376
			$item->addListItem( 'price', $listItem, $refItem );
377
			unset( $listItems[$listItem->getId()] );
378
		}
379
380
		return $item->deleteListItems( $listItems->toArray(), true );
381
	}
382
383
384
	/**
385
	 * Constructs the data array for the view from the given item
386
	 *
387
	 * @param \Aimeos\MShop\Product\Item\Iface $item Product item object including referenced domain items
388
	 * @param bool $copy True if items should be copied, false if not
389
	 * @return string[] Multi-dimensional associative list of item data
390
	 */
391
	protected function toArray( \Aimeos\MShop\Product\Item\Iface $item, bool $copy = false ) : array
392
	{
393
		$data = [];
394
		$siteId = $this->context()->locale()->getSiteId();
395
396
		foreach( $item->getListItems( 'price', null, null, false ) as $listItem )
397
		{
398
			if( ( $refItem = $listItem->getRefItem() ) === null ) {
399
				continue;
400
			}
401
402
			$list = $listItem->toArray( true ) + $refItem->toArray( true );
403
404
			if( $copy === true )
405
			{
406
				$list['product.lists.siteid'] = $siteId;
407
				$list['product.lists.id'] = '';
408
				$list['price.siteid'] = $siteId;
409
				$list['price.id'] = null;
410
			}
411
412
			$list['product.lists.datestart'] = str_replace( ' ', 'T', $list['product.lists.datestart'] ?? '' );
413
			$list['product.lists.dateend'] = str_replace( ' ', 'T', $list['product.lists.dateend'] ?? '' );
414
			$list['config'] = [];
415
416
			foreach( $listItem->getConfig() as $key => $value ) {
417
				$list['config'][] = ['key' => $key, 'val' => $value];
418
			}
419
420
			if( empty( $refItem->getTaxRates() ) ) {
421
				$list['price.taxrates'] = ['' => ''];
422
			}
423
424
			$data[] = $list;
425
		}
426
427
		return $data;
428
	}
429
430
431
	/**
432
	 * Returns if the prices can be chosen by the customers themselves
433
	 *
434
	 * @param \Aimeos\MShop\Product\Item\Iface $item Product item including attribute items
435
	 * @return bool True if price value can be entered by the customer, false if not
436
	 */
437
	protected function isCustom( \Aimeos\MShop\Product\Item\Iface $item ) : bool
438
	{
439
		return !$item->getRefItems( 'attribute', 'price', 'custom', false )->isEmpty();
440
	}
441
442
443
	/**
444
	 * Sets the flag if the price is customizable by the customer
445
	 *
446
	 * @param \Aimeos\MShop\Product\Item\Iface $item Product item including attribute items
447
	 * @param mixed $value Zero, empty, null or false to remove the flag, otherwise add the flag
448
	 * @return \Aimeos\MShop\Product\Item\Iface $item Modified product item
449
	 */
450
	protected function setCustom( \Aimeos\MShop\Product\Item\Iface $item, $value ) : \Aimeos\MShop\Product\Item\Iface
451
	{
452
		$attrItem = $this->getAttributeItem();
453
454
		if( $value )
455
		{
456
			if( $item->getListItem( 'attribute', 'custom', $attrItem->getId(), false ) === null )
0 ignored issues
show
Bug introduced by
It seems like $attrItem->getId() can also be of type null; however, parameter $refId of Aimeos\MShop\Common\Item...ef\Iface::getListItem() 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

456
			if( $item->getListItem( 'attribute', 'custom', /** @scrutinizer ignore-type */ $attrItem->getId(), false ) === null )
Loading history...
457
			{
458
				$listItem = \Aimeos\MShop::create( $this->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

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