Standard   A
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 565
Duplicated Lines 0 %

Importance

Changes 8
Bugs 0 Features 0
Metric Value
wmc 36
eloc 131
dl 0
loc 565
rs 9.52
c 8
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A setType() 0 4 1
A clear() 0 6 1
A add() 0 4 1
A save() 0 7 3
A __construct() 0 5 1
A store() 0 68 2
A get() 0 10 2
A load() 0 4 1
A deleteService() 0 4 1
A deleteCoupon() 0 4 1
A getManager() 0 3 1
B addService() 0 43 6
A updateProduct() 0 21 2
A addCoupon() 0 30 2
A deleteProduct() 0 12 2
A addAddress() 0 15 4
A deleteAddress() 0 4 1
A addProduct() 0 30 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-2024
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
	/** controller/frontend/basket/name
26
	 * Class name of the used basket frontend controller implementation
27
	 *
28
	 * Each default frontend controller can be replace by an alternative imlementation.
29
	 * To use this implementation, you have to set the last part of the class
30
	 * name as configuration value so the controller factory knows which class it
31
	 * has to instantiate.
32
	 *
33
	 * For example, if the name of the default class is
34
	 *
35
	 *  \Aimeos\Controller\Frontend\Basket\Standard
36
	 *
37
	 * and you want to replace it with your own version named
38
	 *
39
	 *  \Aimeos\Controller\Frontend\Basket\Mybasket
40
	 *
41
	 * then you have to set the this configuration option:
42
	 *
43
	 *  controller/jobs/frontend/basket/name = Mybasket
44
	 *
45
	 * The value is the last part of your own class name and it's case sensitive,
46
	 * so take care that the configuration value is exactly named like the last
47
	 * part of the class name.
48
	 *
49
	 * The allowed characters of the class name are A-Z, a-z and 0-9. No other
50
	 * characters are possible! You should always start the last part of the class
51
	 * name with an upper case character and continue only with lower case characters
52
	 * or numbers. Avoid chamel case names like "MyBasket"!
53
	 *
54
	 * @param string Last part of the class name
55
	 * @since 2014.03
56
	 * @category Developer
57
	 */
58
59
	/** controller/frontend/basket/decorators/excludes
60
	 * Excludes decorators added by the "common" option from the basket frontend controllers
61
	 *
62
	 * Decorators extend the functionality of a class by adding new aspects
63
	 * (e.g. log what is currently done), executing the methods of the underlying
64
	 * class only in certain conditions (e.g. only for logged in users) or
65
	 * modify what is returned to the caller.
66
	 *
67
	 * This option allows you to remove a decorator added via
68
	 * "controller/frontend/common/decorators/default" before they are wrapped
69
	 * around the frontend controller.
70
	 *
71
	 *  controller/frontend/basket/decorators/excludes = array( 'decorator1' )
72
	 *
73
	 * This would remove the decorator named "decorator1" from the list of
74
	 * common decorators ("\Aimeos\Controller\Frontend\Common\Decorator\*") added via
75
	 * "controller/frontend/common/decorators/default" for the basket frontend controller.
76
	 *
77
	 * @param array List of decorator names
78
	 * @since 2014.03
79
	 * @category Developer
80
	 * @see controller/frontend/common/decorators/default
81
	 * @see controller/frontend/basket/decorators/global
82
	 * @see controller/frontend/basket/decorators/local
83
	 */
