Completed
Push — master ( b46c0d...fe698d )
by Aimeos
06:27
created

Standard::addBundleProducts()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 39
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 39
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 23
nc 8
nop 4
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 $basket;
26
	private $domainManager;
27
28
29
	/**
30
	 * Initializes the frontend controller.
31
	 *
32
	 * @param \Aimeos\MShop\Context\Item\Iface $context Object storing the required instances for manaing databases
33
	 *  connections, logger, session, etc.
34
	 */
35
	public function __construct( \Aimeos\MShop\Context\Item\Iface $context )
36
	{
37
		parent::__construct( $context );
38
39
		$this->domainManager = \Aimeos\MShop\Factory::createManager( $context, 'order/base' );
40
		$this->basket = $this->domainManager->getSession();
41
42
		$this->checkLocale();
43
	}
44
45
46
	/**
47
	 * Empties the basket and removing all products, addresses, services, etc.
48
	 */
49
	public function clear()
50
	{
51
		$this->basket = $this->domainManager->createItem();
52
		$this->domainManager->setSession( $this->basket );
53
	}
54
55
56
	/**
57
	 * Returns the basket object.
58
	 *
59
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Basket holding products, addresses and delivery/payment options
60
	 */
61
	public function get()
62
	{
63
		return $this->basket;
64
	}
65
66
67
	/**
68
	 * Explicitely persists the basket content
69
	 */
70
	public function save()
71
	{
72
		if( $this->basket->isModified() ) {
73
			$this->domainManager->setSession( $this->basket );
74
		}
75
	}
76
77
78
	/**
79
	 * Adds a categorized product to the basket of the user stored in the session.
80
	 *
81
	 * @param string $prodid ID of the base product to add
82
	 * @param integer $quantity Amount of products that should by added
83
	 * @param array $options Possible options are: 'stock'=>true|false and 'variant'=>true|false
84
	 * 	The 'stock'=>false option allows adding products without being in stock.
85
	 * 	The 'variant'=>false option allows adding the selection product to the basket
86
	 * 	instead of the specific sub-product if the variant-building attribute IDs
87
	 * 	doesn't match a specific sub-product or if the attribute IDs are missing.
88
	 * @param array $variantAttributeIds List of variant-building attribute IDs that identify a specific product
89
	 * 	in a selection products
90
	 * @param array $configAttributeIds  List of attribute IDs that doesn't identify a specific product in a
91
	 * 	selection of products but are stored together with the product (e.g. for configurable products)
92
	 * @param array $hiddenAttributeIds List of attribute IDs that should be stored along with the product in the order
93
	 * @param array $customAttributeValues Associative list of attribute IDs and arbitrary values that should be stored
94
	 * 	along with the product in the order
95
	 * @param string $stocktype Unique code of the stock type to deliver the products from
96
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If the product isn't available
97
	 */
98
	public function addProduct( $prodid, $quantity = 1, array $options = array(), array $variantAttributeIds = array(),
99
		array $configAttributeIds = array(), array $hiddenAttributeIds = array(), array $customAttributeValues = array(),
100
		$stocktype = 'default' )
101
	{
102
		$context = $this->getContext();
103
		$productManager = \Aimeos\MShop\Factory::createManager( $context, 'product' );
104
		$productItem = $productManager->getItem( $prodid, array( 'media', 'supplier', 'price', 'product', 'text' ) );
105
106
		$orderBaseProductItem = \Aimeos\MShop\Factory::createManager( $context, 'order/base/product' )->createItem();
107
		$orderBaseProductItem->copyFrom( $productItem );
108
		$orderBaseProductItem->setQuantity( $quantity );
109
		$orderBaseProductItem->setStockType( $stocktype );
110
111
		$attr = array();
112
		$prices = $productItem->getRefItems( 'price', 'default', 'default' );
113
114
		$priceManager = \Aimeos\MShop\Factory::createManager( $context, 'price' );
115
		$price = $priceManager->getLowestPrice( $prices, $quantity );
116
117
		$attr = array_merge( $attr, $this->createOrderProductAttributes( $price, $prodid, $quantity, $configAttributeIds, 'config' ) );
118
		$attr = array_merge( $attr, $this->createOrderProductAttributes( $price, $prodid, $quantity, $hiddenAttributeIds, 'hidden' ) );
119
		$attr = array_merge( $attr, $this->createOrderProductAttributes( $price, $prodid, $quantity, array_keys( $customAttributeValues ), 'custom', $customAttributeValues ) );
120
121
		// remove product rebate of original price in favor to rebates granted for the order
122
		$price->setRebate( '0.00' );
123
124
		$orderBaseProductItem->setPrice( $price );
125
		$orderBaseProductItem->setAttributes( $attr );
126
127
		$this->basket->addProduct( $orderBaseProductItem );
128
129
		$this->domainManager->setSession( $this->basket );
130
	}
