Completed
Push — master ( 9bf8ab...afe8d8 )
by Aimeos
07:50
created

Standard::addCoupon()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 55
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 55
rs 9.078
c 0
b 0
f 0
cc 4
eloc 20
nc 4
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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-2017
7
 * @package Controller
8
 * @subpackage Frontend
9
 */
10
11
12
namespace Aimeos\Controller\Frontend\Basket;
13
14
15
/**
16
 * Default implementation of the basket frontend controller.
17
 *
18
 * @package Controller
19
 * @subpackage Frontend
20
 */
21
class Standard
22
	extends Base
23
	implements Iface, \Aimeos\Controller\Frontend\Common\Iface
24
{
25
	private $baskets = [];
26
	private $domainManager;
27
	private $type = 'default';
28
29
30
	/**
31
	 * Initializes the frontend controller.
32
	 *
33
	 * @param \Aimeos\MShop\Context\Item\Iface $context Object storing the required instances for manaing databases
34
	 *  connections, logger, session, etc.
35
	 */
36
	public function __construct( \Aimeos\MShop\Context\Item\Iface $context )
37
	{
38
		parent::__construct( $context );
39
40
		$this->domainManager = \Aimeos\MShop\Factory::createManager( $context, 'order/base' );
41
	}
42
43
44
	/**
45
	 * Empties the basket and removing all products, addresses, services, etc.
46
	 *
47
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object
48
	 */
49
	public function clear()
50
	{
51
		$this->baskets[$this->type] = $this->domainManager->createItem();
52
		$this->domainManager->setSession( $this->baskets[$this->type], $this->type );
53
54
		return $this;
55
	}
56
57
58
	/**
59
	 * Returns the basket object.
60
	 *
61
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Basket holding products, addresses and delivery/payment options
62
	 */
63
	public function get()
64
	{
65
		if( !isset( $this->baskets[$this->type] ) )
66
		{
67
			$this->baskets[$this->type] = $this->domainManager->getSession( $this->type );
68
			$this->checkLocale( $this->baskets[$this->type]->getLocale(), $this->type );
69
		}
70
71
		return $this->baskets[$this->type];
72
	}
73
74
75
	/**
76
	 * Explicitely persists the basket content
77
	 *
78
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object
79
	 */
80
	public function save()
81
	{
82
		if( isset( $this->baskets[$this->type] ) && $this->baskets[$this->type]->isModified() ) {
83
			$this->domainManager->setSession( $this->baskets[$this->type], $this->type );
84
		}
85
86
		return $this;
87
	}
88
89
90
	/**
91
	 * Sets the new basket type
92
	 *
93
	 * @param string $type Basket type
94
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object
95
	 */
96
	public function setType( $type )
97
	{
98
		$this->type = $type;
99
		return $this;
100
	}
101
102
103
	/**
104
	 * Creates a new order base object from the current basket
105
	 *
106
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Order base object including products, addresses and services
107
	 */
108
	public function store()
109
	{
110
		$total = 0;
111
		$context = $this->getContext();
112
		$config = $context->getConfig();
113
114
		/** controller/frontend/basket/limit-count
115
		 * Maximum number of orders within the time frame
116
		 *
117
		 * Creating new orders is limited to avoid abuse and mitigate denial of
118
		 * service attacks. The number of orders created within the time frame
119
		 * configured by "controller/frontend/basket/limit-seconds" are counted
120
		 * before a new order of the same user (either logged in or identified
121
		 * by the IP address) is created. If the number of orders is higher than
122
		 * the configured value, an error message will be shown to the user
123
		 * instead of creating a new order.
124
		 *
125
		 * @param integer Number of orders allowed within the time frame
126
		 * @since 2017.05
127
		 * @category Developer
128
		 * @see controller/frontend/basket/limit-seconds
129
		 */
130
		$count = $config->get( 'controller/frontend/basket/limit-count', 5 );
131
132
		/** controller/frontend/basket/limit-seconds
133
		 * Order limitation time frame in seconds
134
		 *
135
		 * Creating new orders is limited to avoid abuse and mitigate denial of
136
		 * service attacks. Within the configured time frame, only a limited
137
		 * number of orders can be created. All orders of the current user
138
		 * (either logged in or identified by the IP address) within the last X
139
		 * seconds are counted. If the total value is higher then the number
140
		 * configured in "controller/frontend/basket/limit-count", an error
141
		 * message will be shown to the user instead of creating a new order.
142
		 *
143
		 * @param integer Number of seconds to check orders within
144
		 * @since 2017.05
145
		 * @category Developer
146
		 * @see controller/frontend/basket/limit-count
147
		 */
148
		$seconds = $config->get( 'controller/frontend/basket/limit-seconds', 300 );
149
150
		$search = $this->domainManager->createSearch();
151
		$expr = [
152
			$search->compare( '==', 'order.base.editor', $context->getEditor() ),
153
			$search->compare( '>=', 'order.base.ctime', date( 'Y-m-d H:i:s', time() - $seconds ) ),
154
		];
155
		$search->setConditions( $search->combine( '&&', $expr ) );
156
		$search->setSlice( 0, 0 );
157
158
		$this->domainManager->searchItems( $search, [], $total );
159
160
		if( $total > $count )
161
		{
162
			$msg = $context->getI18n()->dt( 'controller/frontend', 'Temporary order limit reached' );
163
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
164
		}
165
166
167
		$basket = $this->get()->finish();
168
		$basket->setCustomerId( (string) $context->getUserId() );
169
170
		$this->domainManager->begin();
171
		$this->domainManager->store( $basket );
172
		$this->domainManager->commit();
173
174
		return $basket;
175
	}
176
177
178
	/**
179
	 * Returns the order base object for the given ID
180
	 *
181
	 * @param string $id Unique ID of the order base object
182
	 * @param integer $parts Constants which parts of the order base object should be loaded
183
	 * @param boolean $default True to add default criteria (user logged in), false if not
184
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Order base object including the given parts
185
	 */
186
	public function load( $id, $parts = \Aimeos\MShop\Order\Item\Base\Base::PARTS_ALL, $default = true )
187
	{
188
		return $this->domainManager->load( $id, $parts, false, $default );
189
	}
190
191
192
	/**
193
	 * Adds a categorized product to the basket of the user stored in the session.
194
	 *
195
	 * @param string $prodid ID of the base product to add
196
	 * @param integer $quantity Amount of products that should by added
197
	 * @param array $variantAttributeIds List of variant-building attribute IDs that identify a specific product
198
	 * 	in a selection products
199
	 * @param array $configAttributeIds  List of attribute IDs that doesn't identify a specific product in a
200
	 * 	selection of products but are stored together with the product (e.g. for configurable products)
201
	 * @param array $hiddenAttributeIds List of attribute IDs that should be stored along with the product in the order
202
	 * @param array $customAttributeValues Associative list of attribute IDs and arbitrary values that should be stored
203
	 * 	along with the product in the order
204
	 * @param string $stocktype Unique code of the stock type to deliver the products from
205
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If the product isn't available
206
	 */
207
	public function addProduct( $prodid, $quantity = 1, $stocktype = 'default', array $variantAttributeIds = [],
208
		array $configAttributeIds = [], array $hiddenAttributeIds = [], array $customAttributeValues = [] )
209
	{
210
		$attributeMap = [
211
			'custom' => array_keys( $customAttributeValues ),
212
			'config' => array_keys( $configAttributeIds ),
213
			'hidden' => $hiddenAttributeIds,
214
		];
215
		$this->checkListRef( $prodid, 'attribute', $attributeMap );
216
217
218
		$context = $this->getContext();
219
		$productManager = \Aimeos\MShop\Factory::createManager( $context, 'product' );
220
		$productItem = $productManager->getItem( $prodid, array( 'media', 'supplier', 'price', 'product', 'text' ), true );
221
		$prices = $productItem->getRefItems( 'price', 'default', 'default' );
222
223
		$orderBaseProductItem = \Aimeos\MShop\Factory::createManager( $context, 'order/base/product' )->createItem();
224
		$orderBaseProductItem->copyFrom( $productItem )->setQuantity( $quantity )->setStockType( $stocktype );
225
226
		$attr = $this->getOrderProductAttributes( 'custom', array_keys( $customAttributeValues ), $customAttributeValues );
227
		$attr = array_merge( $attr, $this->getOrderProductAttributes( 'config', array_keys( $configAttributeIds ), [], $configAttributeIds ) );
228
		$attr = array_merge( $attr, $this->getOrderProductAttributes( 'hidden', $hiddenAttributeIds ) );
229
230
		$orderBaseProductItem->setAttributes( $attr );
231
		$orderBaseProductItem->setPrice( $this->calcPrice( $orderBaseProductItem, $prices, $quantity ) );
232
233
		$this->get()->addProduct( $orderBaseProductItem );
234
		$this->save();
235
	}
236
237
238
	/**
239
	 * Deletes a product item from the basket.
240
	 *
241
	 * @param integer $position Position number (key) of the order product item
242
	 */
243
	public function deleteProduct( $position )
244
	{
245
		$product = $this->get()->getProduct( $position );
246
247
		if( $product->getFlags() === \Aimeos\MShop\Order\Item\Base\Product\Base::FLAG_IMMUTABLE )
248
		{
249
			$msg = $this->getContext()->getI18n()->dt( 'controller/frontend', 'Basket item at position "%1$d" cannot be deleted manually' );
250
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( $msg, $position ) );
251
		}
252
253
		$this->get()->deleteProduct( $position );
254
		$this->save();
255
	}
