Completed
Push — master ( 45b214...6fb08c )
by Aimeos
03:09
created

Standard::load()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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