Completed
Push — master ( f23e00...1d5010 )
by Aimeos
02:31
created

Standard::get()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 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-2016
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->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
		$config = $this->getContext()->getConfig();
112
113
		/** controller/frontend/basket/limit-count
114
		 * Maximum number of orders within the time frame
115
		 *
116
		 * Creating new orders is limited to avoid abuse and mitigate denial of
117
		 * service attacks. The number of orders created within the time frame
118
		 * configured by "controller/frontend/basket/limit-seconds" are counted
119
		 * before a new order of the same user (either logged in or identified
120
		 * by the IP address) is created. If the number of orders is higher than
121
		 * the configured value, an error message will be shown to the user
122
		 * instead of creating a new order.
123
		 *
124
		 * @param integer Number of orders allowed within the time frame
125
		 * @since 2017.05
126
		 * @category Developer
127
		 * @see controller/frontend/basket/limit-seconds
128
		 */
129
		$count = $config->get( 'controller/frontend/basket/limit-count', 5 );
130
131
		/** controller/frontend/basket/limit-seconds
132
		 * Order limitation time frame in seconds
133
		 *
134
		 * Creating new orders is limited to avoid abuse and mitigate denial of
135
		 * service attacks. Within the configured time frame, only a limited
136
		 * number of orders can be created. All orders of the current user
137
		 * (either logged in or identified by the IP address) within the last X
138
		 * seconds are counted. If the total value is higher then the number
139
		 * configured in "controller/frontend/basket/limit-count", an error
140
		 * message will be shown to the user instead of creating a new order.
141
		 *
142
		 * @param integer Number of seconds to check orders within
143
		 * @since 2017.05
144
		 * @category Developer
145
		 * @see controller/frontend/basket/limit-count
146
		 */
147
		$seconds = $config->get( 'controller/frontend/basket/limit-seconds', 300 );
148
149
		$search = $this->domainManager->createSearch();
150
		$expr = [
151
			$search->compare( '==', 'order.base.editor', $this->getContext()->getEditor() ),
152
			$search->compare( '>=', 'order.base.ctime', date( 'Y-m-d H:i:s', time() - $seconds ) ),
153
		];
154
		$search->setConditions( $search->combine( '&&', $expr ) );
155
		$search->setSlice( 0, 0 );
156
157
		$this->domainManager->searchItems( $search, [], $total );
158
159
		if( $total > $count ) {
160
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Temporary order limit reached' ) );
161
		}
162
163
164
		$basket = $this->get()->finish();
165
166
		$this->domainManager->begin();
167
		$this->domainManager->store( $basket );
168
		$this->domainManager->commit();
169
170
		return $basket;
171
	}
172
173
174
	/**
175
	 * Returns the order base object for the given ID
176
	 *
177
	 * @param string $id Unique ID of the order base object
178
	 * @param integer $parts Constants which parts of the order base object should be loaded
179
	 * @param boolean $default True to add default criteria (user logged in), false if not
180
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Order base object including the given parts
181
	 */
182
	public function load( $id, $parts = \Aimeos\MShop\Order\Manager\Base\Base::PARTS_ALL, $default = true )
183
	{
184
		return $this->domainManager->load( $id, $parts, false, $default );
185
	}
186
187
188
	/**
189
	 * Adds a categorized product to the basket of the user stored in the session.
190
	 *
191
	 * @param string $prodid ID of the base product to add
192
	 * @param integer $quantity Amount of products that should by added
193
	 * @param array $options Option list (unused at the moment)
194
	 * @param array $variantAttributeIds List of variant-building attribute IDs that identify a specific product
195
	 * 	in a selection products
196
	 * @param array $configAttributeIds  List of attribute IDs that doesn't identify a specific product in a
197
	 * 	selection of products but are stored together with the product (e.g. for configurable products)
198
	 * @param array $hiddenAttributeIds List of attribute IDs that should be stored along with the product in the order
199
	 * @param array $customAttributeValues Associative list of attribute IDs and arbitrary values that should be stored
200
	 * 	along with the product in the order
201
	 * @param string $stocktype Unique code of the stock type to deliver the products from
202
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If the product isn't available
203
	 */
204
	public function addProduct( $prodid, $quantity = 1, array $options = [], array $variantAttributeIds = [],
205
		array $configAttributeIds = [], array $hiddenAttributeIds = [], array $customAttributeValues = [],
206
		$stocktype = 'default' )