256
257
258
	/**
259
	 * Edits the quantity of a product item in the basket.
260
	 *
261
	 * @param integer $position Position number (key) of the order product item
262
	 * @param integer $quantity New quantiy of the product item
263
	 * @param string[] $configAttributeCodes Codes of the product config attributes that should be REMOVED
264
	 */
265
	public function editProduct( $position, $quantity, array $configAttributeCodes = [] )
266
	{
267
		$product = $this->get()->getProduct( $position );
268
269
		if( $product->getFlags() & \Aimeos\MShop\Order\Item\Base\Product\Base::FLAG_IMMUTABLE )
270
		{
271
			$msg = $this->getContext()->getI18n()->dt( 'controller/frontend', 'Basket item at position "%1$d" cannot be changed' );
272
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( $msg, $position ) );
273
		}
274
275
		$product->setQuantity( $quantity );
276
277
		$attributes = $product->getAttributes();
278
		foreach( $attributes as $key => $attribute )
279
		{
280
			if( in_array( $attribute->getCode(), $configAttributeCodes ) ) {
281
				unset( $attributes[$key] );
282
			}
283
		}
284
		$product->setAttributes( $attributes );
285
286
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'product' );
287
		$productItem = $manager->findItem( $product->getProductCode(), array( 'price', 'text' ), true );