84
85
	/** controller/frontend/basket/decorators/global
86
	 * Adds a list of globally available decorators only to the basket frontend controllers
87
	 *
88
	 * Decorators extend the functionality of a class by adding new aspects
89
	 * (e.g. log what is currently done), executing the methods of the underlying
90
	 * class only in certain conditions (e.g. only for logged in users) or
91
	 * modify what is returned to the caller.
92
	 *
93
	 * This option allows you to wrap global decorators
94
	 * ("\Aimeos\Controller\Frontend\Common\Decorator\*") around the frontend controller.
95
	 *
96
	 *  controller/frontend/basket/decorators/global = array( 'decorator1' )
97
	 *
98
	 * This would add the decorator named "decorator1" defined by
99
	 * "\Aimeos\Controller\Frontend\Common\Decorator\Decorator1" only to the frontend controller.
100
	 *
101
	 * @param array List of decorator names
102
	 * @since 2014.03
103
	 * @category Developer
104
	 * @see controller/frontend/common/decorators/default
105
	 * @see controller/frontend/basket/decorators/excludes
106
	 * @see controller/frontend/basket/decorators/local
107
	 */
108
109
	/** controller/frontend/basket/decorators/local
110
	 * Adds a list of local decorators only to the basket frontend controllers
111
	 *
112
	 * Decorators extend the functionality of a class by adding new aspects
113
	 * (e.g. log what is currently done), executing the methods of the underlying
114
	 * class only in certain conditions (e.g. only for logged in users) or
115
	 * modify what is returned to the caller.
116
	 *
117
	 * This option allows you to wrap local decorators
118
	 * ("\Aimeos\Controller\Frontend\Basket\Decorator\*") around the frontend controller.
119
	 *
120
	 *  controller/frontend/basket/decorators/local = array( 'decorator2' )
121
	 *
122
	 * This would add the decorator named "decorator2" defined by
123
	 * "\Aimeos\Controller\Frontend\Basket\Decorator\Decorator2" only to the frontend
124
	 * controller.
125
	 *
126
	 * @param array List of decorator names
127
	 * @since 2014.03
128
	 * @category Developer
129
	 * @see controller/frontend/common/decorators/default
130
	 * @see controller/frontend/basket/decorators/excludes
131
	 * @see controller/frontend/basket/decorators/global
132
	 */
133
134
	private \Aimeos\MShop\Common\Manager\Iface $manager;
135
	private string $type = 'default';
136
	private array $baskets = [];
137
138
139
	/**
140
	 * Initializes the frontend controller.
141
	 *
142
	 * @param \Aimeos\MShop\ContextIface $context Object storing the required instances for manaing databases
143
	 *  connections, logger, session, etc.
144
	 */
145
	public function __construct( \Aimeos\MShop\ContextIface $context )
146
	{
147
		parent::__construct( $context );
148
149
		$this->manager = \Aimeos\MShop::create( $context, 'order' );
150
	}
151
152
153
	/**
154
	 * Adds values like comments to the basket
155
	 *
156
	 * @param array $values Order values like comment
157
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
158
	 */
159
	public function add( array $values ) : Iface
160
	{
161
		$this->baskets[$this->type] = $this->get()->fromArray( $values );
162
		return $this;
163
	}
164
165
166
	/**
167
	 * Empties the basket and removing all products, addresses, services, etc.
168
	 *
169
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
170
	 */
171
	public function clear() : Iface
172
	{
173
		$this->baskets[$this->type] = $this->manager->create();
174
		$this->manager->setSession( $this->baskets[$this->type], $this->type );
175
176
		return $this;
177
	}
178
179
180
	/**
181
	 * Returns the basket object.
182
	 *
183
	 * @return \Aimeos\MShop\Order\Item\Iface Basket holding products, addresses and delivery/payment options
184
	 */
185
	public function get() : \Aimeos\MShop\Order\Item\Iface
186
	{
187
		if( !isset( $this->baskets[$this->type] ) )
188
		{
189
			$this->baskets[$this->type] = $this->manager->getSession( $this->type );
190
			$this->checkLocale( $this->baskets[$this->type]->locale(), $this->type );
191
			$this->baskets[$this->type]->setCustomerId( (string) $this->context()->user() );
192
		}
193
194
		return $this->baskets[$this->type];
195
	}
196
197
198
	/**
199
	 * Explicitly persists the basket content
200
	 *
201
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
202
	 */
