Passed
Pull Request — master (#153)
by
unknown
09:48
created

Standard::getAttributeMap()   A

Complexity

Conditions 6
Paths 12

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 7
nc 12
nop 1
dl 0
loc 14
rs 9.2222
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2012
6
 * @copyright Aimeos (aimeos.org), 2015-2021
7
 * @package Client
8
 * @subpackage Html
9
 */
10
11
12
namespace Aimeos\Client\Html\Basket\Standard;
13
14
15
use Illuminate\Support\Facades\Log;
16
use Illuminate\Http\UploadedFile;
17
18
/**
19
 * Default implementation of standard basket HTML client.
20
 *
21
 * @package Client
22
 * @subpackage Html
23
 */
24
class Standard
25
	extends \Aimeos\Client\Html\Basket\Base
26
	implements \Aimeos\Client\Html\Common\Client\Factory\Iface
27
{
28
	/** client/html/basket/subparts
29
	 * List of HTML sub-clients rendered within the basket standard section
30
	 *
31
	 * The output of the frontend is composed of the code generated by the HTML
32
	 * clients. Each HTML client can consist of serveral (or none) sub-clients
33
	 * that are responsible for rendering certain sub-parts of the output. The
34
	 * sub-clients can contain HTML clients themselves and therefore a
35
	 * hierarchical tree of HTML clients is composed. Each HTML client creates
36
	 * the output that is placed inside the container of its parent.
37
	 *
38
	 * At first, always the HTML code generated by the parent is printed, then
39
	 * the HTML code of its sub-clients. The order of the HTML sub-clients
40
	 * determines the order of the output of these sub-clients inside the parent
41
	 * container. If the configured list of clients is
42
	 *
43
	 *  array( "subclient1", "subclient2" )
44
	 *
45
	 * you can easily change the order of the output by reordering the subparts:
46
	 *
47
	 *  client/html/<clients>/subparts = array( "subclient1", "subclient2" )
48
	 *
49
	 * You can also remove one or more parts if they shouldn't be rendered:
50
	 *
51
	 *  client/html/<clients>/subparts = array( "subclient1" )
52
	 *
53
	 * As the clients only generates structural HTML, the layout defined via CSS
54
	 * should support adding, removing or reordering content by a fluid like
55
	 * design.
56
	 *
57
	 * @param array List of sub-client names
58
	 * @since 2014.03
59
	 * @category Developer
60
	 */
61
	private $subPartPath = 'client/html/basket/subparts';
62
	private $subPartNames = [];
63
	private $view;
64
65
66
	/**
67
	 * Returns the HTML code for insertion into the body.
68
	 *
69
	 * @param string $uid Unique identifier for the output if the content is placed more than once on the same page
70
	 * @return string HTML code
71
	 */
72
	public function getBody( string $uid = '' ) : string
73
	{
74
		$context = $this->getContext();
75
		$view = $this->getView();
76
77
		try
78
		{
79
			$view = $this->view = $this->view ?? $this->getObject()->addData( $view );
80
81
			$html = '';
82
			foreach( $this->getSubClients() as $subclient ) {
83
				$html .= $subclient->setView( $view )->getBody( $uid );
84
			}
85
			$view->standardBody = $html;
86
		}
87
		catch( \Aimeos\Client\Html\Exception $e )
88
		{
89
			$error = array( $context->getI18n()->dt( 'client', $e->getMessage() ) );
90
			$view->standardErrorList = array_merge( $view->get( 'standardErrorList', [] ), $error );
91
		}
92
		catch( \Aimeos\Controller\Frontend\Exception $e )
93
		{
94
			$error = array( $context->getI18n()->dt( 'controller/frontend', $e->getMessage() ) );
95
			$view->standardErrorList = array_merge( $view->get( 'standardErrorList', [] ), $error );
96
		}
97
		catch( \Aimeos\MShop\Exception $e )
98
		{
99
			$error = array( $context->getI18n()->dt( 'mshop', $e->getMessage() ) );
100
			$view->standardErrorList = array_merge( $view->get( 'standardErrorList', [] ), $error );
101
		}
102
		catch( \Exception $e )
103
		{
104
			$error = array( $context->getI18n()->dt( 'client', 'A non-recoverable error occured' ) );
105
			$view->standardErrorList = array_merge( $view->get( 'standardErrorList', [] ), $error );
106
			$this->logException( $e );
107
		}
108
109
		/** client/html/basket/template-body
110
		 * Relative path to the HTML body template of the basket standard client.
111
		 *
112
		 * The template file contains the HTML code and processing instructions
113
		 * to generate the result shown in the body of the frontend. The
114
		 * configuration string is the path to the template file relative
115
		 * to the templates directory (usually in client/html/templates).
116
		 *
117
		 * You can overwrite the template file configuration in extensions and
118
		 * provide alternative templates. These alternative templates should be
119
		 * named like the default one but with the string "standard" replaced by
120
		 * an unique name. You may use the name of your project for this. If
121
		 * you've implemented an alternative client class as well, "standard"
122
		 * should be replaced by the name of the new class.
123
		 *
124
		 * @param string Relative path to the template creating code for the HTML page body
125
		 * @since 2014.03
126
		 * @category Developer
127
		 * @see client/html/basket/template-header
128
		 */
129
		$tplconf = 'client/html/basket/template-body';
130
		$default = 'basket/standard/body-standard';
131
132
		return $view->render( $view->config( $tplconf, $default ) );
133
	}
134
135
136
	/**
137
	 * Returns the HTML string for insertion into the header.
138
	 *
139
	 * @param string $uid Unique identifier for the output if the content is placed more than once on the same page
140
	 * @return string|null String including HTML tags for the header on error
141
	 */
142
	public function getHeader( string $uid = '' ) : ?string
143
	{
144
		$view = $this->getView();
145
146
		try
147
		{
148
			$view = $this->view = $this->view ?? $this->getObject()->addData( $view );
149
150
			$html = '';
151
			foreach( $this->getSubClients() as $subclient ) {
152
				$html .= $subclient->setView( $view )->getHeader( $uid );
153
			}
154
			$view->standardHeader = $html;
155
		}
156
		catch( \Exception $e )
157
		{
158
			$this->logException( $e );
159
		}
160
161
		/** client/html/basket/template-header
162
		 * Relative path to the HTML header template of the basket standard client.
163
		 *
164
		 * The template file contains the HTML code and processing instructions
165
		 * to generate the HTML code that is inserted into the HTML page header
166
		 * of the rendered page in the frontend. The configuration string is the
167
		 * path to the template file relative to the templates directory (usually
168
		 * in client/html/templates).
169
		 *
170
		 * You can overwrite the template file configuration in extensions and
171
		 * provide alternative templates. These alternative templates should be
172
		 * named like the default one but with the string "standard" replaced by
173
		 * an unique name. You may use the name of your project for this. If
174
		 * you've implemented an alternative client class as well, "standard"
175
		 * should be replaced by the name of the new class.
176
		 *
177
		 * @param string Relative path to the template creating code for the HTML page head
178
		 * @since 2014.03
179
		 * @category Developer
180
		 * @see client/html/basket/template-body
181
		 */
182
		$tplconf = 'client/html/basket/template-header';
183
		$default = 'basket/standard/header-standard';
184
185
		return $view->render( $view->config( $tplconf, $default ) );
186
	}
187
188
189
	/**
190
	 * Returns the sub-client given by its name.
191
	 *
192
	 * @param string $type Name of the client type
193
	 * @param string|null $name Name of the sub-client (Default if null)
194
	 * @return \Aimeos\Client\Html\Iface Sub-client object
195
	 */
196
	public function getSubClient( string $type, string $name = null ) : \Aimeos\Client\Html\Iface
197
	{
198
		/** client/html/basket/standard/decorators/excludes
199
		 * Excludes decorators added by the "common" option from the basket standard html client
200
		 *
201
		 * Decorators extend the functionality of a class by adding new aspects
202
		 * (e.g. log what is currently done), executing the methods of the underlying
203
		 * class only in certain conditions (e.g. only for logged in users) or
204
		 * modify what is returned to the caller.
205
		 *
206
		 * This option allows you to remove a decorator added via
207
		 * "client/html/common/decorators/default" before they are wrapped
208
		 * around the html client.
209
		 *
210
		 *  client/html/basket/standard/decorators/excludes = array( 'decorator1' )
211
		 *
212
		 * This would remove the decorator named "decorator1" from the list of
213
		 * common decorators ("\Aimeos\Client\Html\Common\Decorator\*") added via
214
		 * "client/html/common/decorators/default" to the html client.
215
		 *
216
		 * @param array List of decorator names
217
		 * @since 2014.05
218
		 * @category Developer
219
		 * @see client/html/common/decorators/default
220
		 * @see client/html/basket/standard/decorators/global
221
		 * @see client/html/basket/standard/decorators/local
222
		 */
223
224
		/** client/html/basket/standard/decorators/global
225
		 * Adds a list of globally available decorators only to the basket standard html client
226
		 *
227
		 * Decorators extend the functionality of a class by adding new aspects
228
		 * (e.g. log what is currently done), executing the methods of the underlying
229
		 * class only in certain conditions (e.g. only for logged in users) or
230
		 * modify what is returned to the caller.
231
		 *
232
		 * This option allows you to wrap global decorators
233
		 * ("\Aimeos\Client\Html\Common\Decorator\*") around the html client.
234
		 *
235
		 *  client/html/basket/standard/decorators/global = array( 'decorator1' )
236
		 *
237
		 * This would add the decorator named "decorator1" defined by
238
		 * "\Aimeos\Client\Html\Common\Decorator\Decorator1" only to the html client.
239
		 *
240
		 * @param array List of decorator names
241
		 * @since 2014.05
242
		 * @category Developer
243
		 * @see client/html/common/decorators/default
244
		 * @see client/html/basket/standard/decorators/excludes
245
		 * @see client/html/basket/standard/decorators/local
246
		 */
247
248
		/** client/html/basket/standard/decorators/local
249
		 * Adds a list of local decorators only to the basket standard html client
250
		 *
251
		 * Decorators extend the functionality of a class by adding new aspects
252
		 * (e.g. log what is currently done), executing the methods of the underlying
253
		 * class only in certain conditions (e.g. only for logged in users) or
254
		 * modify what is returned to the caller.
255
		 *
256
		 * This option allows you to wrap local decorators
257
		 * ("\Aimeos\Client\Html\Basket\Decorator\*") around the html client.
258
		 *
259
		 *  client/html/basket/standard/decorators/local = array( 'decorator2' )
260
		 *
261
		 * This would add the decorator named "decorator2" defined by
262
		 * "\Aimeos\Client\Html\Basket\Decorator\Decorator2" only to the html client.
263
		 *
264
		 * @param array List of decorator names
265
		 * @since 2014.05
266
		 * @category Developer
267
		 * @see client/html/common/decorators/default
268
		 * @see client/html/basket/standard/decorators/excludes
269
		 * @see client/html/basket/standard/decorators/global
270
		 */
271
272
		return $this->createSubClient( 'basket/standard/' . $type, $name );
273
	}
274
275
276
	/**
277
	 * Sets the necessary parameter values in the view.
278
	 *
279
	 * A view must be available and this method doesn't generate any output
280
	 * besides setting view variables if necessary.
281
	 */
282
	public function process()
283
	{
284
		$view = $this->getView();
285
		$context = $this->getContext();
286
		$controller = \Aimeos\Controller\Frontend::create( $context, 'basket' );
287
288
		try
289
		{
290
			switch( $view->param( 'b_action' ) )
291
			{
292
				case 'add':
293
					$this->addProducts( $view );
294
					break;
295
				case 'coupon-delete':
296
					$this->deleteCoupon( $view );
297
					break;
298
				case 'delete':
299
					$this->deleteProducts( $view );
300
					break;
301
				default:
302
					$this->updateProducts( $view );
303
					$this->addCoupon( $view );
304
			}
305
306
			parent::process();
307
308
			/** client/html/basket/standard/check
309
			 * Alters the behavior of the product checks before continuing with the checkout
310
			 *
311
			 * By default, the product related checks are performed every time the basket
312
			 * is shown. They test if there are any products in the basket and execute all
313
			 * basket plugins that have been registered for the "check.before" and "check.after"
314
			 * events.
315
			 *
316
			 * Using this configuration setting, you can either disable all checks completely
317
			 * (0) or display a "Check" button instead of the "Checkout" button (2). In the
318
			 * later case, customers have to click on the "Check" button first to perform
319
			 * the checks and if everything is OK, the "Checkout" button will be displayed
320
			 * that allows the customers to continue the checkout process. If one of the
321
			 * checks fails, the customers have to fix the related basket item and must click
322
			 * on the "Check" button again before they can continue.
323
			 *
324
			 * Available values are:
325
			 *  0 = no product related checks
326
			 *  1 = checks are performed every time when the basket is displayed
327
			 *  2 = checks are performed only when clicking on the "check" button
328
			 *
329
			 * @param integer One of the allowed values (0, 1 or 2)
330
			 * @since 2016.08
331
			 * @category Developer
332
			 * @category User
333
			 */
334
			$check = (int) $view->config( 'client/html/basket/standard/check', 1 );
335
336
			switch( $check )
337
			{
338
				case 2: if( $view->param( 'b_check', 0 ) == 0 ) { break; }
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
339
				case 1: $controller->get()->check( \Aimeos\MShop\Order\Item\Base\Base::PARTS_PRODUCT );
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
340
				default: $view->standardCheckout = true;
341
			}
342
		}
343
		catch( \Aimeos\Controller\Frontend\Exception $e )
344
		{
345
			$error = array( $context->getI18n()->dt( 'controller/frontend', $e->getMessage() ) );
346
			$view->standardErrorList = array_merge( $view->get( 'standardErrorList', [] ), $error );
347
		}
348
		catch( \Aimeos\MShop\Exception $e )
349
		{
350
			$error = array( $context->getI18n()->dt( 'mshop', $e->getMessage() ) );
351
			$view->standardErrorList = array_merge( $view->get( 'standardErrorList', [] ), $error );
352
		}
353
		catch( \Exception $e )
354
		{
355
			$error = array( $context->getI18n()->dt( 'client', 'A non-recoverable error occured' ) );
356
			$view->standardErrorList = array_merge( $view->get( 'standardErrorList', [] ), $error );
357
			$this->logException( $e );
358
		}
359
360
		// store updated basket after plugins updated content and have thrown an exception
361
		$controller->save();
362
	}
363
364
365
	/**
366
	 * Returns the list of sub-client names configured for the client.
367
	 *
368
	 * @return array List of HTML client names
369
	 */
370
	protected function getSubClientNames() : array
371
	{
372
		return $this->getContext()->getConfig()->get( $this->subPartPath, $this->subPartNames );
373
	}
374
375
376
	/**
377
	 * Sets the necessary parameter values in the view.
378
	 *
379
	 * @param \Aimeos\MW\View\Iface $view The view object which generates the HTML output
380
	 * @param array &$tags Result array for the list of tags that are associated to the output
381
	 * @param string|null &$expire Result variable for the expiration date of the output (null for no expiry)
382
	 * @return \Aimeos\MW\View\Iface Modified view object
383
	 */
384
	public function addData( \Aimeos\MW\View\Iface $view, array &$tags = [], string &$expire = null ) : \Aimeos\MW\View\Iface
385
	{
386
		$context = $this->getContext();
387
		$site = $context->getLocale()->getSiteItem()->getCode();
388
389
		if( ( $params = $context->getSession()->get( 'aimeos/catalog/detail/params/last/' . $site ) ) !== null )
390
		{
391
			$target = $view->config( 'client/html/catalog/detail/url/target' );
392
			$controller = $view->config( 'client/html/catalog/detail/url/controller', 'catalog' );
393
			$action = $view->config( 'client/html/catalog/detail/url/action', 'detail' );
394
			$config = $view->config( 'client/html/catalog/detail/url/config', [] );
395
		}
396
		else
397
		{
398
			$params = $context->getSession()->get( 'aimeos/catalog/lists/params/last/' . $site, [] );
399
400
			$target = $view->config( 'client/html/catalog/lists/url/target' );
401
			$controller = $view->config( 'client/html/catalog/lists/url/controller', 'catalog' );
402
			$action = $view->config( 'client/html/catalog/lists/url/action', 'list' );
403
			$config = $view->config( 'client/html/catalog/lists/url/config', [] );
404
		}
405
406
		if( empty( $params ) === false ) {
407
			$view->standardBackUrl = $view->url( $target, $controller, $action, $params, [], $config );
408
		}
409
410
		$basket = \Aimeos\Controller\Frontend::create( $this->getContext(), 'basket' )->get();
411
412
		$view->standardBasket = $basket;
413
		$view->standardTaxRates = $this->getTaxRates( $basket );
414
		$view->standardNamedTaxes = $this->getNamedTaxes( $basket );
415
		$view->standardCostsDelivery = $this->getCostsDelivery( $basket );
416
		$view->standardCostsPayment = $this->getCostsPayment( $basket );
417
418
		return parent::addData( $view, $tags, $expire );
419
	}
420
421
422
	/**
423
	 * Adds the coupon specified by the view parameters from the basket.
424
	 *
425
	 * @param \Aimeos\MW\View\Iface $view View object
426
	 */
427
	protected function addCoupon( \Aimeos\MW\View\Iface $view )
428
	{
429
		if( ( $coupon = $view->param( 'b_coupon' ) ) != '' )
430
		{
431
			$context = $this->getContext();
432
			$cntl = \Aimeos\Controller\Frontend::create( $context, 'basket' );
433
			$code = $cntl->get()->getCoupons()->keys()->first();
434
435
			/** client/html/basket/standard/coupon/overwrite
436
			 * Replace previous coupon codes each time the user enters a new one
437
			 *
438
			 * If you want to allow only one coupon code per order and replace a
439
			 * previously entered one automatically, this configuration option
440
			 * should be set to true.
441
			 *
442
			 * @param boolean True to overwrite a previous coupon, false to keep them
443
			 * @since 2020.04
444
			 * @category Developer
445
			 * @category User
446
			 */
447
			if( $code && $context->getConfig()->get( 'client/html/basket/standard/coupon/overwrite', false ) ) {
448
				$cntl->deleteCoupon( $code );
449
			}
450
451
			$cntl->addCoupon( $coupon );
452
			$this->clearCached();
453
		}
454
	}
455
456
457
	/**
458
	 * Adds the products specified by the view parameters to the basket.
459
	 *
460
	 * @param \Aimeos\MW\View\Iface $view View object
461
	 */
462
	protected function addProducts( \Aimeos\MW\View\Iface $view )
463
	{
464
		$attrIds[] = \Aimeos\MShop::create( $this->getContext(), 'attribute' )->find( 'custom', [], 'product', 'upload' )->getId();
0 ignored issues
show
Comprehensibility Best Practice introduced by
$attrIds was never initialized. Although not strictly required by PHP, it is generally a good practice to add $attrIds = array(); before regardless.
Loading history...
465
466
		$attrIds[] = \Aimeos\MShop::create( $this->getContext(), 'attribute' )->find( 'file', [], 'product', 'upload' )->getId();
467
468
        $fs = $this->getContext()->fs( 'fs' );
469
470
        if( !$fs->has('basket-upload' ) ) {
471
            $fs->mkdir( 'basket-upload' );
472
        }
473
474
		$context = $this->getContext();
475
		$domains = ['attribute', 'media', 'price', 'product', 'text', 'custom'];
476
477
		$basketCntl = \Aimeos\Controller\Frontend::create( $context, 'basket' );
478
		$productCntl = \Aimeos\Controller\Frontend::create( $context, 'product' )->uses( $domains );
479
480
		if( ( $prodid = $view->param( 'b_prodid', '' ) ) !== '' && $view->param( 'b_quantity', 0 ) > 0 )
481
		{
482
			$basketCntl->addProduct(
483
				$productCntl->get( $prodid ),
484
				(float) $view->param( 'b_quantity', 0 ),
485
				(array) $view->param( 'b_attrvarid', [] ),
486
				$this->getAttributeMap( $view->param( 'b_attrconfid', [] ) ),
487
				array_filter( (array) $view->param( 'b_attrcustid', [] ) ),
488
				(string) $view->param( 'b_stocktype', 'default' ),
489
				(string) $view->param( 'b_supplier', '' ),
490
				$view->param( 'b_siteid' )
491
			);
492
		}
493
		else
494
		{
495
			$list = [];
496
			$entries = (array) $view->param( 'b_prod', [] );
497
498
			for($i = 0; $i < count($entries); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
499
			    $paths = [];
500
				foreach ($attrIds as $attrId) {
501
					if (isset($entries[$i]['attrcustid'][$attrId]) && is_array($entries[$i]['attrcustid'][$attrId])) {
502
						/** @var UploadedFile $file */
503
						foreach ($entries[$i]['attrcustid'][$attrId] as $file) {
504
							$filepath = 'basket-upload/' . md5($file->getFilename() . microtime(true)) . '.' . $file->extension();
505
							try {
506
								$stream = fopen($file->getRealPath(), 'r+');
507
								$fs->writes($filepath, $stream);
508
								fclose($stream);
509
							} catch (\Exception $ex) {
510
								Log::error($ex->getMessage());
511
							}
512
							$paths[] = $filepath;
513
						}
514
						$entries[$i]['attrcustid'][$attrId] = $paths;
515
					}
516
					if (isset($entries[$i]['prodid'])) {
517
						$list[] = $entries[$i]['prodid'];
518
					}
519
				}
520
            }
521
522
			foreach( $entries as $values )
523
			{
524
				if( ( $values['prodid'] ?? null ) && ( $values['quantity'] ?? 0 ) > 0 )
525
				{
526
					$basketCntl->addProduct( $productCntl->get( $values['prodid'] ),
527
						(float) ( $values['quantity'] ?? 0 ),
528
						array_filter( (array) ( $values['attrvarid'] ?? [] ) ),
529
						$this->getAttributeMap( (array) ( $values['attrconfid'] ?? [] ) ),
530
						array_filter( (array) ( $values['attrcustid'] ?? [] ) ),
531
						(string) ( $values['stocktype'] ?? 'default' ),
532
						(string) ( $values['supplier'] ?? '' ),
533
						$values['siteid'] ?? null
534
					);
535
				}
536
			}
537
		}
538
539
		$this->clearCached();
540
	}
541
542
543
	/**
544
	 * Removes the coupon specified by the view parameters from the basket.
545
	 *
546
	 * @param \Aimeos\MW\View\Iface $view View object
547
	 */
548
	protected function deleteCoupon( \Aimeos\MW\View\Iface $view )
549
	{
550
		if( ( $coupon = $view->param( 'b_coupon' ) ) != '' )
551
		{
552
			\Aimeos\Controller\Frontend::create( $this->getContext(), 'basket' )->deleteCoupon( $coupon );
553
			$this->clearCached();
554
		}
555
	}
556
557
558
	/**
559
	 * Removes the products specified by the view parameters from the basket.
560
	 *
561
	 * @param \Aimeos\MW\View\Iface $view View object
562
	 */
563
	protected function deleteProducts( \Aimeos\MW\View\Iface $view )
564
	{
565
		$controller = \Aimeos\Controller\Frontend::create( $this->getContext(), 'basket' );
566
		$products = (array) $view->param( 'b_position', [] );
567
568
		foreach( $products as $position ) {
569
			$controller->deleteProduct( $position );
570
		}
571
572
		$this->clearCached();
573
	}
574
575
576
	protected function getAttributeMap( array $values )
577
	{
578
		$list = [];
579
		$confIds = ( isset( $values['id'] ) ? array_filter( (array) $values['id'] ) : [] );
580
		$confQty = ( isset( $values['qty'] ) ? array_filter( (array) $values['qty'] ) : [] );
581
582
		foreach( $confIds as $idx => $id )
583
		{
584
			if( isset( $confQty[$idx] ) && $confQty[$idx] > 0 ) {
585
				$list[$id] = $confQty[$idx];
586
			}
587
		}
588
589
		return $list;
590
	}
591
592
593
	/**
594
	 * Edits the products specified by the view parameters to the basket.
595
	 *
596
	 * @param \Aimeos\MW\View\Iface $view View object
597
	 */
598
	protected function updateProducts( \Aimeos\MW\View\Iface $view )
599
	{
600
		$controller = \Aimeos\Controller\Frontend::create( $this->getContext(), 'basket' );
601
		$products = (array) $view->param( 'b_prod', [] );
602
603
		if( ( $position = $view->param( 'b_position', '' ) ) !== '' )
604
		{
605
			$products[] = array(
606
				'position' => $position,
607
				'quantity' => $view->param( 'b_quantity', 1 )
608
			);
609
		}
610
611
		foreach( $products as $values )
612
		{
613
			$controller->updateProduct(
614
				( isset( $values['position'] ) ? (int) $values['position'] : 0 ),
615
				( isset( $values['quantity'] ) ? (float) $values['quantity'] : 1 )
616
			);
617
		}
618
619
		$this->clearCached();
620
	}
621
}
622