288
		$product->setPrice( $this->calcPrice( $product, $productItem->getRefItems( 'price', 'default' ), $quantity ) );
289
290
		$this->get()->editProduct( $product, $position );
291
292
		$this->save();
293
	}
294
295
296
	/**
297
	 * Adds the given coupon code and updates the basket.
298
	 *
299
	 * @param string $code Coupon code entered by the user
300
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception if the coupon code is invalid or not allowed
301
	 */
302
	public function addCoupon( $code )
303
	{
304
		$context = $this->getContext();
305
306
		/** controller/frontend/basket/standard/coupon/allowed
307
		 * Number of coupon codes a customer is allowed to enter
308
		 *
309
		 * This configuration option enables shop owners to limit the number of coupon
310
		 * codes that can be added by a customer to his current basket. By default, only
311
		 * one coupon code is allowed per order.
312
		 *
313
		 * Coupon codes are valid until a payed order is placed by the customer. The
314
		 * "count" of the codes is decreased afterwards. If codes are not personalized
315
		 * the codes can be reused in the next order until their "count" reaches zero.
316
		 *
317
		 * @param integer Positive number of coupon codes including zero
318
		 * @since 2017.08
319
		 * @category User
320
		 * @category Developer
321
		 */
322
		$allowed = $context->getConfig()->get( 'controller/frontend/basket/standard/coupon/allowed', 1 );
323
324
		if( $allowed <= count( $this->get()->getCoupons() ) )
325
		{
326
			$msg = $context->getI18n()->dt( 'controller/frontend', 'Number of coupon codes exceeds the limit' );
327
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
328
		}
329
330
331
		$manager = \Aimeos\MShop\Factory::createManager( $context, 'coupon' );
332
333
		$search = $manager->createSearch();
334
		$search->setConditions( $search->compare( '==', 'coupon.code.code', $code ) );
335
		$search->setSlice( 0, 1 );
336
337
		$result = $manager->searchItems( $search );
338
339
		if( ( $item = reset( $result ) ) === false )
340
		{
341
			$msg = sprintf( $context->getI18n()->dt( 'controller/frontend', 'Coupon code "%1$s" is invalid or not available any more' ), $code );
342
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
343
		}
344
345
346
		$provider = $manager->getProvider( $item, $code );
347
348
		if( $provider->isAvailable( $this->get() ) !== true )
349
		{
350
			$msg = sprintf( $context->getI18n()->dt( 'controller/frontend', 'Requirements for coupon code "%1$s" aren\'t met' ), $code );
351
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
352
		}
353
354
		$provider->addCoupon( $this->get() );
355
		$this->save();
356
	}