207
	{
208
		$context = $this->getContext();
209
		$productManager = \Aimeos\MShop\Factory::createManager( $context, 'product' );
210
		$productItem = $productManager->getItem( $prodid, array( 'media', 'supplier', 'price', 'product', 'text' ), true );
211
212
		$orderBaseProductItem = \Aimeos\MShop\Factory::createManager( $context, 'order/base/product' )->createItem();
213
		$orderBaseProductItem->copyFrom( $productItem );
214
		$orderBaseProductItem->setQuantity( $quantity );
215
		$orderBaseProductItem->setStockType( $stocktype );
216
217
		$attr = [];
218
		$prices = $productItem->getRefItems( 'price', 'default', 'default' );
219
220
		$priceManager = \Aimeos\MShop\Factory::createManager( $context, 'price' );
221
		$price = $priceManager->getLowestPrice( $prices, $quantity );
222
223
		$attr = array_merge( $attr, $this->createOrderProductAttributes( $price, $prodid, $quantity, $configAttributeIds, 'config' ) );
224
		$attr = array_merge( $attr, $this->createOrderProductAttributes( $price, $prodid, $quantity, $hiddenAttributeIds, 'hidden' ) );
225
		$attr = array_merge( $attr, $this->createOrderProductAttributes( $price, $prodid, $quantity, array_keys( $customAttributeValues ), 'custom', $customAttributeValues ) );
226
227
		// remove product rebate of original price in favor to rebates granted for the order
228
		$price->setRebate( '0.00' );
229
230
		$orderBaseProductItem->setPrice( $price );
231
		$orderBaseProductItem->setAttributes( $attr );
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 = sprintf( 'Basket item at position "%1$d" cannot be deleted manually', $position );
250
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
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 array $options Possible options are: 'stock'=>true|false
264
	 * 	The 'stock'=>false option allows adding products without being in stock.
265
	 * @param string[] $configAttributeCodes Codes of the product config attributes that should be REMOVED
266
	 */
267
	public function editProduct( $position, $quantity, array $options = [],
268
		array $configAttributeCodes = [] )
269
	{
270
		$product = $this->get()->getProduct( $position );
271
272
		if( $product->getFlags() & \Aimeos\MShop\Order\Item\Base\Product\Base::FLAG_IMMUTABLE )
273
		{
274
			$msg = sprintf( 'Basket item at position "%1$d" cannot be changed', $position );
275
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
276
		}
277
278
		$product->setQuantity( $quantity );
279
280
		$attributes = $product->getAttributes();
281
		foreach( $attributes as $key => $attribute )
282
		{
283
			if( in_array( $attribute->getCode(), $configAttributeCodes ) ) {
284
				unset( $attributes[$key] );
285
			}
286
		}
287
		$product->setAttributes( $attributes );
288
289
		$productItem = $this->getDomainItem( 'product', 'product.code', $product->getProductCode(), array( 'price', 'text' ) );
290
		$prices = $productItem->getRefItems( 'price', 'default' );
291
		$product->setPrice( $this->calcPrice( $product, $prices, $quantity ) );
292
293
		$this->get()->deleteProduct( $position );
294
		$this->get()->addProduct( $product, $position );
295
296
		$this->save();
297
	}
298
299
300
	/**
301
	 * Adds the given coupon code and updates the basket.
302
	 *
303
	 * @param string $code Coupon code entered by the user
304
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception if the coupon code is invalid or not allowed
305
	 */
306
	public function addCoupon( $code )
307
	{
308
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'coupon' );
309
		$codeManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'coupon/code' );
310
311
312
		$search = $codeManager->createSearch( true );
313
		$expr = array(
314
			$search->compare( '==', 'coupon.code.code', $code ),
315
			$search->getConditions(),
316
		);
317
		$search->setConditions( $search->combine( '&&', $expr ) );
318
		$search->setSlice( 0, 1 );
319
320
		$result = $codeManager->searchItems( $search );
321
322
		if( ( $codeItem = reset( $result ) ) === false ) {
323
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Coupon code "%1$s" is invalid or not available any more', $code ) );
324
		}
325
326
327
		$search = $manager->createSearch( true );
328
		$expr = array(
329
			$search->compare( '==', 'coupon.id', $codeItem->getParentId() ),
330
			$search->getConditions(),
331
		);
332
		$search->setConditions( $search->combine( '&&', $expr ) );
333
		$search->setSlice( 0, 1 );
334
335
		$result = $manager->searchItems( $search );
336
337
		if( ( $item = reset( $result ) ) === false ) {
338
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Coupon for code "%1$s" is not available any more', $code ) );
339
		}
340
341
342
		$provider = $manager->getProvider( $item, $code );
343
344
		if( $provider->isAvailable( $this->get() ) !== true ) {
345
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Requirements for coupon code "%1$s" aren\'t met', $code ) );
346
		}
347
348
		$provider->addCoupon( $this->get() );
349
		$this->save();
350
	}
351
352
353
	/**
354
	 * Removes the given coupon code and its effects from the basket.
355
	 *
356
	 * @param string $code Coupon code entered by the user
357
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception if the coupon code is invalid
358
	 */
