Passed
Push — master ( fd6974...da0ae9 )
by Aimeos
07:05 queued 04:07
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 Aimeos (aimeos.org), 2015-2022
6
 * @package Client
7
 * @subpackage Html
8
 */
9
10
11
namespace Aimeos\Client\Html\Basket\Standard;
12
13
14
/**
15
 * Default implementation of standard basket HTML client.
16
 *
17
 * @package Client
18
 * @subpackage Html
19
 */
20
class Standard
21
	extends \Aimeos\Client\Html\Basket\Base
22
	implements \Aimeos\Client\Html\Common\Client\Factory\Iface
23
{
24
	/** client/html/basket/standard/name
25
	 * Class name of the used basket standard client implementation
26
	 *
27
	 * Each default HTML client can be replace by an alternative imlementation.
28
	 * To use this implementation, you have to set the last part of the class
29
	 * name as configuration value so the client factory knows which class it
30
	 * has to instantiate.
31
	 *
32
	 * For example, if the name of the default class is
33
	 *
34
	 *  \Aimeos\Client\Html\Basket\Standard\Standard
35
	 *
36
	 * and you want to replace it with your own version named
37
	 *
38
	 *  \Aimeos\Client\Html\Basket\Standard\Mybasket
39
	 *
40
	 * then you have to set the this configuration option:
41
	 *
42
	 *  client/html/basket/standard/name = Mybasket
43
	 *
44
	 * The value is the last part of your own class name and it's case sensitive,
45
	 * so take care that the configuration value is exactly named like the last
46
	 * part of the class name.
47
	 *
48
	 * The allowed characters of the class name are A-Z, a-z and 0-9. No other
49
	 * characters are possible! You should always start the last part of the class
50
	 * name with an upper case character and continue only with lower case characters
51
	 * or numbers. Avoid chamel case names like "MyBasket"!
52
	 *
53
	 * @param string Last part of the class name
54
	 * @since 2014.03
55
	 */
56
57
58
	/**
59
	 * Sets the necessary parameter values in the view.
60
	 *
61
	 * @param \Aimeos\Base\View\Iface $view The view object which generates the HTML output
62
	 * @param array &$tags Result array for the list of tags that are associated to the output
63
	 * @param string|null &$expire Result variable for the expiration date of the output (null for no expiry)
64
	 * @return \Aimeos\Base\View\Iface Modified view object
65
	 */
66
	public function data( \Aimeos\Base\View\Iface $view, array &$tags = [], string &$expire = null ) : \Aimeos\Base\View\Iface
67
	{
68
		$context = $this->context();
69
		$site = $context->locale()->getSiteItem()->getCode();
70
71
		if( !empty( $params = $context->session()->get( 'aimeos/catalog/detail/params/last/' . $site ) ) ) {
72
			$view->standardBackUrl = $view->link( 'client/html/catalog/detail/url', array_filter( $params ) );
73
		} elseif( !empty( $params = $context->session()->get( 'aimeos/catalog/lists/params/last/' . $site, [] ) ) ) {
74
			$view->standardBackUrl = $view->link( 'client/html/catalog/lists/url', array_filter( $params ) );
75
		}
76
77
		$view->standardBasket = \Aimeos\Controller\Frontend::create( $this->context(), 'basket' )->get();
78
79
		return parent::data( $view, $tags, $expire );
80
	}
81
82
83
	/**
84
	 * Sets the necessary parameter values in the view.
85
	 *
86
	 * A view must be available and this method doesn't generate any output
87
	 * besides setting view variables if necessary.
88
	 */
89
	public function init()
90
	{
91
		$view = $this->view();
92
		$context = $this->context();
93
		$controller = \Aimeos\Controller\Frontend::create( $context, 'basket' );
94
95
		try
96
		{
97
			switch( $view->param( 'b_action' ) )
98
			{
99
				case 'add':
100
					$this->addProducts( $view );
101
					break;
102
				case 'coupon-delete':
103
					$this->deleteCoupon( $view );
104
					break;
105
				case 'delete':
106
					$this->deleteProducts( $view );
107
					break;
108
				case 'save':
109
					$this->saveBasket( $view );
110
					break;
111
				default:
112
					$this->updateProducts( $view );
113
					$this->addCoupon( $view );
114
			}
115
116
			/** client/html/basket/standard/check
117
			 * Alters the behavior of the product checks before continuing with the checkout
118
			 *
119
			 * By default, the product related checks are performed every time the basket
120
			 * is shown. They test if there are any products in the basket and execute all
121
			 * basket plugins that have been registered for the "check.before" and "check.after"
122
			 * events.
123
			 *
124
			 * Using this configuration setting, you can either disable all checks completely
125
			 * (0) or display a "Check" button instead of the "Checkout" button (2). In the
126
			 * later case, customers have to click on the "Check" button first to perform
127
			 * the checks and if everything is OK, the "Checkout" button will be displayed
128
			 * that allows the customers to continue the checkout process. If one of the
129
			 * checks fails, the customers have to fix the related basket item and must click
130
			 * on the "Check" button again before they can continue.
131
			 *
132
			 * Available values are:
133
			 *  0 = no product related checks
134
			 *  1 = checks are performed every time when the basket is displayed
135
			 *  2 = checks are performed only when clicking on the "check" button
136
			 *
137
			 * @param integer One of the allowed values (0, 1 or 2)
138
			 * @since 2016.08
139
			 */
140
			$check = (int) $view->config( 'client/html/basket/standard/check', 1 );
141
142
			switch( $check )
143
			{
144
				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...
145
				case 1: $controller->get()->check( ['order/base/product'] );
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
146
				default: $view->standardCheckout = true;
147
			}
148
		}
149
		catch( \Exception $e )
150
		{
151
			$controller->save();
152
			throw $e;
153
		}
154
155
		$controller->save();
156
	}
157
158
159
	/**
160
	 * Adds the coupon specified by the view parameters from the basket.
161
	 *
162
	 * @param \Aimeos\Base\View\Iface $view View object
163
	 */
164
	protected function addCoupon( \Aimeos\Base\View\Iface $view )
165
	{
166
		if( ( $coupon = $view->param( 'b_coupon' ) ) != '' )
167
		{
168
			$context = $this->context();
169
			$cntl = \Aimeos\Controller\Frontend::create( $context, 'basket' );
170
			$code = $cntl->get()->getCoupons()->keys()->first();
171
172
			/** client/html/basket/standard/coupon/overwrite
173
			 * Replace previous coupon codes each time the user enters a new one
174
			 *
175
			 * If you want to allow only one coupon code per order and replace a
176
			 * previously entered one automatically, this configuration option
177
			 * should be set to true.
178
			 *
179
			 * @param boolean True to overwrite a previous coupon, false to keep them
180
			 * @since 2020.04
181
			 */
182
			if( $code && $context->config()->get( 'client/html/basket/standard/coupon/overwrite', false ) ) {
183
				$cntl->deleteCoupon( $code );
184
			}
185
186
			$cntl->addCoupon( $coupon );
187
			$this->clearCached();
188
		}
189
	}
190
191
192
	/**
193
	 * Adds the products specified by the view parameters to the basket.
194
	 *
195
	 * @param \Aimeos\Base\View\Iface $view View object
196
	 */
197
	protected function addProducts( \Aimeos\Base\View\Iface $view )
198
	{
199
		$context = $this->context();
200
		$domains = ['attribute', 'catalog', 'media', 'price', 'product', 'text', 'locale/site'];
201
202
		$basketCntl = \Aimeos\Controller\Frontend::create( $context, 'basket' );
203
		$productCntl = \Aimeos\Controller\Frontend::create( $context, 'product' )->uses( $domains );
204
205
		if( ( $prodid = $view->param( 'b_prodid', '' ) ) !== '' && $view->param( 'b_quantity', 0 ) > 0 )
206
		{
207
			$basketCntl->addProduct(
208
				$productCntl->get( $prodid ),
209
				(float) $view->param( 'b_quantity', 0 ),
210
				(array) $view->param( 'b_attrvarid', [] ),
211
				$this->getAttributeMap( $view->param( 'b_attrconfid', [] ) ),
212
				array_filter( (array) $view->param( 'b_attrcustid', [] ) ),
213
				(string) $view->param( 'b_stocktype', 'default' )
214
			);
215
		}
216
		else
217
		{
218
			foreach( (array) $view->param( 'b_prod', [] ) as $values )
219
			{
220
				if( ( $values['prodid'] ?? null ) && ( $values['quantity'] ?? 0 ) > 0 )
221
				{
222
					$basketCntl->addProduct( $productCntl->get( $values['prodid'] ),
223
						(float) ( $values['quantity'] ?? 0 ),
224
						array_filter( (array) ( $values['attrvarid'] ?? [] ) ),
225
						$this->getAttributeMap( (array) ( $values['attrconfid'] ?? [] ) ),
226
						array_filter( (array) ( $values['attrcustid'] ?? [] ) ),
227
						(string) ( $values['stocktype'] ?? 'default' )
228
					);
229
				}
230
			}
231
		}
232
233
		$this->clearCached();
234
	}
235
236
237
	/**
238
	 * Removes the coupon specified by the view parameters from the basket.
239
	 *
240
	 * @param \Aimeos\Base\View\Iface $view View object
241
	 */
242
	protected function deleteCoupon( \Aimeos\Base\View\Iface $view )
243
	{
244
		if( ( $coupon = $view->param( 'b_coupon' ) ) != '' )
245
		{
246
			\Aimeos\Controller\Frontend::create( $this->context(), 'basket' )->deleteCoupon( $coupon );
247
			$this->clearCached();
248
		}
249
	}
250
251
252
	/**
253
	 * Removes the products specified by the view parameters from the basket.
254
	 *
255
	 * @param \Aimeos\Base\View\Iface $view View object
256
	 */
257
	protected function deleteProducts( \Aimeos\Base\View\Iface $view )
258
	{
259
		$controller = \Aimeos\Controller\Frontend::create( $this->context(), 'basket' );
260
		$products = (array) $view->param( 'b_position', [] );
261
262
		foreach( $products as $position ) {
263
			$controller->deleteProduct( $position );
264
		}
265
266
		$this->clearCached();
267
	}
268
269
270
	protected function getAttributeMap( array $values )
271
	{
272
		$list = [];
273
		$confIds = ( isset( $values['id'] ) ? array_filter( (array) $values['id'] ) : [] );
274
		$confQty = ( isset( $values['qty'] ) ? array_filter( (array) $values['qty'] ) : [] );
275
276
		foreach( $confIds as $idx => $id )
277
		{
278
			if( isset( $confQty[$idx] ) && $confQty[$idx] > 0 ) {
279
				$list[$id] = $confQty[$idx];
280
			}
281
		}
282
283
		return $list;
284
	}
285
286
287
	/**
288
	 * Saves the basket of the user permanently
289
	 *
290
	 * @param \Aimeos\Base\View\Iface $view View object
291
	 */
292
	protected function saveBasket( \Aimeos\Base\View\Iface $view )
293
	{
294
		$context = $this->context();
295
296
		if( ( $userId = $context->user() ) === null )
297
		{
298
			$msg = $view->translate( 'client', 'You must log in first' );
299
			$view->error = array_merge( $view->get( 'error', [] ), [$msg] );
300
301
			return;
302
		}
303
304
		$manager = \Aimeos\MShop::create( $context, 'order/basket' );
305
306
		$item = $manager->create()->setId( md5( microtime( true ) . getmypid() . rand() ) )
307
			->setCustomerId( $userId )->setName( $view->get( 'b_name', date( 'Y-m-d H:i:s') ) )
308
			->setItem( \Aimeos\Controller\Frontend::create( $context, 'basket' )->get() );
309
310
		$manager->save( $item );
311
312
		$msg = $view->translate( 'client', 'Basket saved sucessfully' );
313
		$view->info = array_merge( $view->get( 'info', [] ), [$msg] );
314
	}
315
316
317
	/**
318
	 * Edits the products specified by the view parameters to the basket.
319
	 *
320
	 * @param \Aimeos\Base\View\Iface $view View object
321
	 */
322
	protected function updateProducts( \Aimeos\Base\View\Iface $view )
323
	{
324
		$controller = \Aimeos\Controller\Frontend::create( $this->context(), 'basket' );
325
		$products = (array) $view->param( 'b_prod', [] );
326
327
		if( ( $position = $view->param( 'b_position', '' ) ) !== '' )
328
		{
329
			$products[] = array(
330
				'position' => $position,
331
				'quantity' => $view->param( 'b_quantity', 1 )
332
			);
333
		}
334
335
		foreach( $products as $values )
336
		{
337
			$controller->updateProduct(
338
				( isset( $values['position'] ) ? (int) $values['position'] : 0 ),
339
				( isset( $values['quantity'] ) ? (float) $values['quantity'] : 1 )
340
			);
341
		}
342
343
		$this->clearCached();
344
	}
345
346
	/** client/html/basket/template-body
347
	 * Relative path to the HTML body template of the basket standard client.
348
	 *
349
	 * The template file contains the HTML code and processing instructions
350
	 * to generate the result shown in the body of the frontend. The
351
	 * configuration string is the path to the template file relative
352
	 * to the templates directory (usually in client/html/templates).
353
	 *
354
	 * You can overwrite the template file configuration in extensions and
355
	 * provide alternative templates. These alternative templates should be
356
	 * named like the default one but suffixed by
357
	 * an unique name. You may use the name of your project for this. If
358
	 * you've implemented an alternative client class as well, it
359
	 * should be suffixed by the name of the new class.
360
	 *
361
	 * @param string Relative path to the template creating code for the HTML page body
362
	 * @since 2014.03
363
	 * @see client/html/basket/template-header
364
	 */
365
366
	/** client/html/basket/template-header
367
	 * Relative path to the HTML header template of the basket standard client.
368
	 *
369
	 * The template file contains the HTML code and processing instructions
370
	 * to generate the HTML code that is inserted into the HTML page header
371
	 * of the rendered page in the frontend. The configuration string is the
372
	 * path to the template file relative to the templates directory (usually
373
	 * in client/html/templates).
374
	 *
375
	 * You can overwrite the template file configuration in extensions and
376
	 * provide alternative templates. These alternative templates should be
377
	 * named like the default one but suffixed by
378
	 * an unique name. You may use the name of your project for this. If
379
	 * you've implemented an alternative client class as well, it
380
	 * should be suffixed by the name of the new class.
381
	 *
382
	 * @param string Relative path to the template creating code for the HTML page head
383
	 * @since 2014.03
384
	 * @see client/html/basket/template-body
385
	 */
386
}
387