Completed
Push — master ( a77584...7f3f56 )
by Aimeos
03:42
created

Standard::getHeader()   A

Complexity

Conditions 4
Paths 13

Size

Total Lines 46
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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