Passed
Push — master ( cb7c44...75ed13 )
by Aimeos
01:47
created

Standard::deleteAddress()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
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-2018
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 $manager;
26
	private $baskets = [];
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->manager = \Aimeos\MShop::create( $context, 'order/base' );
41
	}
42
43
44
	/**
45
	 * Adds values like comments to the basket
46
	 *
47
	 * @param array $values Order base values like comment
48
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
49
	 */
50
	public function add( array $values )
51
	{
52
		$this->baskets[$this->type] = $this->get()->fromArray( $values );
53
		return $this;
54
	}
55
56
57
	/**
58
	 * Empties the basket and removing all products, addresses, services, etc.
59
	 *
60
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
61
	 */
62
	public function clear()
63
	{
64
		$this->baskets[$this->type] = $this->manager->createItem();
65
		$this->manager->setSession( $this->baskets[$this->type], $this->type );
66
67
		return $this;
68
	}
69
70
71
	/**
72
	 * Returns the basket object.
73
	 *
74
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Basket holding products, addresses and delivery/payment options
75
	 */
76
	public function get()
77
	{
78
		if( !isset( $this->baskets[$this->type] ) )
79
		{
80
			$this->baskets[$this->type] = $this->manager->getSession( $this->type );
81
			$this->checkLocale( $this->baskets[$this->type]->getLocale(), $this->type );
82
		}
83
84
		return $this->baskets[$this->type];
85
	}
86
87
88
	/**
89
	 * Explicitely persists the basket content
90
	 *
91
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
92
	 */
93
	public function save()
94
	{
95
		if( isset( $this->baskets[$this->type] ) && $this->baskets[$this->type]->isModified() ) {
96
			$this->manager->setSession( $this->baskets[$this->type], $this->type );
97
		}
98
99
		return $this;
100
	}
101
102
103
	/**
104
	 * Sets the new basket type
105
	 *
106
	 * @param string $type Basket type
107
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
108
	 */
109
	public function setType( $type )
110
	{
111
		$this->type = $type;
112
		return $this;
113
	}
114
115
116
	/**
117
	 * Creates a new order base object from the current basket
118
	 *
119
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Order base object including products, addresses and services
120
	 */
121
	public function store()
122
	{
123
		$total = 0;
124
		$context = $this->getContext();
125
		$config = $context->getConfig();
126
127
		/** controller/frontend/basket/limit-count
128
		 * Maximum number of orders within the time frame
129
		 *
130
		 * Creating new orders is limited to avoid abuse and mitigate denial of
131
		 * service attacks. The number of orders created within the time frame
132
		 * configured by "controller/frontend/basket/limit-seconds" are counted
133
		 * before a new order of the same user (either logged in or identified
134
		 * by the IP address) is created. If the number of orders is higher than
135
		 * the configured value, an error message will be shown to the user
136
		 * instead of creating a new order.
137
		 *
138
		 * @param integer Number of orders allowed within the time frame
139
		 * @since 2017.05
140
		 * @category Developer
141
		 * @see controller/frontend/basket/limit-seconds
142
		 */
143
		$count = $config->get( 'controller/frontend/basket/limit-count', 5 );
144
145
		/** controller/frontend/basket/limit-seconds
146
		 * Order limitation time frame in seconds
147
		 *
148
		 * Creating new orders is limited to avoid abuse and mitigate denial of
149
		 * service attacks. Within the configured time frame, only a limited
150
		 * number of orders can be created. All orders of the current user
151
		 * (either logged in or identified by the IP address) within the last X
152
		 * seconds are counted. If the total value is higher then the number
153
		 * configured in "controller/frontend/basket/limit-count", an error
154
		 * message will be shown to the user instead of creating a new order.
155
		 *
156
		 * @param integer Number of seconds to check orders within
157
		 * @since 2017.05
158
		 * @category Developer
159
		 * @see controller/frontend/basket/limit-count
160
		 */
161
		$seconds = $config->get( 'controller/frontend/basket/limit-seconds', 300 );
162
163
		$search = $this->manager->createSearch();
164
		$expr = [
165
			$search->compare( '==', 'order.base.editor', $context->getEditor() ),
166
			$search->compare( '>=', 'order.base.ctime', date( 'Y-m-d H:i:s', time() - $seconds ) ),
167
		];
168
		$search->setConditions( $search->combine( '&&', $expr ) );
169
		$search->setSlice( 0, 0 );
170
171
		$this->manager->searchItems( $search, [], $total );
172
173
		if( $total > $count )
174
		{
175
			$msg = $context->getI18n()->dt( 'controller/frontend', 'Temporary order limit reached' );
176
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
177
		}
178
179
180
		$basket = $this->get()->finish();
181
		$basket->setCustomerId( (string) $context->getUserId() );
182
183
		$this->manager->begin();
184
		$this->manager->store( $basket );
185
		$this->manager->commit();
186
187
		$this->save(); // for reusing unpaid orders, might have side effects (!)
188
		$this->createSubscriptions( $basket );
189
190
		return $basket;
191
	}
