Completed
Push — master ( 1b879b...45b214 )
by Aimeos
02:38
created

Standard   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 384
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
wmc 32
lcom 1
cbo 7
dl 0
loc 384
rs 9.6
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A clear() 0 7 1
A get() 0 10 2
A save() 0 8 3
A setType() 0 5 1
B addProduct() 0 32 1
A deleteProduct() 0 13 2
B editProduct() 0 31 4
B addCoupon() 0 45 4
A deleteCoupon() 0 17 2
B setAddress() 0 26 4
B setService() 0 38 4
A setAddressFromArray() 0 14 3
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
2 ignored issues
show
Coding Style introduced by
The extends keyword must be on the same line as the class name
Loading history...
Coding Style introduced by
Expected 0 spaces between "Base" and comma; 1 found
Loading history...
23
	implements Iface, \Aimeos\Controller\Frontend\Common\Iface
1 ignored issue
show
Coding Style introduced by
The implements keyword must be on the same line as the class name
Loading history...
24
{
25
	private $baskets = array();
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
	 * Adds a categorized product to the basket of the user stored in the session.
105
	 *
106
	 * @param string $prodid ID of the base product to add
107
	 * @param integer $quantity Amount of products that should by added
108
	 * @param array $options Possible options are: 'stock'=>true|false and 'variant'=>true|false
109
	 * 	The 'stock'=>false option allows adding products without being in stock.
110
	 * 	The 'variant'=>false option allows adding the selection product to the basket
111
	 * 	instead of the specific sub-product if the variant-building attribute IDs
112
	 * 	doesn't match a specific sub-product or if the attribute IDs are missing.
113
	 * @param array $variantAttributeIds List of variant-building attribute IDs that identify a specific product
114
	 * 	in a selection products
115
	 * @param array $configAttributeIds  List of attribute IDs that doesn't identify a specific product in a
116
	 * 	selection of products but are stored together with the product (e.g. for configurable products)
117
	 * @param array $hiddenAttributeIds List of attribute IDs that should be stored along with the product in the order
118
	 * @param array $customAttributeValues Associative list of attribute IDs and arbitrary values that should be stored
119
	 * 	along with the product in the order
120
	 * @param string $stocktype Unique code of the stock type to deliver the products from
121
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If the product isn't available
122
	 */
123
	public function addProduct( $prodid, $quantity = 1, array $options = array(), array $variantAttributeIds = array(),
124
		array $configAttributeIds = array(), array $hiddenAttributeIds = array(), array $customAttributeValues = array(),
125
		$stocktype = 'default' )
126
	{
127
		$context = $this->getContext();
128
		$productManager = \Aimeos\MShop\Factory::createManager( $context, 'product' );
129
		$productItem = $productManager->getItem( $prodid, array( 'media', 'supplier', 'price', 'product', 'text' ) );
130
131
		$orderBaseProductItem = \Aimeos\MShop\Factory::createManager( $context, 'order/base/product' )->createItem();
132
		$orderBaseProductItem->copyFrom( $productItem );
133
		$orderBaseProductItem->setQuantity( $quantity );
134
		$orderBaseProductItem->setStockType( $stocktype );
135
136
		$attr = array();
137
		$prices = $productItem->getRefItems( 'price', 'default', 'default' );
138
139
		$priceManager = \Aimeos\MShop\Factory::createManager( $context, 'price' );
140
		$price = $priceManager->getLowestPrice( $prices, $quantity );
141
142
		$attr = array_merge( $attr, $this->createOrderProductAttributes( $price, $prodid, $quantity, $configAttributeIds, 'config' ) );
143
		$attr = array_merge( $attr, $this->createOrderProductAttributes( $price, $prodid, $quantity, $hiddenAttributeIds, 'hidden' ) );
144
		$attr = array_merge( $attr, $this->createOrderProductAttributes( $price, $prodid, $quantity, array_keys( $customAttributeValues ), 'custom', $customAttributeValues ) );
145
146
		// remove product rebate of original price in favor to rebates granted for the order
147
		$price->setRebate( '0.00' );
148
149
		$orderBaseProductItem->setPrice( $price );
150
		$orderBaseProductItem->setAttributes( $attr );
151
152
		$this->get()->addProduct( $orderBaseProductItem );
153
		$this->save();
154
	}
155
156
157
	/**
158
	 * Deletes a product item from the basket.
159
	 *
160
	 * @param integer $position Position number (key) of the order product item
161
	 */
162
	public function deleteProduct( $position )
163
	{
164
		$product = $this->get()->getProduct( $position );
165
166
		if( $product->getFlags() === \Aimeos\MShop\Order\Item\Base\Product\Base::FLAG_IMMUTABLE )
167
		{
168
			$msg = sprintf( 'Basket item at position "%1$d" cannot be deleted manually', $position );
169
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
170
		}
171
172
		$this->get()->deleteProduct( $position );
173
		$this->save();
174
	}
175
176
177
	/**
178
	 * Edits the quantity of a product item in the basket.
179
	 *
180
	 * @param integer $position Position number (key) of the order product item
181
	 * @param integer $quantity New quantiy of the product item
182
	 * @param array $options Possible options are: 'stock'=>true|false
183
	 * 	The 'stock'=>false option allows adding products without being in stock.
184
	 * @param string[] $configAttributeCodes Codes of the product config attributes that should be REMOVED
185
	 */
186
	public function editProduct( $position, $quantity, array $options = array(),
187
		array $configAttributeCodes = array() )
188
	{
189
		$product = $this->get()->getProduct( $position );
190
191
		if( $product->getFlags() & \Aimeos\MShop\Order\Item\Base\Product\Base::FLAG_IMMUTABLE )
192
		{
193
			$msg = sprintf( 'Basket item at position "%1$d" cannot be changed', $position );
194
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
195
		}
196
197
		$product->setQuantity( $quantity );
198
199
		$attributes = $product->getAttributes();
200
		foreach( $attributes as $key => $attribute )
201
		{
202
			if( in_array( $attribute->getCode(), $configAttributeCodes ) ) {
203
				unset( $attributes[$key] );
204
			}
205
		}
206
		$product->setAttributes( $attributes );
207
208
		$productItem = $this->getDomainItem( 'product', 'product.code', $product->getProductCode(), array( 'price', 'text' ) );
209
		$prices = $productItem->getRefItems( 'price', 'default' );
210
		$product->setPrice( $this->calcPrice( $product, $prices, $quantity ) );
211
212
		$this->get()->deleteProduct( $position );
213
		$this->get()->addProduct( $product, $position );
214
215
		$this->save();
216
	}
217
218
219
	/**
220
	 * Adds the given coupon code and updates the basket.
221
	 *
222
	 * @param string $code Coupon code entered by the user
223
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception if the coupon code is invalid or not allowed
224
	 */
225
	public function addCoupon( $code )
226
	{
227
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'coupon' );
228
		$codeManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'coupon/code' );
