Passed
Push — master ( d1d93a...135f5f )
by Aimeos
04:03
created

Standard::deleteCoupon()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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