Passed
Push — master ( 3747d0...2ef593 )
by Aimeos
15:15
created

Autofill   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 343
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 41
eloc 154
c 0
b 0
f 0
dl 0
loc 343
rs 9.1199

9 Methods

Rating   Name   Duplication   Size   Complexity  
A checkConfigBE() 0 5 1
A getConfigBE() 0 3 1
A register() 0 11 1
A update() 0 28 6
A getServiceItem() 0 24 4
C setServicesDefault() 0 36 13
A setAddressDefault() 0 19 4
A setAddresses() 0 15 4
B setServices() 0 36 7

How to fix   Complexity   

Complex Class

Complex classes like Autofill often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Autofill, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2014
6
 * @copyright Aimeos (aimeos.org), 2015-2023
7
 * @package MShop
8
 * @subpackage Plugin
9
 */
10
11
12
namespace Aimeos\MShop\Plugin\Provider\Order;
13
14
15
/**
16
 * Adds address and service items to the basket
17
 *
18
 * This plugins acts if a product is added to the basket or a delivery/payment
19
 * service is removed from the basket. It adds the a delivery/payment service
20
 * item and the customer address(es) to the basket.
21
 *
22
 * The following options are available:
23
 * - address: 1 (add billing address of the logged in customer to the basket)
24
 * - delivery: 1 (add the first delivery option to the basket)
25
 * - deliverycode: '...' and delivery: 1 (add specific delivery option to the basket)
26
 * - payment: 1 (add the first payment option to the basket)
27
 * - paymentcode: '...' and payment: 1 (add specific payment option to the basket)
28
 * - useorder: 1 (use last order of the customer to pre-fill addresses or services)
29
 * - orderservice: 1 (add delivery and payment services from the last order of the customer)
30
 * - orderaddress: 1 (add billing and delivery addresses from the last order of the customer)
31
 *
32
 * This plugin interacts with other plugins that add products or remove services!
33
 * Especially the "ServiceUpdate" plugin may remove a delivery/payment option
34
 * that isn't available any more based on the current basket content.
35
 *
36
 * To trace the execution and interaction of the plugins, set the log level to DEBUG:
37
 *	madmin/log/manager/loglevel = 7
38
 *
39
 * @package MShop
40
 * @subpackage Plugin
41
 */
42
class Autofill
43
	extends \Aimeos\MShop\Plugin\Provider\Factory\Base
44
	implements \Aimeos\MShop\Plugin\Provider\Iface, \Aimeos\MShop\Plugin\Provider\Factory\Iface