229
230
231
		$search = $codeManager->createSearch( true );
232
		$expr = array(
233
			$search->compare( '==', 'coupon.code.code', $code ),
234
			$search->getConditions(),
235
		);
236
		$search->setConditions( $search->combine( '&&', $expr ) );
237
		$search->setSlice( 0, 1 );
238
239
		$result = $codeManager->searchItems( $search );
240
241
		if( ( $codeItem = reset( $result ) ) === false ) {
242
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Coupon code "%1$s" is invalid or not available any more', $code ) );
243
		}
244
245
246
		$search = $manager->createSearch( true );
247
		$expr = array(
248
			$search->compare( '==', 'coupon.id', $codeItem->getParentId() ),
249
			$search->getConditions(),
250
		);
251
		$search->setConditions( $search->combine( '&&', $expr ) );
252
		$search->setSlice( 0, 1 );
253
254
		$result = $manager->searchItems( $search );
255
256
		if( ( $item = reset( $result ) ) === false ) {
257
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Coupon for code "%1$s" is not available any more', $code ) );
258
		}
259
260
261
		$provider = $manager->getProvider( $item, $code );
262
263
		if( $provider->isAvailable( $this->get() ) !== true ) {
264
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Requirements for coupon code "%1$s" aren\'t met', $code ) );
265
		}
266
267
		$provider->addCoupon( $this->get() );
268
		$this->save();
269
	}
270
271
272
	/**
273
	 * Removes the given coupon code and its effects from the basket.
274
	 *
275
	 * @param string $code Coupon code entered by the user
276
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception if the coupon code is invalid
277
	 */
278
	public function deleteCoupon( $code )
279
	{
280
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'coupon' );
281
282
		$search = $manager->createSearch();
283
		$search->setConditions( $search->compare( '==', 'coupon.code.code', $code ) );