203
	public function save() : Iface
204
	{
205
		if( isset( $this->baskets[$this->type] ) && $this->baskets[$this->type]->isModified() ) {
206
			$this->manager->setSession( $this->baskets[$this->type], $this->type );
207
		}
208
209
		return $this;
210
	}
211
212
213
	/**
214
	 * Sets the new basket type
215
	 *
216
	 * @param string $type Basket type
217
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
218
	 */
219
	public function setType( string $type ) : Iface
220
	{
221
		$this->type = $type;
222
		return $this;
223
	}
224
225
226
	/**
227
	 * Creates a new order object from the current basket
228
	 *
229
	 * @return \Aimeos\MShop\Order\Item\Iface Order object including products, addresses and services
230
	 */
231
	public function store() : \Aimeos\MShop\Order\Item\Iface
232
	{
233
		$total = 0;
234
		$context = $this->context();
235
		$config = $context->config();
236
237
		/** controller/frontend/basket/limit-count
238
		 * Maximum number of orders within the time frame
239
		 *
240
		 * Creating new orders is limited to avoid abuse and mitigate denial of
241
		 * service attacks. The number of orders created within the time frame
242
		 * configured by "controller/frontend/basket/limit-seconds" are counted
243
		 * before a new order of the same user (either logged in or identified
244
		 * by the IP address) is created. If the number of orders is higher than
245
		 * the configured value, an error message will be shown to the user
246
		 * instead of creating a new order.
247
		 *
248
		 * @param integer Number of orders allowed within the time frame
249
		 * @since 2017.05
250
		 * @category Developer
251
		 * @see controller/frontend/basket/limit-seconds
252
		 */
253
		$count = $config->get( 'controller/frontend/basket/limit-count', 5 );
254
255
		/** controller/frontend/basket/limit-seconds
256
		 * Order limitation time frame in seconds
257
		 *
258
		 * Creating new orders is limited to avoid abuse and mitigate denial of
259
		 * service attacks. Within the configured time frame, only a limited
260
		 * number of orders can be created. All orders of the current user
261
		 * (either logged in or identified by the IP address) within the last X
262
		 * seconds are counted. If the total value is higher then the number
263
		 * configured in "controller/frontend/basket/limit-count", an error
264
		 * message will be shown to the user instead of creating a new order.
265
		 *
266
		 * @param integer Number of seconds to check orders within
267
		 * @since 2017.05
268
		 * @category Developer
269
		 * @see controller/frontend/basket/limit-count
270
		 */
271
		$seconds = $config->get( 'controller/frontend/basket/limit-seconds', 900 );
272
273
		$search = $this->manager->filter()->slice( 0, 0 );
274
		$expr = [
275
			$search->compare( '==', 'order.editor', $context->editor() ),
276
			$search->compare( '>=', 'order.ctime', date( 'Y-m-d H:i:s', time() - $seconds ) ),
277
		];
278
		$search->add( $search->and( $expr ) );
279
280
		$this->manager->search( $search, [], $total );
281
282
		if( $total >= $count )
283
		{
284
			$msg = $context->translate( 'controller/frontend', 'Temporary order limit reached' );
285
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
286
		}
287
288
289
		$basket = $this->get()->setCustomerId( (string) $context->user() )->finish()->check();
290
291
		$this->manager->begin();
292
		$this->manager->save( $basket );
293
		$this->manager->commit();
294
295
		$this->save(); // for reusing unpaid orders, might have side effects (!)
296
		$this->createSubscriptions( $basket );
297
298
		return $basket;
299
	}
300
301
302
	/**
303
	 * Returns the order object for the given ID
304
	 *
305
	 * @param string $id Unique ID of the order object
306
	 * @param array $ref References items that should be fetched too
307
	 * @param bool $default True to add default criteria (user logged in), false if not
308
	 * @return \Aimeos\MShop\Order\Item\Iface Order object including the given parts
309
	 */