357
358
359
	/**
360
	 * Removes the given coupon code and its effects from the basket.
361
	 *
362
	 * @param string $code Coupon code entered by the user
363
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception if the coupon code is invalid
364
	 */
365
	public function deleteCoupon( $code )
366
	{
367
		$context = $this->getContext();
368
		$manager = \Aimeos\MShop\Factory::createManager( $context, 'coupon' );
369
370
		$search = $manager->createSearch();
371
		$search->setConditions( $search->compare( '==', 'coupon.code.code', $code ) );
372
		$search->setSlice( 0, 1 );
373
374
		$result = $manager->searchItems( $search );
375
376
		if( ( $item = reset( $result ) ) === false )
377
		{
378
			$msg = $context->getI18n()->dt( 'controller/frontend', 'Coupon code "%1$s" is invalid' );
379
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( $msg, $code ) );
380
		}
381
382
		$manager->getProvider( $item, $code )->deleteCoupon( $this->get() );
383
		$this->save();
384
	}
385
386
387
	/**
388
	 * Sets the address of the customer in the basket.
389
	 *
390
	 * @param string $type Address type constant from \Aimeos\MShop\Order\Item\Base\Address\Base
391
	 * @param \Aimeos\MShop\Common\Item\Address\Iface|array|null $value Address object or array with key/value pairs of address or null to remove address from basket
392
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If the billing or delivery address is not of any required type of
393
	 * 	if one of the keys is invalid when using an array with key/value pairs
394
	 */
395
	public function setAddress( $type, $value )
396
	{
397
		$context = $this->getContext();
398
		$address = \Aimeos\MShop\Factory::createManager( $context, 'order/base/address' )->createItem();
399
		$address->setType( $type );
400
401
		if( $value instanceof \Aimeos\MShop\Common\Item\Address\Iface )
402
		{
403
			$address->copyFrom( $value );
404
			$this->get()->setAddress( $address, $type );
405
		}
406
		else if( is_array( $value ) )
407
		{
408
			$this->setAddressFromArray( $address, $value );
409
			$this->get()->setAddress( $address, $type );
410
		}
411
		else if( $value === null )
412
		{
413
			$this->get()->deleteAddress( $type );
414
		}
415
		else
416
		{
417
			$msg = $context->getI18n()->dt( 'controller/frontend', 'Invalid value for address type "%1$s"' );
418
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( $msg, $type ) );
419
		}