284
		$search->setSlice( 0, 1 );
285
286
		$result = $manager->searchItems( $search );
287
288
		if( ( $item = reset( $result ) ) === false ) {
289
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Coupon code "%1$s" is invalid', $code ) );
290
		}
291
292
		$manager->getProvider( $item, $code )->deleteCoupon( $this->get() );
293
		$this->save();
294
	}
295
296
297
	/**
298
	 * Sets the address of the customer in the basket.
299
	 *
300
	 * @param string $type Address type constant from \Aimeos\MShop\Order\Item\Base\Address\Base
301
	 * @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
302
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If the billing or delivery address is not of any required type of
303
	 * 	if one of the keys is invalid when using an array with key/value pairs
304
	 */
305
	public function setAddress( $type, $value )
306
	{
307
		$address = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'order/base/address' )->createItem();
308
		$address->setType( $type );
309
310
		if( $value instanceof \Aimeos\MShop\Common\Item\Address\Iface )
311
		{
312
			$address->copyFrom( $value );
313
			$this->get()->setAddress( $address, $type );
314
		}
315
		else if( is_array( $value ) )
316
		{
317
			$this->setAddressFromArray( $address, $value );
318
			$this->get()->setAddress( $address, $type );
319
		}
320
		else if( $value === null )
321
		{
322
			$this->get()->deleteAddress( $type );
323
		}
324
		else
325
		{
326
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Invalid value for address type "%1$s"', $type ) );
327
		}
328
329
		$this->save();
330
	}
331
332
333
	/**
334
	 * Sets the delivery/payment service item based on the service ID.
335
	 *
336
	 * @param string $type Service type code like 'payment' or 'delivery'
337
	 * @param string $id Unique ID of the service item
338
	 * @param array $attributes Associative list of key/value pairs containing the attributes selected or
339
	 * 	entered by the customer when choosing one of the delivery or payment options
340
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If there is no price to the service item attached
341
	 */
342
	public function setService( $type, $id, array $attributes = array() )
343
	{
344
		$context = $this->getContext();
345
346
		$serviceManager = \Aimeos\MShop\Factory::createManager( $context, 'service' );
347
		$serviceItem = $this->getDomainItem( 'service', 'service.id', $id, array( 'media', 'price', 'text' ) );
348
349
		$provider = $serviceManager->getProvider( $serviceItem );
350
		$result = $provider->checkConfigFE( $attributes );
351
		$unknown = array_diff_key( $attributes, $result );
352
353
		if( count( $unknown ) > 0 )
354
		{
355
			$msg = sprintf( 'Unknown attributes "%1$s"', implode( '","', array_keys( $unknown ) ) );
356
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
357
		}
358
359
		foreach( $result as $key => $value )
360
		{
361
			if( $value !== null ) {
362
				throw new \Aimeos\Controller\Frontend\Basket\Exception( $value );
363
			}
364
		}
365
366
		$orderBaseServiceManager = \Aimeos\MShop\Factory::createManager( $context, 'order/base/service' );
367
		$orderServiceItem = $orderBaseServiceManager->createItem();
368
		$orderServiceItem->copyFrom( $serviceItem );
369
370
		$price = $provider->calcPrice( $this->get() );
371
		// remove service rebate of original price
372
		$price->setRebate( '0.00' );
373
		$orderServiceItem->setPrice( $price );
374
375
		$provider->setConfigFE( $orderServiceItem, $attributes );
376
377
		$this->get()->setService( $orderServiceItem, $type );
378
		$this->save();
379
	}
380
381
382
	/**
383
	 * Fills the order address object with the values from the array.
384
	 *
385
	 * @param \Aimeos\MShop\Order\Item\Base\Address\Iface $address Address item to store the values into
386
	 * @param array $map Associative array of key/value pairs. The keys must be the same as when calling toArray() from
387
	 * 	an address item.
388
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception
389
	 */
390
	protected function setAddressFromArray( \Aimeos\MShop\Order\Item\Base\Address\Iface $address, array $map )
391
	{
392
		foreach( $map as $key => $value ) {
393
			$map[$key] = strip_tags( $value ); // prevent XSS
394
		}
395
396
		$errors = $address->fromArray( $map );
397
398
		if( count( $errors ) > 0 )
399
		{
400
			$msg = sprintf( 'Invalid address properties, please check your input' );
401
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg, 0, null, $errors );
402
		}
403
	}
404
}
405