310
	public function load( string $id, array $ref = ['order/address', 'order/coupon', 'order/product', 'order/service'],
311
		bool $default = true ) : \Aimeos\MShop\Order\Item\Iface
312
	{
313
		return $this->manager->get( $id, $ref, $default );
314
	}
315
316
317
	/**
318
	 * Adds a product to the basket of the customer stored in the session
319
	 *
320
	 * @param \Aimeos\MShop\Product\Item\Iface $product Product to add including texts, media, prices, attributes, etc.
321
	 * @param float $quantity Amount of products that should by added
322
	 * @param array $variant List of variant-building attribute IDs that identify an article in a selection product
323
	 * @param array $config List of configurable attribute IDs the customer has chosen from
324
	 * @param array $custom Associative list of attribute IDs as keys and arbitrary values that will be added to the ordered product
325
	 * @param string $stocktype Unique code of the stock type to deliver the products from
326
	 * @param string|null $siteId Unique ID of the site the product should be bought from or NULL for site the product is from
327
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
328
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If the product isn't available
329
	 */
330
	public function addProduct( \Aimeos\MShop\Product\Item\Iface $product, float $quantity = 1,
331
		array $variant = [], array $config = [], array $custom = [], string $stocktype = 'default', string $siteId = null ) : Iface
332
	{
333
		$quantity = $this->call( 'checkQuantity', $product, $quantity );
334
		$this->call( 'checkAttributes', [$product], 'custom', array_keys( $custom ) );
335
		$this->call( 'checkAttributes', [$product], 'config', array_keys( $config ) );
336
337
		$prices = $product->getRefItems( 'price', 'default', 'default' );
338
		$hidden = $product->getRefItems( 'attribute', null, 'hidden' );
339
340
		$custAttr = $this->call( 'getOrderProductAttributes', 'custom', array_keys( $custom ), $custom );
341
		$confAttr = $this->call( 'getOrderProductAttributes', 'config', array_keys( $config ), [], $config );
342
		$hideAttr = $this->call( 'getOrderProductAttributes', 'hidden', $hidden->keys()->toArray() );
343
344
		$orderBaseProductItem = \Aimeos\MShop::create( $this->context(), 'order/product' )
345
			->create()
346
			->copyFrom( $product )
347
			->setQuantity( $quantity )
348
			->setStockType( $stocktype )
349
			->setSiteId( $siteId ?: $product->getSiteId() )
350
			->setAttributeItems( array_merge( $custAttr, $confAttr, $hideAttr ) );
351
352
		$price = $this->call( 'calcPrice', $orderBaseProductItem, $prices, $quantity );
353
		$orderBaseProductItem
354
			->setPrice( $price )
355
			->setSiteId( $siteId ?: $price->getSiteId() )
356
			->setVendor( $this->getVendor( $siteId ?: $price->getSiteId() ) );
357
358
		$this->baskets[$this->type] = $this->get()->addProduct( $orderBaseProductItem );
359
		return $this->save();
360
	}
361
362
363
	/**
364
	 * Deletes a product item from the basket.
365
	 *
366
	 * @param int $position Position number (key) of the order product item
367
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
368
	 */
369
	public function deleteProduct( int $position ) : Iface
370
	{
371
		$product = $this->get()->getProduct( $position );
372
373
		if( $product->getFlags() === \Aimeos\MShop\Order\Item\Product\Base::FLAG_IMMUTABLE )
374
		{
375
			$msg = $this->context()->translate( 'controller/frontend', 'Basket item at position "%1$d" cannot be deleted manually' );
376
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( $msg, $position ) );
377
		}
378
379
		$this->baskets[$this->type] = $this->get()->deleteProduct( $position );
380
		return $this->save();
381
	}
