Completed
Push — master ( e7d02e...e6fed6 )
by Aimeos
02:54
created

Standard::addBasketServices()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 40
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 20
c 1
b 0
f 0
nc 6
nop 3
dl 0
loc 40
rs 8.9777
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2018
6
 * @package Controller
7
 * @subpackage Jobs
8
 */
9
10
11
namespace Aimeos\Controller\Jobs\Subscription\Process\Renew;
12
13
14
/**
15
 * Job controller for subscription processs renew.
16
 *
17
 * @package Controller
18
 * @subpackage Jobs
19
 */
20
class Standard
21
	extends \Aimeos\Controller\Jobs\Subscription\Process\Base
22
	implements \Aimeos\Controller\Jobs\Iface
23
{
24
	/**
25
	 * Returns the localized name of the job.
26
	 *
27
	 * @return string Name of the job
28
	 */
29
	public function getName() : string
30
	{
31
		return $this->getContext()->getI18n()->dt( 'controller/jobs', 'Subscription process renew' );
32
	}
33
34
35
	/**
36
	 * Returns the localized description of the job.
37
	 *
38
	 * @return string Description of the job
39
	 */
40
	public function getDescription() : string
41
	{
42
		return $this->getContext()->getI18n()->dt( 'controller/jobs', 'Renews subscriptions at next date' );
43
	}
44
45
46
	/**
47
	 * Executes the job.
48
	 *
49
	 * @throws \Aimeos\Controller\Jobs\Exception If an error occurs
50
	 */
51
	public function run()
52
	{
53
		$context = $this->getContext();
54
		$config = $context->getConfig();
55
		$logger = $context->getLogger();
56
57
		$names = (array) $config->get( 'controller/common/subscription/process/processors', [] );
58
59
		$date = date( 'Y-m-d' );
60
		$processors = $this->getProcessors( $names );
61
		$manager = \Aimeos\MShop::create( $context, 'subscription' );
62
63
		$search = $manager->createSearch( true );
64
		$expr = [
65
			$search->compare( '<=', 'subscription.datenext', $date ),
66
			$search->combine( '||', [
67
				$search->compare( '==', 'subscription.dateend', null ),
68
				$search->compare( '>', 'subscription.dateend', $date ),
69
			] ),
70
			$search->getConditions(),
71
		];
72
		$search->setConditions( $search->combine( '&&', $expr ) );
73
		$search->setSortations( [$search->sort( '+', 'subscription.id' )] );
74
75
		$start = 0;
76
77
		do
78
		{
79
			$search->setSlice( $start, 100 );
80
			$items = $manager->searchItems( $search );
81
82
			foreach( $items as $item )
83
			{
84
				try
85
				{
86
					foreach( $processors as $processor ) {
87
						$processor->renewBefore( $item );
88
					}
89
90
					$context = $this->createContext( $item->getOrderBaseId() );
91
					$newOrder = $this->createOrderBase( $context, $item );
92
					$newInvoice = $this->createOrderInvoice( $context, $newOrder );
93
94
					try
95
					{
96
						$this->createPayment( $context, $newOrder, $newInvoice );
97
98
						$interval = new \DateInterval( $item->getInterval() );
99
						$date = date_create()->add( $interval )->format( 'Y-m-d' );
100
						$item = $item->setDateNext( $date )->setPeriod( $item->getPeriod() + 1 );
101
					}
102
					catch( \Exception $e )
103
					{
104
						$item->setReason( \Aimeos\MShop\Subscription\Item\Iface::REASON_PAYMENT );
105
						$item->setDateEnd( date_create()->format( 'Y-m-d' ) );
106
107
						throw $e;
108
					}
109
110
					foreach( $processors as $processor ) {
111
						$processor->renewAfter( $item, $newInvoice );
112
					}
113
				}
114
				catch( \Exception $e )
115
				{
116
					$msg = 'Unable to process subscription with ID "%1$s": %2$s';
117
					$logger->log( sprintf( $msg, $item->getId(), $e->getMessage() ) );
118
					$logger->log( $e->getTraceAsString() );
119
				}
120
121
				$manager->saveItem( $item );
0 ignored issues
show
Bug introduced by
The method saveItem() does not exist on Aimeos\MShop\Common\Manager\Iface. Did you maybe mean saveItems()? ( Ignorable by Annotation )

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

121
				$manager->/** @scrutinizer ignore-call */ 
122
              saveItem( $item );

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...
122
			}
123
124
			$count = count( $items );
125
			$start += $count;
126
		}
127
		while( $count === $search->getSliceSize() );
128
	}
129
130
131
	/**
132
	 * Adds the given addresses to the basket
133
	 *
134
	 * @param \Aimeos\MShop\Context\Item\Iface Context object
0 ignored issues
show
Bug introduced by
The type Aimeos\Controller\Jobs\S...n\Process\Renew\Context was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
135
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket object to add the addresses to
136
	 * @param array $addresses Associative list of type as key and address object implementing \Aimeos\MShop\Order\Item\Base\Address\Iface as value
137
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Order with addresses added
138
	 */
139
	protected function addBasketAddresses( \Aimeos\MShop\Context\Item\Iface $context,
140
		\Aimeos\MShop\Order\Item\Base\Iface $newBasket, array $addresses ) : \Aimeos\MShop\Order\Item\Base\Iface
141
	{
142
		foreach( $addresses as $type => $orderAddresses )
143
		{
144
			$idx = 0;
145
146
			foreach( $orderAddresses as $orderAddress ) {
147
				$newBasket->addAddress( $orderAddress->setId( null ), $type, $idx );
148
			}
149
		}
150
151
		return $newBasket;
152
	}
153
154
155
	/**
156
	 * Adds the given coupon codes to basket if enabled
157
	 *
158
	 * @param \Aimeos\MShop\Context\Item\Iface Context object
159
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Order including product and addresses
160
	 * @param array $codes List of coupon codes that should be added to the given basket
161
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Basket, maybe with coupons added
162
	 */
163
	protected function addBasketCoupons( \Aimeos\MShop\Context\Item\Iface $context,
164
		\Aimeos\MShop\Order\Item\Base\Iface $basket, array $codes ) : \Aimeos\MShop\Order\Item\Base\Iface
165
	{
166
		/** controller/jobs/subcription/process/renew/standard/use-coupons
167
		 * Applies the coupons of the previous order also to the new one
168
		 *
169
		 * Reuse coupon codes added to the basket by the customer the first time
170
		 * again in new subcription orders. If they have any effect depends on
171
		 * the codes still being active (status, time frame and count) and the
172
		 * decorators added to the coupon providers in the admin interface.
173
		 *
174
		 * @param boolean True to reuse coupon codes, false to remove coupons
175
		 * @category Developer
176
		 * @category User
177
		 * @since 2018.10
178
		 */
179
		if( $context->getConfig()->get( 'controller/jobs/subcription/process/renew/standard/use-coupons', false ) )
180
		{
181
			foreach( $codes as $code )
182
			{
183
				try {
184
					$basket->addCoupon( $code );
185
				} catch( \Aimeos\MShop\Plugin\Provider\Exception $e ) {
186
					$basket->deleteCoupon( $code );
187
				} catch( \Aimeos\MShop\Coupon\Exception $e ) {
188
					$basket->deleteCoupon( $code );
189
				}
190
			}
191
		}
192
193
		return $basket;
194
	}
195
196
197
	/**
198
	 * Adds the given products to the basket
199
	 *
200
	 * @param \Aimeos\MShop\Context\Item\Iface Context object
201
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket object to add the products to
202
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface[] $orderProducts List of product items
203
	 * @param string $orderProductId Unique ID of the ordered subscription product
204
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Order with products added
205
	 */
206
	protected function addBasketProducts( \Aimeos\MShop\Context\Item\Iface $context,
207
		\Aimeos\MShop\Order\Item\Base\Iface $newBasket, array $orderProducts, $orderProductId ) : \Aimeos\MShop\Order\Item\Base\Iface
208
	{
209
		foreach( $orderProducts as $orderProduct )
210
		{
211
			if( $orderProduct->getId() == $orderProductId )
212
			{
213
				foreach( $orderProduct->getAttributeItems() as $attrItem ) {
214
					$attrItem->setId( null );
215
				}
216
				$newBasket->addProduct( $orderProduct->setId( null ) );
217
			}
218
		}
219
220
		return $newBasket;
221
	}
222
223
224
	/**
225
	 * Adds a matching delivery and payment service to the basket
226
	 *
227
	 * @param \Aimeos\MShop\Context\Item\Iface Context object
228
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket object to add the services to
229
	 * @param array $services Associative list of type as key and list of service objects implementing \Aimeos\MShop\Order\Item\Base\Service\Iface as values
230
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Order with delivery and payment service added
231
	 */
232
	protected function addBasketServices( \Aimeos\MShop\Context\Item\Iface $context,
233
		\Aimeos\MShop\Order\Item\Base\Iface $newBasket, array $services ) : \Aimeos\MShop\Order\Item\Base\Iface
234
	{
235
		$type = \Aimeos\MShop\Order\Item\Base\Service\Base::TYPE_PAYMENT;
236
237
		if( isset( $services[$type] ) )
238
		{
239
			$idx = 0;
240
241
			foreach( $services[$type] as $orderService )
242
			{
243
				foreach( $orderService->getAttributeItems() as $attrItem ) {
244
					$attrItem->setId( null );
245
				}
246
				$newBasket->addService( $orderService->setId( null ), $type, $idx++ );
247
			}
248
		}
249
250
		$idx = 0;
251
		$type = \Aimeos\MShop\Order\Item\Base\Service\Base::TYPE_DELIVERY;
252
253
		$serviceManager = \Aimeos\MShop::create( $context, 'service' );
254
		$orderServiceManager = \Aimeos\MShop::create( $context, 'order/base/service' );
255
256
		$search = $serviceManager->createSearch( true );
257
		$search->setSortations( [$search->sort( '+', 'service.position' )] );
258
		$search->setConditions( $search->compare( '==', 'service.type', $type ) );
259
260
		foreach( $serviceManager->searchItems( $search, ['media', 'price', 'text'] ) as $item )
261
		{
262
			$provider = $serviceManager->getProvider( $item, $item->getType() );
263
264
			if( $provider->isAvailable( $newBasket ) === true )
265
			{
266
				$orderServiceItem = $orderServiceManager->createItem()->copyFrom( $item );
267
				return $newBasket->addService( $orderServiceItem, $type, $idx++ );
268
			}
269
		}
270
271
		return $newBasket;
272
	}
273
274
275
	/**
276
	 * Creates a new context based on the order and the customer the subscription belongs to
277
	 *
278
	 * @param string $baseId Unique order base ID
279
	 * @return \Aimeos\MShop\Context\Item\Iface New context object
280
	 */
281
	protected function createContext( string $baseId ) : \Aimeos\MShop\Context\Item\Iface
282
	{
283
		$context = clone $this->getContext();
284
285
		$manager = \Aimeos\MShop::create( $context, 'order/base' );
286
		$baseItem = $manager->getItem( $baseId );
287
288
		$locale = $baseItem->getLocale();
289
		$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
290
291
		$manager = \Aimeos\MShop::create( $context, 'locale' );
292
		$locale = $manager->bootstrap( $baseItem->getSiteCode(), $locale->getLanguageId(), $locale->getCurrencyId(), false, $level );
293
294
		$context->setLocale( $locale );
295
296
		try
297
		{
298
			$manager = \Aimeos\MShop::create( $context, 'customer' );
299
			$customerItem = $manager->getItem( $baseItem->getCustomerId(), ['customer/group'] );
300
301
			$context->setUserId( $baseItem->getCustomerId() );
302
			$context->setGroupIds( $customerItem->getGroups() );
303
		}
304
		catch( \Exception $e ) {} // Subscription without account
305
306
		return $context;
307
	}
308
309
310
	/**
311
	 * Creates and stores a new order for the subscribed product
312
	 *
313
	 * @param \Aimeos\MShop\Context\Item\Iface Context object
314
	 * @param \Aimeos\MShop\Subscription\Item\Iface $subscription Subscription item with order base ID and order product ID
315
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Complete order with product, addresses and services saved to the storage
316
	 */
317
	protected function createOrderBase( \Aimeos\MShop\Context\Item\Iface $context, \Aimeos\MShop\Subscription\Item\Iface $subscription ) : \Aimeos\MShop\Order\Item\Base\Iface
318
	{
319
		$manager = \Aimeos\MShop::create( $context, 'order/base' );
320
321
		$basket = $manager->load( $subscription->getOrderBaseId() );
322
		$newBasket = $manager->createItem()->setCustomerId( $basket->getCustomerId() );
323
324
		$newBasket = $this->addBasketProducts( $context, $newBasket, $basket->getProducts(), $subscription->getOrderProductId() );
325
		$newBasket = $this->addBasketAddresses( $context, $newBasket, $basket->getAddresses() );
326
		$newBasket = $this->addBasketServices( $context, $newBasket, $basket->getServices() );
327
		$newBasket = $this->addBasketCoupons( $context, $newBasket, array_keys( $basket->getCoupons() ) );
328
329
		return $manager->store( $newBasket );
330
	}
331
332
333
	/**
334
	 * Creates and stores a new invoice for the given order basket
335
	 *
336
	 * @param \Aimeos\MShop\Context\Item\Iface Context object
337
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Complete order with product, addresses and services saved to the storage
338
	 * @return \Aimeos\MShop\Order\Item\Iface New invoice item associated to the order saved to the storage
339
	 */
340
	protected function createOrderInvoice( \Aimeos\MShop\Context\Item\Iface $context, \Aimeos\MShop\Order\Item\Base\Iface $basket ) : \Aimeos\MShop\Order\Item\Iface
341
	{
342
		$manager = \Aimeos\MShop::create( $context, 'order' );
343
344
		$item = $manager->createItem();
345
		$item->setBaseId( $basket->getId() );
346
		$item->setType( 'subscription' );
347
348
		return $manager->saveItem( $item );
349
	}
350
351
352
	/**
353
	 * Creates a new payment for the given order and invoice
354
	 *
355
	 * @param \Aimeos\MShop\Context\Item\Iface Context object
356
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Complete order with product, addresses and services
357
	 * @param \Aimeos\MShop\Order\Item\Iface New invoice item associated to the order
0 ignored issues
show
Bug introduced by
The type Aimeos\Controller\Jobs\S...ption\Process\Renew\New was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
358
	 */
359
	protected function createPayment( \Aimeos\MShop\Context\Item\Iface $context, \Aimeos\MShop\Order\Item\Base\Iface $basket,
360
		\Aimeos\MShop\Order\Item\Iface $invoice )
361
	{
362
		$manager = \Aimeos\MShop::create( $context, 'service' );
363
364
		foreach( $basket->getService( \Aimeos\MShop\Order\Item\Base\Service\Base::TYPE_PAYMENT ) as $service )
365
		{
366
			$item = $manager->getItem( $service->getServiceId() );
367
			$provider = $manager->getProvider( $item, 'payment' );
368
369
			$provider->repay( $invoice );
370
		}
371
	}
372
}
373