Completed
Push — master ( 1ad2fa...984043 )
by Aimeos
07:51
created

Standard::createOrderBase()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.7998
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 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()
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()
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\Factory::createManager( $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
					$context = $this->createContext( $item->getOrderBaseId() );
87
					$newOrder = $this->createOrderBase( $context, $item );
88
					$newInvoice = $this->createOrderInvoice( $context, $newOrder );
89
90
					try
91
					{
92
						$this->createPayment( $context, $newOrder, $newInvoice );
93
94
						$interval = new \DateInterval( $item->getInterval() );
95
						$item->setDateNext( date_create()->add( $interval )->format( 'Y-m-d' ) );
96
					}
97
					catch( \Exception $e )
98
					{
99
						$item->setReason( \Aimeos\MShop\Subscription\Item\Iface::REASON_PAYMENT );
100
						$item->setDateEnd( date_create()->format( 'Y-m-d' ) );
101
						$manager->saveItem( $item );
102
103
						throw $e;
104
					}
105
106
					$manager->saveItem( $item );
107
108
					foreach( $processors as $processor ) {
109
						$processor->renew( $item, $newInvoice );
110
					}
111
				}
112
				catch( \Exception $e )
113
				{
114
					$msg = 'Unable to process subscription with ID "%1$s": %2$s';
115
					$logger->log( sprintf( $msg, $item->getId(), $e->getMessage() ) );
116
					$logger->log( $e->getTraceAsString() );
117
				}
118
			}
119
120
			$count = count( $items );
121
			$start += $count;
122
		}
123
		while( $count === $search->getSliceSize() );
124
	}
125
126
127
	/**
128
	 * Adds the given addresses to the basket
129
	 *
130
	 * @param \Aimeos\MShop\Context\Item\Iface Context object
131
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket object to add the addresses to
0 ignored issues
show
Documentation introduced by
There is no parameter named $basket. Did you maybe mean $newBasket?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
132
	 * @param array $addresses Associative list of type as key and address object implementing \Aimeos\MShop\Order\Item\Base\Address\Iface as value
133
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Order with addresses added
134
	 */
135
	protected function addBasketAddresses( \Aimeos\MShop\Context\Item\Iface $context,
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
136
		\Aimeos\MShop\Order\Item\Base\Iface $newBasket, array $addresses )
137
	{
138
		foreach( $addresses as $type => $orderAddress ) {
139
			$newBasket->setAddress( $orderAddress, $type );
140
		}
141
142
		return $newBasket;
143
	}
144
145
146
	/**
147
	 * Adds the given coupon codes to basket if enabled
148
	 *
149
	 * @param \Aimeos\MShop\Context\Item\Iface Context object
150
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Order including product and addresses
151
	 * @param array $codes List of coupon codes that should be added to the given basket
152
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Basket, maybe with coupons added
153
	 */
154
	protected function addBasketCoupons( \Aimeos\MShop\Context\Item\Iface $context,
155
		\Aimeos\MShop\Order\Item\Base\Iface $basket, array $codes )
156
	{
157
		/** controller/jobs/subcription/process/renew/standard/use-coupons
158
		 *
159
		 * Reuse coupon codes added to the basket by the customer the first time
160
		 * again in new subcription orders. If they have any effect depends on
161
		 * the codes still being active (status, time frame and count) and the
162
		 * decorators added to the coupon providers in the admin interface.
163
		 *
164
		 * @param boolean True to reuse coupon codes, false to remove coupons
165
		 * @category Developer
166
		 * @category User
167
		 * @since 2018.10
168
		 */
169
		if( $context->getConfig()->get( 'controller/jobs/subcription/process/renew/standard/use-coupons', false ) )
170
		{
171
			$manager = \Aimeos\MShop\Factory::createManager( $context, 'coupon' );
172
			$codeManager = \Aimeos\MShop\Factory::createManager( $context, 'coupon/code' );
173
174
			foreach( $codes as $code )
175
			{
176
				$search = $manager->createSearch( true )->setSlice( 0, 1 );
177
				$expr = [
178
					$search->compare( '==', 'coupon.code.code', $code ),
179
					$codeManager->createSearch( true )->getConditions(),
180
					$search->getConditions(),
181
				];
182
				$search->setConditions( $search->combine( '&&', $expr ) );
183
184
				$result = $manager->searchItems( $search );
185
186
				if( ( $item = reset( $result ) ) === false ) {
187
					continue;
188
				}
189
190
				$provider = $manager->getProvider( $item, $code );
191
192
				if( $provider->isAvailable( $basket ) !== true ) {
193
					continue;
194
				}
195
196
				$provider->addCoupon( $basket );
197
			}
198
		}
199
200
		return $basket;
201
	}