420
421
		$this->save();
422
	}
423
424
425
	/**
426
	 * Adds the delivery/payment service item based on the service ID.
427
	 *
428
	 * @param string $type Service type code like 'payment' or 'delivery'
429
	 * @param string $id|null Unique ID of the service item or null to remove it
0 ignored issues
show
Bug introduced by
There is no parameter named $id|null. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
430
	 * @param array $attributes Associative list of key/value pairs containing the attributes selected or
431
	 * 	entered by the customer when choosing one of the delivery or payment options
432
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If there is no price to the service item attached
433
	 */
434
	public function addService( $type, $id, array $attributes = [] )
435
	{
436
		$context = $this->getContext();
437
438
		$serviceManager = \Aimeos\MShop\Factory::createManager( $context, 'service' );
439
		$serviceItem = $serviceManager->getItem( $id, array( 'media', 'price', 'text' ) );
440
441
		$provider = $serviceManager->getProvider( $serviceItem, $serviceItem->getType() );
442
		$result = $provider->checkConfigFE( $attributes );
443
		$unknown = array_diff_key( $attributes, $result );
444
445
		if( count( $unknown ) > 0 )
446
		{
447
			$msg = $context->getI18n()->dt( 'controller/frontend', 'Unknown attributes "%1$s"' );
448
			$msg = sprintf( $msg, implode( '","', array_keys( $unknown ) ) );
449
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
450
		}
451
452
		foreach( $result as $key => $value )
453
		{
454
			if( $value !== null ) {
455
				throw new \Aimeos\Controller\Frontend\Basket\Exception( $value );
456
			}
457
		}
458
459
		$orderBaseServiceManager = \Aimeos\MShop\Factory::createManager( $context, 'order/base/service' );
460
		$orderServiceItem = $orderBaseServiceManager->createItem();
461
		$orderServiceItem->copyFrom( $serviceItem );
462
463
		// remove service rebate of original price
464
		$price = $provider->calcPrice( $this->get() )->setRebate( '0.00' );
465
		$orderServiceItem->setPrice( $price );
466
467
		$provider->setConfigFE( $orderServiceItem, $attributes );
468
469
		$this->get()->addService( $orderServiceItem, $type );
470
		$this->save();
471
	}
472
473
474
	/**
475
	 * Removes the delivery or payment service items from the basket
476
	 *
477
	 * @param string $type Service type code like 'payment' or 'delivery'
478
	 */
479
	public function deleteService( $type )
480
	{
481
		$this->get()->deleteService( $type );
482
		$this->save();
483
	}
484
485
486
	/**
487
	 * Fills the order address object with the values from the array.
488
	 *
489
	 * @param \Aimeos\MShop\Order\Item\Base\Address\Iface $address Address item to store the values into
490
	 * @param array $map Associative array of key/value pairs. The keys must be the same as when calling toArray() from
491
	 * 	an address item.
492
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception
493
	 */
494
	protected function setAddressFromArray( \Aimeos\MShop\Order\Item\Base\Address\Iface $address, array $map )
495
	{
496
		foreach( $map as $key => $value ) {
497
			$map[$key] = strip_tags( $value ); // prevent XSS
498
		}
499
500
		$errors = $address->fromArray( $map );
501
502
		if( count( $errors ) > 0 )
503
		{
504
			$msg = $this->getContext()->getI18n()->dt( 'controller/frontend', 'Invalid address properties, please check your input' );
505
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg, 0, null, $errors );
506
		}
507
	}
508
}
509