359
	public function deleteCoupon( $code )
360
	{
361
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'coupon' );
362
363
		$search = $manager->createSearch();
364
		$search->setConditions( $search->compare( '==', 'coupon.code.code', $code ) );
365
		$search->setSlice( 0, 1 );
366
367
		$result = $manager->searchItems( $search );
368
369
		if( ( $item = reset( $result ) ) === false ) {
370
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Coupon code "%1$s" is invalid', $code ) );
371
		}
372
373
		$manager->getProvider( $item, $code )->deleteCoupon( $this->get() );
374
		$this->save();
375
	}
376
377
378
	/**
379
	 * Sets the address of the customer in the basket.
380
	 *
381
	 * @param string $type Address type constant from \Aimeos\MShop\Order\Item\Base\Address\Base
382
	 * @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
383
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If the billing or delivery address is not of any required type of
384
	 * 	if one of the keys is invalid when using an array with key/value pairs
385
	 */
386
	public function setAddress( $type, $value )
387
	{
388
		$address = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'order/base/address' )->createItem();
389
		$address->setType( $type );
390
391
		if( $value instanceof \Aimeos\MShop\Common\Item\Address\Iface )
392
		{
393
			$address->copyFrom( $value );
394
			$this->get()->setAddress( $address, $type );
395
		}
396
		else if( is_array( $value ) )
397
		{
398
			$this->setAddressFromArray( $address, $value );
399
			$this->get()->setAddress( $address, $type );
400
		}
401
		else if( $value === null )
402
		{
403
			$this->get()->deleteAddress( $type );
404
		}
405
		else
406
		{
407
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Invalid value for address type "%1$s"', $type ) );
408
		}
409
410
		$this->save();
411
	}
412
413
414
	/**
415
	 * Sets the delivery/payment service item based on the service ID.
416
	 *
417
	 * @param string $type Service type code like 'payment' or 'delivery'
418
	 * @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...
419
	 * @param array $attributes Associative list of key/value pairs containing the attributes selected or
420
	 * 	entered by the customer when choosing one of the delivery or payment options
421
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If there is no price to the service item attached
422
	 */
423
	public function setService( $type, $id, array $attributes = [] )
424
	{
425
		if( $id === null )
426
		{
427
			$this->get()->deleteService( $type );
428
			$this->save();
429
			return;
430
		}
431
432
		$context = $this->getContext();
433
434
		$serviceManager = \Aimeos\MShop\Factory::createManager( $context, 'service' );
435
		$serviceItem = $this->getDomainItem( 'service', 'service.id', $id, array( 'media', 'price', 'text' ) );
436
437
		$provider = $serviceManager->getProvider( $serviceItem );
438
		$result = $provider->checkConfigFE( $attributes );
439
		$unknown = array_diff_key( $attributes, $result );
440
441
		if( count( $unknown ) > 0 )
442
		{
443
			$msg = sprintf( 'Unknown attributes "%1$s"', implode( '","', array_keys( $unknown ) ) );
444
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
445
		}
446
447
		foreach( $result as $key => $value )
448
		{
449
			if( $value !== null ) {
450
				throw new \Aimeos\Controller\Frontend\Basket\Exception( $value );
451
			}
452
		}
453
454
		$orderBaseServiceManager = \Aimeos\MShop\Factory::createManager( $context, 'order/base/service' );
455
		$orderServiceItem = $orderBaseServiceManager->createItem();
456
		$orderServiceItem->copyFrom( $serviceItem );
457
458
		// remove service rebate of original price
459
		$price = $provider->calcPrice( $this->get() )->setRebate( '0.00' );
460
		$orderServiceItem->setPrice( $price );
461
462
		$provider->setConfigFE( $orderServiceItem, $attributes );
463
464
		$this->get()->setService( $orderServiceItem, $type );
465
		$this->save();
466
	}
467
468
469
	/**
470
	 * Fills the order address object with the values from the array.
471
	 *
472
	 * @param \Aimeos\MShop\Order\Item\Base\Address\Iface $address Address item to store the values into
473
	 * @param array $map Associative array of key/value pairs. The keys must be the same as when calling toArray() from
474
	 * 	an address item.
475
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception
476
	 */
477
	protected function setAddressFromArray( \Aimeos\MShop\Order\Item\Base\Address\Iface $address, array $map )
478
	{
479
		foreach( $map as $key => $value ) {
480
			$map[$key] = strip_tags( $value ); // prevent XSS
481
		}
482
483
		$errors = $address->fromArray( $map );
484
485
		if( count( $errors ) > 0 )
486
		{
487
			$msg = sprintf( 'Invalid address properties, please check your input' );
488
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg, 0, null, $errors );
489
		}
490
	}
491
}
492