192
193
194
	/**
195
	 * Returns the order base object for the given ID
196
	 *
197
	 * @param string $id Unique ID of the order base object
198
	 * @param integer $parts Constants which parts of the order base object should be loaded
199
	 * @param boolean $default True to add default criteria (user logged in), false if not
200
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Order base object including the given parts
201
	 */
202
	public function load( $id, $parts = \Aimeos\MShop\Order\Item\Base\Base::PARTS_ALL, $default = true )
203
	{
204
		return $this->manager->load( $id, $parts, false, $default );
205
	}
206
207
208
	/**
209
	 * Adds a product to the basket of the customer stored in the session
210
	 *
211
	 * @param \Aimeos\MShop\Product\Item\Iface $product Product to add including texts, media, prices, attributes, etc.
212
	 * @param integer $quantity Amount of products that should by added
213
	 * @param array $variant List of variant-building attribute IDs that identify an article in a selection product
214
	 * @param array $config List of configurable attribute IDs the customer has chosen from
215
	 * @param array $custom Associative list of attribute IDs as keys and arbitrary values that will be added to the ordered product
216
	 * @param string $stocktype Unique code of the stock type to deliver the products from
217
	 * @param string|null $supplier Unique supplier code the product is from
218
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
219
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If the product isn't available
220
	 */
221
	public function addProduct( \Aimeos\MShop\Product\Item\Iface $product, $quantity = 1,
222
		array $variant = [], array $config = [], array $custom = [], $stocktype = 'default', $supplier = null )
223
	{
224
		$attributeMap = ['custom' => array_keys( $custom ), 'config' => array_keys( $config )];
225
		$this->checkListRef( $product->getId(), 'attribute', $attributeMap );
226
227
		$prices = $product->getRefItems( 'price', 'default', 'default' );
228
		$hidden = $product->getRefItems( 'attribute', null, 'hidden' );
229
230
		$custAttr = $this->getOrderProductAttributes( 'custom', array_keys( $custom ), $custom );
231
		$confAttr = $this->getOrderProductAttributes( 'config', array_keys( $config ), [], $config );
232
		$hideAttr = $this->getOrderProductAttributes( 'hidden', array_keys( $hidden ) );
233
234
		$orderBaseProductItem = \Aimeos\MShop::create( $this->getContext(), 'order/base/product' )->createItem()
235
			->copyFrom( $product )->setQuantity( $quantity )->setStockType( $stocktype )->setSupplierCode( $supplier )
0 ignored issues
show
Bug introduced by
The method copyFrom() does not exist on Aimeos\MShop\Attribute\Item\Iface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

235
			->/** @scrutinizer ignore-call */ copyFrom( $product )->setQuantity( $quantity )->setStockType( $stocktype )->setSupplierCode( $supplier )

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
236
			->setAttributeItems( array_merge( $custAttr, $confAttr, $hideAttr ) );
237
238
		$orderBaseProductItem = $orderBaseProductItem
239
			->setPrice( $this->calcPrice( $orderBaseProductItem, $prices, $quantity ) );
240
241
		$this->baskets[$this->type] = $this->get()->addProduct( $orderBaseProductItem );
242
		return $this->save();
243
	}
244
245
246
	/**
247
	 * Deletes a product item from the basket.
248
	 *
249
	 * @param integer $position Position number (key) of the order product item
250
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
251
	 */
252
	public function deleteProduct( $position )
253
	{
254
		$product = $this->get()->getProduct( $position );
255
256
		if( $product->getFlags() === \Aimeos\MShop\Order\Item\Base\Product\Base::FLAG_IMMUTABLE )
257
		{
258
			$msg = $this->getContext()->getI18n()->dt( 'controller/frontend', 'Basket item at position "%1$d" cannot be deleted manually' );
259
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( $msg, $position ) );
260
		}
261
262
		$this->baskets[$this->type] = $this->get()->deleteProduct( $position );
263
		return $this->save();
264
	}
265
266
267
	/**
268
	 * Edits the quantity of a product item in the basket.
269
	 *
270
	 * @param integer $position Position number (key) of the order product item
271
	 * @param integer $quantity New quantiy of the product item
272
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
273
	 */
274
	public function updateProduct( $position, $quantity )
275
	{
276
		$product = $this->get()->getProduct( $position );
277
278
		if( $product->getFlags() & \Aimeos\MShop\Order\Item\Base\Product\Base::FLAG_IMMUTABLE )
279
		{
280
			$msg = $this->getContext()->getI18n()->dt( 'controller/frontend', 'Basket item at position "%1$d" cannot be changed' );
281
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( $msg, $position ) );
282
		}