45
{
46
	private array $beConfig = array(
47
		'address' => array(
48
			'code' => 'address',
49
			'internalcode' => 'address',
50
			'label' => 'Add customer address automatically',
51
			'type' => 'boolean',
52
			'internaltype' => 'boolean',
53
			'default' => '',
54
			'required' => false,
55
		),
56
		'delivery' => array(
57
			'code' => 'delivery',
58
			'internalcode' => 'delivery',
59
			'label' => 'Add delivery option automatically',
60
			'type' => 'boolean',
61
			'internaltype' => 'boolean',
62
			'default' => '',
63
			'required' => false,
64
		),
65
		'deliverycode' => array(
66
			'code' => 'deliverycode',
67
			'internalcode' => 'deliverycode',
68
			'label' => 'Add delivery by code',
69
			'type' => 'string',
70
			'internaltype' => 'string',
71
			'default' => '',
72
			'required' => false,
73
		),
74
		'payment' => array(
75
			'code' => 'payment',
76
			'internalcode' => 'payment',
77
			'label' => 'Add payment option automatically',
78
			'type' => 'boolean',
79
			'internaltype' => 'boolean',
80
			'default' => '',
81
			'required' => false,
82
		),
83
		'paymentcode' => array(
84
			'code' => 'paymentcode',
85
			'internalcode' => 'paymentcode',
86
			'label' => 'Add payment by code',
87
			'type' => 'string',
88
			'internaltype' => 'string',
89
			'default' => '',
90
			'required' => false,
91
		),
92
		'useorder' => array(
93
			'code' => 'useorder',
94
			'internalcode' => 'useorder',
95
			'label' => 'Add from last order',
96
			'type' => 'boolean',
97
			'internaltype' => 'boolean',
98
			'default' => '',
99
			'required' => false,
100
		),
101
		'orderaddress' => array(
102
			'code' => 'orderaddress',
103
			'internalcode' => 'orderaddress',
104
			'label' => 'Add address from last order',
105
			'type' => 'boolean',
106
			'internaltype' => 'boolean',
107
			'default' => '',
108
			'required' => false,
109
		),
110
		'orderservice' => array(
111
			'code' => 'orderservice',
112
			'internalcode' => 'orderservice',
113
			'label' => 'Add delivery/payment from last order',
114
			'type' => 'boolean',
115
			'internaltype' => 'boolean',
116
			'default' => '',
117
			'required' => false,
118
		),
119
	);
120
121
122
	/**
123
	 * Checks the backend configuration attributes for validity.
124
	 *
125
	 * @param array $attributes Attributes added by the shop owner in the administraton interface
126
	 * @return array An array with the attribute keys as key and an error message as values for all attributes that are
127
	 * 	known by the provider but aren't valid
128
	 */
129
	public function checkConfigBE( array $attributes ) : array
130
	{
131
		$errors = parent::checkConfigBE( $attributes );
132
133
		return array_merge( $errors, $this->checkConfig( $this->beConfig, $attributes ) );
134
	}
135
136
137
	/**
138
	 * Returns the configuration attribute definitions of the provider to generate a list of available fields and
139
	 * rules for the value of each field in the administration interface.
140
	 *
141
	 * @return array List of attribute definitions implementing \Aimeos\Base\Critera\Attribute\Iface
142
	 */
143
	public function getConfigBE() : array
144
	{
145
		return $this->getConfigItems( $this->beConfig );
146
	}
147
148
149
	/**
150
	 * Subscribes itself to a publisher
151
	 *
152
	 * @param \Aimeos\MW\Observer\Publisher\Iface $p Object implementing publisher interface
153
	 * @return \Aimeos\MShop\Plugin\Provider\Iface Plugin object for method chaining
154
	 */
155
	public function register( \Aimeos\MW\Observer\Publisher\Iface $p ) : \Aimeos\MW\Observer\Listener\Iface
156
	{
157
		$plugin = $this->object();
158
159
		$p->attach( $plugin, 'addAddress.after' );
160
		$p->attach( $plugin, 'setAddresses.after' );
161
		$p->attach( $plugin, 'addProduct.after' );
162
		$p->attach( $plugin, 'setProducts.after' );
163
		$p->attach( $plugin, 'deleteService.after' );
164
165
		return $this;
166
	}
167
168
169
	/**
170
	 * Receives a notification from a publisher object
171
	 *
172
	 * @param \Aimeos\MW\Observer\Publisher\Iface $order Shop basket instance implementing publisher interface
173
	 * @param string $action Name of the action to listen for
174
	 * @param mixed $value Object or value changed in publisher
175
	 * @return mixed Modified value parameter
176
	 * @throws \Aimeos\MShop\Plugin\Provider\Exception if an error occurs
177
	 */
178
	public function update( \Aimeos\MW\Observer\Publisher\Iface $order, string $action, $value = null )
179
	{
180
		map( [$order] )->implements( \Aimeos\MShop\Order\Item\Iface::class, true );
181
182
		$context = $this->context();
183
		$services = $order->getServices();
184
		$addresses = $order->getAddresses();
185
186
		if( ( $userid = $context->user() ) !== null
187
			&& (bool) $this->getConfigValue( 'useorder', false ) === true
188
			&& ( $addresses->isEmpty() || $services->isEmpty() )
189
		) {
190
			$orderManager = \Aimeos\MShop::create( $context, 'order' );
191
192
			$search = $orderManager->filter()->add( ['order.customerid' => $userid] )
193
				->order( '-order.ctime' )->slice( 0, 1 );
194
195
			if( ( $item = $orderManager->search( $search, ['order/address', 'order/service', 'service'] )->first() ) !== null )
196
			{
197
				$this->setAddresses( $order, $item );
0 ignored issues
show
Bug introduced by
$order of type Aimeos\MW\Observer\Publisher\Iface is incompatible with the type Aimeos\MShop\Order\Item\Iface expected by parameter $order of Aimeos\MShop\Plugin\Prov...utofill::setAddresses(). ( Ignorable by Annotation )

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

197
				$this->setAddresses( /** @scrutinizer ignore-type */ $order, $item );
Loading history...
198
				$this->setServices( $order, $item );
0 ignored issues
show
Bug introduced by
$order of type Aimeos\MW\Observer\Publisher\Iface is incompatible with the type Aimeos\MShop\Order\Item\Iface expected by parameter $order of Aimeos\MShop\Plugin\Prov...Autofill::setServices(). ( Ignorable by Annotation )

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

198
				$this->setServices( /** @scrutinizer ignore-type */ $order, $item );
Loading history...
199
			}
200
		}
201
202
		$this->setAddressDefault( $order );
0 ignored issues
show
Bug introduced by
$order of type Aimeos\MW\Observer\Publisher\Iface is incompatible with the type Aimeos\MShop\Order\Item\Iface expected by parameter $order of Aimeos\MShop\Plugin\Prov...ll::setAddressDefault(). ( Ignorable by Annotation )

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

202
		$this->setAddressDefault( /** @scrutinizer ignore-type */ $order );
Loading history...
203
		$this->setServicesDefault( $order );
0 ignored issues
show
Bug introduced by
$order of type Aimeos\MW\Observer\Publisher\Iface is incompatible with the type Aimeos\MShop\Order\Item\Iface expected by parameter $order of Aimeos\MShop\Plugin\Prov...l::setServicesDefault(). ( Ignorable by Annotation )

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

203
		$this->setServicesDefault( /** @scrutinizer ignore-type */ $order );
Loading history...
204
205
		return $value;
206
	}
207
208
209
	/**
210
	 * Returns the order service item for the given type and code if available.
211
	 *
212
	 * @param \Aimeos\MShop\Order\Item\Iface $order Basket of the customer
213
	 * @param string $type Service type constant from \Aimeos\MShop\Order\Item\Service\Base
214
	 * @param string|null $code Service item code
215
	 * @return \Aimeos\MShop\Order\Item\Service\Iface|null Order service item if available or null otherwise
216
	 */
217
	protected function getServiceItem( \Aimeos\MShop\Order\Item\Iface $order, string $type,
218
		string $code = null ) : ?\Aimeos\MShop\Order\Item\Service\Iface
219
	{
220
		$context = $this->context();
221
		$serviceManager = \Aimeos\MShop::create( $context, 'service' );
222
223
		$filter = $serviceManager->filter( true )->add( ['service.type' => $type] )->order( 'service.position' );
224
225
		if( $code !== null ) {
226
			$filter->add( 'service.code', '==', $code );
227
		}
228
229
		foreach( $serviceManager->search( $filter, ['media', 'price', 'text'] ) as $item )
230
		{
231
			$provider = $serviceManager->getProvider( $item, $item->getType() );
232
233
			if( $provider->isAvailable( $order ) === true )
234
			{
235
				return \Aimeos\MShop::create( $context, 'order/service' )->create()
236
					->copyFrom( $item )->setPrice( $provider->calcPrice( $order ) );
237
			}
238
		}
239
240
		return null;
241
	}
242
243
244
	/**
245
	 * Adds the addresses from the given order item to the basket.
246
	 *
247
	 * @param \Aimeos\MShop\Order\Item\Iface $order Basket object
248
	 * @param \Aimeos\MShop\Order\Item\Iface $item Existing order to fetch the addresses from
249
	 * @return \Aimeos\MShop\Order\Item\Iface Updated basket object
250
	 */
251
	protected function setAddresses( \Aimeos\MShop\Order\Item\Iface $order,
252
		\Aimeos\MShop\Order\Item\Iface $item ) : \Aimeos\MShop\Order\Item\Iface
253
	{
254
		if( $order->getAddresses()->isEmpty() && (bool) $this->getConfigValue( 'orderaddress', true ) === true )
255
		{
256
			$map = $item->getAddresses();
257
258
			foreach( $map as $type => $list ) {
259
				map( $list )->setId( null );
260
			}
261
262
			$order->setAddresses( $map );
263
		}
264
265
		return $order;
266
	}
267
268
269
	/**
270
	 * Adds the services from the given order item to the basket.
271
	 *
272
	 * @param \Aimeos\MShop\Order\Item\Iface $order Basket object
273
	 * @param \Aimeos\MShop\Order\Item\Iface $item Existing order to fetch the services from
274
	 * @return \Aimeos\MShop\Order\Item\Iface Updated basket object
275
	 */
276
	protected function setServices( \Aimeos\MShop\Order\Item\Iface $order,
277
		\Aimeos\MShop\Order\Item\Iface $item ) : \Aimeos\MShop\Order\Item\Iface
278
	{
279
		if( $order->getServices()->isEmpty() && $this->getConfigValue( 'orderservice', true ) == true )
280
		{
281
			$map = $item->getServices();
282
			$serviceManager = \Aimeos\MShop::create( $this->context(), 'service' );
283
284
			foreach( $map as $type => $list )
285
			{
286
				foreach( $list as $key => $service )
287
				{
288
					if( $serviceItem = $service->getServiceItem() )
0 ignored issues
show
Unused Code introduced by
The assignment to $serviceItem is dead and can be removed.
Loading history...
289
					{
290
						$provider = $serviceManager->getProvider( $service->getServiceItem(), $service->getType() );
291
292
						if( $provider->isAvailable( $order ) === true )
293
						{
294
							$attrItems = $service->getAttributeItems()->filter( function( $attr ) {
295
								return in_array( $attrItem->getType(), ['', 'hidden'] );
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $attrItem seems to be never defined.
Loading history...
296
							} );
297
298
							$service->setId( null )->setAttributeItems( $attrItems->setId( null ) );
299
						}
300
						else
301
						{
302
							unset( $map[$type][$key] );
303
						}
304
					}
305
				}
306
			}
307
308
			$order->setServices( $map );
309
		}
310
311
		return $order;
312
	}
313
314
315
	/**
316
	 * Adds the default addresses to the basket if they are not available.
317
	 *
318
	 * @param \Aimeos\MShop\Order\Item\Iface $order Basket object
319
	 * @return \Aimeos\MShop\Order\Item\Iface Updated basket object
320
	 */
321
	protected function setAddressDefault( \Aimeos\MShop\Order\Item\Iface $order ) : \Aimeos\MShop\Order\Item\Iface
322
	{
323
		$context = $this->context();
324
		$addresses = $order->getAddresses();
325
		$type = \Aimeos\MShop\Order\Item\Address\Base::TYPE_PAYMENT;
326
327
		if( $context->user() !== null && !isset( $addresses[$type] )
328
			&& (bool) $this->getConfigValue( 'address', false ) === true
329
		) {
330
			$address = \Aimeos\MShop::create( $context, 'customer' )
331
				->get( $context->user() )->getPaymentAddress();
332
333
			$addrItem = \Aimeos\MShop::create( $context, 'order/address' )
334
				->create()->copyFrom( $address );
335
336
			$order->addAddress( $addrItem, $type );
337
		}
338
339
		return $order;
340
	}
341
342
343
	/**
344
	 * Adds the default services to the basket if they are not available.
345
	 *
346
	 * @param \Aimeos\MShop\Order\Item\Iface $order Basket object
347
	 * @return \Aimeos\MShop\Order\Item\Iface Updated basket object
348
	 */
349
	protected function setServicesDefault( \Aimeos\MShop\Order\Item\Iface $order ) : \Aimeos\MShop\Order\Item\Iface
350
	{
351
		$type = \Aimeos\MShop\Order\Item\Service\Base::TYPE_DELIVERY;
352
353
		foreach( $order->getService( $type ) as $pos => $service )
354
		{
355
			if( $this->getServiceItem( $order, $type, $service->getCode() ) === null ) {
356
				$order->deleteService( $type, $pos );
357
			}
358
		}
359
360
		if( $order->getService( $type ) === [] && (bool) $this->getConfigValue( 'delivery', false ) === true
361
			&& ( ( $item = $this->getServiceItem( $order, $type, $this->getConfigValue( 'deliverycode' ) ) ) !== null
362
			|| ( $item = $this->getServiceItem( $order, $type ) ) !== null )
363
		) {
364
			$order->addService( $item, $type );
365
		}
366
367
368
		$type = \Aimeos\MShop\Order\Item\Service\Base::TYPE_PAYMENT;
369
370
		foreach( $order->getService( $type ) as $pos => $service )
371
		{
372
			if( $this->getServiceItem( $order, $type, $service->getCode() ) === null ) {
373
				$order->deleteService( $type, $pos );
374
			}
375
		}
376
377
		if( $order->getService( $type ) === [] && (bool) $this->getConfigValue( 'payment', false ) === true
378
			&& ( ( $item = $this->getServiceItem( $order, $type, $this->getConfigValue( 'paymentcode' ) ) ) !== null
379
			|| ( $item = $this->getServiceItem( $order, $type ) ) !== null )
380
		) {
381
			$order->addService( $item, $type );
382
		}
383
384
		return $order;
385
	}
386
}
387