382
383
384
	/**
385
	 * Edits the quantity of a product item in the basket.
386
	 *
387
	 * @param int $position Position number (key) of the order product item
388
	 * @param float $quantity New quantiy of the product item
389
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
390
	 */
391
	public function updateProduct( int $position, float $quantity ) : Iface
392
	{
393
		$context = $this->context();
394
		$orderProduct = $this->get()->getProduct( $position );
395
396
		if( $orderProduct->getFlags() & \Aimeos\MShop\Order\Item\Product\Base::FLAG_IMMUTABLE )
397
		{
398
			$msg = $context->translate( 'controller/frontend', 'Basket item at position "%1$d" cannot be changed' );
399
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( $msg, $position ) );
400
		}
401
402
		$manager = \Aimeos\MShop::create( $context, 'product' );
403
		$product = $manager->get( $orderProduct->getProductId(), ['attribute', 'catalog', 'price', 'text'], true );
404
		$product = \Aimeos\MShop::create( $context, 'rule' )->apply( $product, 'catalog' );
405
406
		$quantity = $this->call( 'checkQuantity', $product, $quantity );
407
		$price = $this->call( 'calcPrice', $orderProduct, $product->getRefItems( 'price', 'default', 'default' ), $quantity );
408
		$orderProduct = $orderProduct->setQuantity( $quantity )->setPrice( $price );
409
410
		$this->baskets[$this->type] = $this->get()->addProduct( $orderProduct, $position );
411
		return $this->save();
412
	}
413
414
415
	/**
416
	 * Adds the given coupon code and updates the basket.
417
	 *
418
	 * @param string $code Coupon code entered by the user
419
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
420
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception if the coupon code is invalid or not allowed
421
	 */
422
	public function addCoupon( string $code ) : Iface
423
	{
424
		$context = $this->context();
425
426
		/** controller/frontend/basket/coupon/allowed
427
		 * Number of coupon codes a customer is allowed to enter
428
		 *
429
		 * This configuration option enables shop owners to limit the number of coupon
430
		 * codes that can be added by a customer to his current basket. By default, only
431
		 * one coupon code is allowed per order.
432
		 *
433
		 * Coupon codes are valid until a payed order is placed by the customer. The
434
		 * "count" of the codes is decreased afterwards. If codes are not personalized
435
		 * the codes can be reused in the next order until their "count" reaches zero.
436
		 *
437
		 * @param integer Positive number of coupon codes including zero
438
		 * @since 2017.08
439
		 * @category User
440
		 * @category Developer
441
		 */
442
		$allowed = $context->config()->get( 'controller/frontend/basket/coupon/allowed', 1 );
443
444
		if( $allowed <= count( $this->get()->getCoupons() ) )
445
		{
446
			$msg = $context->translate( 'controller/frontend', 'Number of coupon codes exceeds the limit' );
447
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
448
		}
449
450
		$this->baskets[$this->type] = $this->get()->addCoupon( $code );
451
		return $this->save();
452
	}
453
454
455
	/**
456
	 * Removes the given coupon code and its effects from the basket.
457
	 *
458
	 * @param string $code Coupon code entered by the user
459
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
460
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception if the coupon code is invalid
461
	 */
462
	public function deleteCoupon( string $code ) : Iface
463
	{
464
		$this->baskets[$this->type] = $this->get()->deleteCoupon( $code );
465
		return $this->save();
466
	}
467
468
469
	/**
470
	 * Adds an address of the customer to the basket
471
	 *
472
	 * @param string $type Address type code like 'payment' or 'delivery'
473
	 * @param array $values Associative list of key/value pairs with address details
474
	 * @param int|null $position Position number (key) of the order address item to replace
475
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
476
	 */
477
	public function addAddress( string $type, array $values = [], int $position = null ) : Iface