202
203
204
	/**
205
	 * Adds the given products to the basket
206
	 *
207
	 * @param \Aimeos\MShop\Context\Item\Iface Context object
208
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket object to add the products to
0 ignored issues
show
Documentation introduced by
There is no parameter named $basket. Did you maybe mean $newBasket?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
209
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface[] $orderProducts List of product items
210
	 * @param string $orderProductId Unique ID of the ordered subscription product
211
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Order with products added
212
	 */
213
	protected function addBasketProducts( \Aimeos\MShop\Context\Item\Iface $context,
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
214
		\Aimeos\MShop\Order\Item\Base\Iface $newBasket, array $orderProducts, $orderProductId )
215
	{
216
		foreach( $orderProducts as $orderProduct )
217
		{
218
			if( $orderProduct->getId() == $orderProductId )
219
			{
220
				foreach( $orderProduct->getAttributeItems() as $attrItem ) {
221
					$attrItem->setId( null );
222
				}
223
				$newBasket->addProduct( $orderProduct->setId( null ) );
0 ignored issues
show
Compatibility introduced by
$orderProduct->setId(null) of type object<Aimeos\MShop\Common\Item\Iface> is not a sub-type of object<Aimeos\MShop\Orde...tem\Base\Product\Iface>. It seems like you assume a child interface of the interface Aimeos\MShop\Common\Item\Iface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
224
			}
225
		}
226
227
		return $newBasket;
228
	}
229
230
231
	/**
232
	 * Adds a matching delivery and payment service to the basket
233
	 *
234
	 * @param \Aimeos\MShop\Context\Item\Iface Context object
235
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket object to add the services to
0 ignored issues
show
Documentation introduced by
There is no parameter named $basket. Did you maybe mean $newBasket?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
236
	 * @param array $services Associative list of type as key and list of service objects implementing \Aimeos\MShop\Order\Item\Base\Service\Iface as values
237
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Order with delivery and payment service added
238
	 */
239
	protected function addBasketServices( \Aimeos\MShop\Context\Item\Iface $context,
240
		\Aimeos\MShop\Order\Item\Base\Iface $newBasket, array $services )
241
	{
242
		$type = \Aimeos\MShop\Order\Item\Base\Service\Base::TYPE_PAYMENT;
243
244
		if( isset( $services[$type] ) )
245
		{
246
			foreach( $services[$type] as $orderService ) {
247
				$newBasket->addService( $orderService, $type );
248
			}
249
		}
250
251
		$type = \Aimeos\MShop\Order\Item\Base\Service\Base::TYPE_DELIVERY;
252
253
		$serviceManager = \Aimeos\MShop\Factory::createManager( $context, 'service' );
254
		$orderServiceManager = \Aimeos\MShop\Factory::createManager( $context, 'order/base/service' );
255
256
		$search = $serviceManager->createSearch( true );
257
		$search->setSortations( [$search->sort( '+', 'service.position' )] );
258
		$search->setConditions( $search->compare( '==', 'service.type.code', $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 );
0 ignored issues
show
Bug introduced by
The method copyFrom() does not seem to exist on object<Aimeos\MShop\Attribute\Item\Iface>.

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...
267
				return $newBasket->addService( $orderServiceItem, $type );
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( $baseId )
282
	{
283
		$context = clone $this->getContext();
284
285
		$manager = \Aimeos\MShop\Factory::createManager( $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\Factory::createManager( $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\Factory::createManager( $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 )
318
	{
319
		$manager = \Aimeos\MShop\Factory::createManager( $context, 'order/base' );
320
321
		$basket = $manager->load( $subscription->getOrderBaseId() );
322
		$newBasket = $manager->createItem()->setCustomerId( $basket->getCustomerId() );
0 ignored issues
show
Bug introduced by
The method setCustomerId() does not seem to exist on object<Aimeos\MShop\Attribute\Item\Iface>.

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...
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 )
341
	{
342
		$manager = \Aimeos\MShop\Factory::createManager( $context, 'order' );
343
344
		$item = $manager->createItem();
345
		$item->setBaseId( $basket->getId() );
0 ignored issues
show
Bug introduced by
The method setBaseId() does not seem to exist on object<Aimeos\MShop\Attribute\Item\Iface>.

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...
346
		$item->setType( 'subscription' );
0 ignored issues
show
Bug introduced by
The method setType() does not exist on Aimeos\MShop\Attribute\Item\Iface. Did you maybe mean setTypeId()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
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
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\Factory::createManager( $context, 'service' );
363
364
		foreach( $basket->getService( \Aimeos\MShop\Order\Item\Base\Service\Base::TYPE_PAYMENT ) as $service )
0 ignored issues
show
Bug introduced by
The expression $basket->getService(\Aim...ice\Base::TYPE_PAYMENT) of type object<Aimeos\MShop\Orde...em\Base\Service\Iface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
365
		{
366
			$item = $manager->getItem( $service->getServiceId() );
367
			$provider = $manager->getProvider( $item, 'payment' );
368
369
			$provider->repay( $invoice );
370
		}
371
	}
372
}
373