131
132
133
	/**
134
	 * Deletes a product item from the basket.
135
	 *
136
	 * @param integer $position Position number (key) of the order product item
137
	 */
138
	public function deleteProduct( $position )
139
	{
140
		$product = $this->basket->getProduct( $position );
141
142
		if( $product->getFlags() === \Aimeos\MShop\Order\Item\Base\Product\Base::FLAG_IMMUTABLE )
143
		{
144
			$msg = sprintf( 'Basket item at position "%1$d" cannot be deleted manually', $position );
145
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
146
		}
147
148
		$this->basket->deleteProduct( $position );
149
		$this->domainManager->setSession( $this->basket );
150
	}
151
152
153
	/**
154
	 * Edits the quantity of a product item in the basket.
155
	 *
156
	 * @param integer $position Position number (key) of the order product item
157
	 * @param integer $quantity New quantiy of the product item
158
	 * @param array $options Possible options are: 'stock'=>true|false
159
	 * 	The 'stock'=>false option allows adding products without being in stock.
160
	 * @param string[] $configAttributeCodes Codes of the product config attributes that should be REMOVED
161
	 */
162
	public function editProduct( $position, $quantity, array $options = array(),
163
		array $configAttributeCodes = array() )
164
	{
165
		$product = $this->basket->getProduct( $position );
166
167
		if( $product->getFlags() & \Aimeos\MShop\Order\Item\Base\Product\Base::FLAG_IMMUTABLE )
168
		{
169
			$msg = sprintf( 'Basket item at position "%1$d" cannot be changed', $position );
170
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
171
		}
172
173
		$product->setQuantity( $quantity );
174
175
		$attributes = $product->getAttributes();
176
		foreach( $attributes as $key => $attribute )
177
		{
178
			if( in_array( $attribute->getCode(), $configAttributeCodes ) ) {
179
				unset( $attributes[$key] );
180
			}
181
		}
182
		$product->setAttributes( $attributes );
183
184
		$productItem = $this->getDomainItem( 'product', 'product.code', $product->getProductCode(), array( 'price', 'text' ) );
185
		$prices = $productItem->getRefItems( 'price', 'default' );
186
		$product->setPrice( $this->calcPrice( $product, $prices, $quantity ) );
187
188
		$this->basket->deleteProduct( $position );
189
		$this->basket->addProduct( $product, $position );
190
191
		$this->domainManager->setSession( $this->basket );
192
	}
193
194
195
	/**
196
	 * Adds the given coupon code and updates the basket.
197
	 *
198
	 * @param string $code Coupon code entered by the user
199
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception if the coupon code is invalid or not allowed
200
	 */
201
	public function addCoupon( $code )
202
	{
203
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'coupon' );
204
		$codeManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'coupon/code' );
205
206
207
		$search = $codeManager->createSearch( true );
208
		$expr = array(
209
			$search->compare( '==', 'coupon.code.code', $code ),
210
			$search->getConditions(),
211
		);
212
		$search->setConditions( $search->combine( '&&', $expr ) );
213
		$search->setSlice( 0, 1 );
214
215
		$result = $codeManager->searchItems( $search );
216
217
		if( ( $codeItem = reset( $result ) ) === false ) {
218
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Coupon code "%1$s" is invalid or not available any more', $code ) );
219
		}
220
221
222
		$search = $manager->createSearch( true );
223
		$expr = array(
224
			$search->compare( '==', 'coupon.id', $codeItem->getParentId() ),
225
			$search->getConditions(),
226
		);
227
		$search->setConditions( $search->combine( '&&', $expr ) );
228
		$search->setSlice( 0, 1 );
229
230
		$result = $manager->searchItems( $search );
231
232
		if( ( $item = reset( $result ) ) === false ) {
233
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Coupon for code "%1$s" is not available any more', $code ) );
234
		}
235
236
237
		$provider = $manager->getProvider( $item, $code );
238
239
		if( $provider->isAvailable( $this->basket ) !== true ) {
240
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Requirements for coupon code "%1$s" aren\'t met', $code ) );
241
		}
242
243
		$provider->addCoupon( $this->basket );
244
		$this->domainManager->setSession( $this->basket );
245
	}
246
247
248
	/**
249
	 * Removes the given coupon code and its effects from the basket.
250
	 *
251
	 * @param string $code Coupon code entered by the user
252
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception if the coupon code is invalid
253
	 */
254
	public function deleteCoupon( $code )