478
	{
479
		foreach( $values as $key => $value )
480
		{
481
			if( is_scalar( $value ) ) {
482
				$values[$key] = strip_tags( (string) $value ); // prevent XSS
483
			}
484
		}
485
486
		$context = $this->context();
487
		$address = \Aimeos\MShop::create( $context, 'order/address' )->create()->fromArray( $values );
488
		$address->set( 'nostore', ( $values['nostore'] ?? false ) ? true : false );
489
490
		$this->baskets[$this->type] = $this->get()->addAddress( $address, $type, $position );
491
		return $this->save();
492
	}
493
494
495
	/**
496
	 * Removes the address of the given type and position if available
497
	 *
498
	 * @param string $type Address type code like 'payment' or 'delivery'
499
	 * @param int|null $position Position of the address in the list to remove
500
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
501
	 */
502
	public function deleteAddress( string $type, int $position = null ) : Iface
503
	{
504
		$this->baskets[$this->type] = $this->get()->deleteAddress( $type, $position );
505
		return $this->save();
506
	}
507
508
509
	/**
510
	 * Adds the delivery/payment service including the given configuration
511
	 *
512
	 * @param \Aimeos\MShop\Service\Item\Iface $service Service item selected by the customer
513
	 * @param array $config Associative list of key/value pairs with the options selected by the customer
514
	 * @param int|null $position Position of the service in the list to replace
515
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
516
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If given service attributes are invalid
517
	 */
518
	public function addService( \Aimeos\MShop\Service\Item\Iface $service, array $config = [], int $position = null ) : Iface
519
	{
520
		$basket = $this->get();
521
		$context = $this->context();
522
523
		$type = $service->getType();
524
		$code = $service->getCode();
525
526
		foreach( $basket->getService( $type ) as $pos => $ordService )
527
		{
528
			if( !$position && $ordService->getCode() === $code ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $position of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
529
				$position = $pos;
530
			}
531
		}
532
533
		$manager = \Aimeos\MShop::create( $context, 'service' );
534
		$provider = $manager->getProvider( $service, $type );
535
536
		$errors = $provider->checkConfigFE( $config );
537
		$unknown = array_diff_key( $config, $errors );
538
539
		if( count( $unknown ) > 0 )
540
		{
541
			$msg = $context->translate( 'controller/frontend', 'Unknown service attributes' );
542
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg, -1, null, $unknown );
543
		}
544
545
		if( count( array_filter( $errors ) ) > 0 )
546
		{
547
			$msg = $context->translate( 'controller/frontend', 'Invalid service attributes' );
548
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg, -1, null, array_filter( $errors ) );
549
		}
550
551
		// remove service rebate of original price
552
		$price = $provider->calcPrice( $this->get(), $config )->setRebate( '0.00' );
553
554
		$orderBaseServiceManager = \Aimeos\MShop::create( $context, 'order/service' );
555
556
		$orderServiceItem = $orderBaseServiceManager->create()->copyFrom( $service )->setPrice( $price );
557
		$orderServiceItem = $provider->setConfigFE( $orderServiceItem, $config );
558
559
		$this->baskets[$this->type] = $basket->addService( $orderServiceItem, $type, $position );
560
		return $this->save();
561
	}
562
563
564
	/**
565
	 * Removes the delivery or payment service items from the basket
566
	 *
567
	 * @param string $type Service type code like 'payment' or 'delivery'
568
	 * @param int|null $position Position of the service in the list to remove
569
	 * @return \Aimeos\Controller\Frontend\Basket\Iface Basket frontend object for fluent interface
570
	 */
571
	public function deleteService( string $type, int $position = null ) : Iface
572
	{
573
		$this->baskets[$this->type] = $this->get()->deleteService( $type, $position );
574
		return $this->save();
575
	}
576
577
578
	/**
579
	 * Returns the manager used by the controller
580
	 *
581
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object
582
	 */
583
	protected function getManager() : \Aimeos\MShop\Common\Manager\Iface
584
	{
585
		return $this->manager;
586
	}
587
}
588