283
284
		$manager = \Aimeos\MShop::create( $this->getContext(), 'product' );
285
		$productItem = $manager->findItem( $product->getProductCode(), array( 'price', 'text' ), true );
286
287
		$price = $this->calcPrice( $product, $productItem->getRefItems( 'price', 'default' ), $quantity );
288
		$product = $product->setQuantity( $quantity )->setPrice( $price );
289
290
		$this->baskets[$this->type] = $this->get()->addProduct( $product, $position );
291
		return $this->save();
292
	}
293
294
295
	/**
296
	 * Adds the given coupon code and updates the basket.
297
	 *
298
	 * @param string $code Coupon code entered by the user
299
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
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
		$this->baskets[$this->type] = $this->get()->addCoupon( $code );
331
		return $this->save();
332
	}
333
334
335
	/**
336
	 * Removes the given coupon code and its effects from the basket.
337
	 *
338
	 * @param string $code Coupon code entered by the user
339
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
340
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception if the coupon code is invalid
341
	 */
342
	public function deleteCoupon( $code )
343
	{
344
		$this->baskets[$this->type] = $this->get()->deleteCoupon( $code );
345
		return $this->save();
346
	}
347
348
349
	/**
350
	 * Adds an address of the customer to the basket
351
	 *
352
	 * @param string $type Address type code like 'payment' or 'delivery'
353
	 * @param array $values Associative list of key/value pairs with address details
354
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
355
	 */
356
	public function addAddress( $type, array $values = [], $position = null )
357
	{
358
		foreach( $values as $key => $value ) {
359
			$values[$key] = strip_tags( $value ); // prevent XSS
360
		}
361
362
		$context = $this->getContext();
363
		$address = \Aimeos\MShop::create( $context, 'order/base/address' )->createItem()->fromArray( $values );
364
365
		$this->baskets[$this->type] = $this->get()->addAddress( $address, $type, $position );
366
		return $this->save();
367
	}
368
369
370
	/**
371
	 * Removes the address of the given type and position if available
372
	 *
373
	 * @param string $type Address type code like 'payment' or 'delivery'
374
	 * @param integer|null $position Position of the address in the list to overwrite
375
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
376
	 */
377
	public function deleteAddress( $type, $position = null )
378
	{
379
		$this->baskets[$this->type] = $this->get()->deleteAddress( $type, $position );
380
		return $this->save();
381
	}
382
383
384
	/**
385
	 * Adds the delivery/payment service including the given configuration
386
	 *
387
	 * @param \Aimeos\MShop\Service\Item\Iface $service Service item selected by the customer
388
	 * @param array $config Associative list of key/value pairs with the options selected by the customer
389
	 * @param integer|null $position Position of the address in the list to overwrite
390
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
391
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If given service attributes are invalid
392
	 */
393
	public function addService( \Aimeos\MShop\Service\Item\Iface $service, array $config = [], $position = null )
394
	{
395
		$context = $this->getContext();
396
		$manager = \Aimeos\MShop::create( $context, 'service' );
397
398
		$provider = $manager->getProvider( $service, $service->getType() );
399
		$errors = array_filter( $provider->checkConfigFE( $config ) );
400
		$unknown = array_diff_key( $config, $errors );
401
402
		if( count( $unknown ) > 0 )
403
		{
404
			$msg = $context->getI18n()->dt( 'controller/frontend', 'Unknown service attributes' );
405
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg, -1, null, $unknown );
406
		}
407
408
		if( count( $errors ) > 0 )
409
		{
410
			$msg = $context->getI18n()->dt( 'controller/frontend', 'Invalid service attributes' );
411
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg, -1, null, $errors );
412
		}
413
414
		// remove service rebate of original price
415
		$price = $provider->calcPrice( $this->get() )->setRebate( '0.00' );
416
417
		$orderBaseServiceManager = \Aimeos\MShop::create( $context, 'order/base/service' );
418
419
		$orderServiceItem = $orderBaseServiceManager->createItem()->copyFrom( $service )->setPrice( $price );
420
		$orderServiceItem = $provider->setConfigFE( $orderServiceItem, $config );
421
422
		$this->baskets[$this->type] = $this->get()->addService( $orderServiceItem, $service->getType(), $position );
423
		return $this->save();
424
	}
425
426
427
	/**
428
	 * Removes the delivery or payment service items from the basket
429
	 *
430
	 * @param string $type Service type code like 'payment' or 'delivery'
431
	 * @param integer|null $position Position of the address in the list to overwrite
432
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
433
	 */
434
	public function deleteService( $type, $position = null )
435
	{
436
		$this->baskets[$this->type] = $this->get()->deleteService( $type, $position );
437
		return $this->save();
438
	}
439
}
440