255
	{
256
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'coupon' );
257
258
		$search = $manager->createSearch();
259
		$search->setConditions( $search->compare( '==', 'coupon.code.code', $code ) );
260
		$search->setSlice( 0, 1 );
261
262
		$result = $manager->searchItems( $search );
263
264
		if( ( $item = reset( $result ) ) === false ) {
265
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Coupon code "%1$s" is invalid', $code ) );
266
		}
267
268
		$manager->getProvider( $item, $code )->deleteCoupon( $this->basket );
269
		$this->domainManager->setSession( $this->basket );
270
	}
271
272
273
	/**
274
	 * Sets the address of the customer in the basket.
275
	 *
276
	 * @param string $type Address type constant from \Aimeos\MShop\Order\Item\Base\Address\Base
277
	 * @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
278
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If the billing or delivery address is not of any required type of
279
	 * 	if one of the keys is invalid when using an array with key/value pairs
280
	 */
281
	public function setAddress( $type, $value )
282
	{
283
		$address = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'order/base/address' )->createItem();
284
		$address->setType( $type );
285
286
		if( $value instanceof \Aimeos\MShop\Common\Item\Address\Iface )
287
		{
288
			$address->copyFrom( $value );
289
			$this->basket->setAddress( $address, $type );
290
		}
291
		else if( is_array( $value ) )
292
		{
293
			$this->setAddressFromArray( $address, $value );
294
			$this->basket->setAddress( $address, $type );
295
		}
296
		else if( $value === null )
297
		{
298
			$this->basket->deleteAddress( $type );
299
		}
300
		else
301
		{
302
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Invalid value for address type "%1$s"', $type ) );
303
		}
304
305
		$this->domainManager->setSession( $this->basket );
306
	}
307
308
309
	/**
310
	 * Sets the delivery/payment service item based on the service ID.
311
	 *
312
	 * @param string $type Service type code like 'payment' or 'delivery'
313
	 * @param string $id Unique ID of the service item
314
	 * @param array $attributes Associative list of key/value pairs containing the attributes selected or
315
	 * 	entered by the customer when choosing one of the delivery or payment options
316
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If there is no price to the service item attached
317
	 */
318
	public function setService( $type, $id, array $attributes = array() )
319
	{
320
		$context = $this->getContext();
321
322
		$serviceManager = \Aimeos\MShop\Factory::createManager( $context, 'service' );
323
		$serviceItem = $this->getDomainItem( 'service', 'service.id', $id, array( 'media', 'price', 'text' ) );
324
325
		$provider = $serviceManager->getProvider( $serviceItem );
326
		$result = $provider->checkConfigFE( $attributes );
327
		$unknown = array_diff_key( $attributes, $result );
328
329
		if( count( $unknown ) > 0 )
330
		{
331
			$msg = sprintf( 'Unknown attributes "%1$s"', implode( '","', array_keys( $unknown ) ) );
332
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
333
		}
334
335
		foreach( $result as $key => $value )
336
		{
337
			if( $value !== null ) {
338
				throw new \Aimeos\Controller\Frontend\Basket\Exception( $value );
339
			}
340
		}
341
342
		$orderBaseServiceManager = \Aimeos\MShop\Factory::createManager( $context, 'order/base/service' );
343
		$orderServiceItem = $orderBaseServiceManager->createItem();
344
		$orderServiceItem->copyFrom( $serviceItem );
345
346
		$price = $provider->calcPrice( $this->basket );
347
		// remove service rebate of original price
348
		$price->setRebate( '0.00' );
349
		$orderServiceItem->setPrice( $price );
350
351
		$provider->setConfigFE( $orderServiceItem, $attributes );
352
353
		$this->basket->setService( $orderServiceItem, $type );
354
		$this->domainManager->setSession( $this->basket );
355
	}
356
357
358
	/**
359
	 * Fills the order address object with the values from the array.
360
	 *
361
	 * @param \Aimeos\MShop\Order\Item\Base\Address\Iface $address Address item to store the values into
362
	 * @param array $map Associative array of key/value pairs. The keys must be the same as when calling toArray() from
363
	 * 	an address item.
364
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception
365
	 */
366
	protected function setAddressFromArray( \Aimeos\MShop\Order\Item\Base\Address\Iface $address, array $map )
367
	{
368
		foreach( $map as $key => $value ) {
369
			$map[$key] = strip_tags( $value ); // prevent XSS
370
		}
371
372
		$errors = $address->fromArray( $map );
373
374
		if( count( $errors ) > 0 )
375
		{
376
			$msg = sprintf( 'Invalid address properties, please check your input' );
377
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg, 0, null, $errors );
378
		}
379
	}
380
}
381