Completed
Push — master ( 44d316...b85694 )
by Aimeos
02:03
created

Standard::addBasketCoupons()   B

Complexity

Conditions 5
Paths 2

Size

Total Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 47
rs 8.8452
c 0
b 0
f 0
cc 5
nc 2
nop 3
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 coupon code to basket if enabled
129
	 *
130
	 * @param \Aimeos\MShop\Context\Item\Iface Context object
131
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Order including product and addresses
132
	 * @param array $codes List of coupon codes that should be added to the given basket
133
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Basket, maybe with coupons added
134
	 */
135
	protected function addBasketCoupons( \Aimeos\MShop\Context\Item\Iface $context, \Aimeos\MShop\Order\Item\Base\Iface $basket, array $codes )
136
	{
137
		/** controller/jobs/subcription/process/renew/standard/use-coupons
138
		 *
139
		 * Reuse coupon codes added to the basket by the customer the first time
140
		 * again in new subcription orders. If they have any effect depends on
141
		 * the codes still being active (status, time frame and count) and the
142
		 * decorators added to the coupon providers in the admin interface.
143
		 *
144
		 * @param boolean True to reuse coupon codes, false to remove coupons
145
		 * @category Developer
146
		 * @category User
147
		 * @since 2018.10
148
		 */
149
		if( $context->getConfig()->get( 'controller/jobs/subcription/process/renew/standard/use-coupons', false ) )
150
		{
151
			$manager = \Aimeos\MShop\Factory::createManager( $context, 'coupon' );
152
			$codeManager = \Aimeos\MShop\Factory::createManager( $context, 'coupon/code' );
153
154
			foreach( $codes as $code )
155
			{
156
				$search = $manager->createSearch( true )->setSlice( 0, 1 );
157
				$expr = [
158
					$search->compare( '==', 'coupon.code.code', $code ),
159
					$codeManager->createSearch( true )->getConditions(),
160
					$search->getConditions(),
161
				];
162
				$search->setConditions( $search->combine( '&&', $expr ) );
163
164
				$result = $manager->searchItems( $search );
165
166
				if( ( $item = reset( $result ) ) === false ) {
167
					continue;
168
				}
169
170
				$provider = $manager->getProvider( $item, $code );
171
172
				if( $provider->isAvailable( $basket ) !== true ) {
173
					continue;
174
				}
175
176
				$provider->addCoupon( $basket );
177
			}
178
		}
179
180
		return $basket;
181
	}
182
183
184
	/**
185
	 * Adds a matching delivery service to the basket
186
	 *
187
	 * @param \Aimeos\MShop\Context\Item\Iface Context object
188
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Order including product and addresses
189
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Order with delivery service added
190
	 */
191
	protected function addDeliveryService( \Aimeos\MShop\Context\Item\Iface $context, \Aimeos\MShop\Order\Item\Base\Iface $basket )
192
	{
193
		$type = \Aimeos\MShop\Order\Item\Base\Service\Base::TYPE_DELIVERY;
194
195
		$serviceManager = \Aimeos\MShop\Factory::createManager( $context, 'service' );
196
		$orderServiceManager = \Aimeos\MShop\Factory::createManager( $context, 'order/base/service' );
197
198
		$search = $serviceManager->createSearch( true );
199
		$search->setSortations( [$search->sort( '+', 'service.position' )] );
200
		$search->setConditions( $search->compare( '==', 'service.type.code', $type ) );
201
202
		foreach( $serviceManager->searchItems( $search, ['media', 'price', 'text'] ) as $item )
203
		{
204
			$provider = $serviceManager->getProvider( $item, $item->getType() );
205
206
			if( $provider->isAvailable( $basket ) === true )
207
			{
208
				$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...
209
				$basket->addService( $orderServiceItem, $type );
210
211
				return $basket;
212
			}
213
		}
214
215
		return $basket;
216
	}
217
218
219
	/**
220
	 * Creates a new context based on the order and the customer the subscription belongs to
221
	 *
222
	 * @param string $baseId Unique order base ID
223
	 * @return \Aimeos\MShop\Context\Item\Iface New context object
224
	 */
225
	protected function createContext( $baseId )
226
	{
227
		$context = clone $this->getContext();
228
229
		$manager = \Aimeos\MShop\Factory::createManager( $context, 'order/base' );
230
		$baseItem = $manager->getItem( $baseId );
231
232
		$locale = $baseItem->getLocale();
233
		$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
234
235
		$manager = \Aimeos\MShop\Factory::createManager( $context, 'locale' );
236
		$locale = $manager->bootstrap( $baseItem->getSiteCode(), $locale->getLanguageId(), $locale->getCurrencyId(), false, $level );
237
238
		$context->setLocale( $locale );
239
240
		try
241
		{
242
			$manager = \Aimeos\MShop\Factory::createManager( $context, 'customer' );
243
			$customerItem = $manager->getItem( $baseItem->getCustomerId(), ['customer/group'] );
244
245
			$context->setUserId( $baseItem->getCustomerId() );
246
			$context->setGroupIds( $customerItem->getGroups() );
247
		}
248
		catch( \Exception $e ) {} // Subscription without account
249
250
		return $context;
251
	}
252
253
254
	/**
255
	 * Creates and stores a new order for the subscribed product
256
	 *
257
	 * @param \Aimeos\MShop\Context\Item\Iface Context object
258
	 * @param \Aimeos\MShop\Subscription\Item\Iface $subscription Subscription item with order base ID and order product ID
259
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Complete order with product, addresses and services saved to the storage
260
	 */
261
	protected function createOrderBase( \Aimeos\MShop\Context\Item\Iface $context, \Aimeos\MShop\Subscription\Item\Iface $subscription )
262
	{
263
		$manager = \Aimeos\MShop\Factory::createManager( $context, 'order/base' );
264
		$basket = $manager->load( $subscription->getOrderBaseId() );
265
266
		$newBasket = $manager->createItem();
267
		$newBasket->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...
268
269
		foreach( $basket->getProducts() as $orderProduct )
270
		{
271
			if( $orderProduct->getId() === $subscription->getOrderProductId() )
272
			{
273
				foreach( $orderProduct->getAttributeItems() as $attrItem ) {
274
					$attrItem->setId( null );
275
				}
276
				$newBasket->addProduct( $orderProduct->setId( null ) );
0 ignored issues
show
Bug introduced by
The method addProduct() 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...
277
			}
278
		}
279
280
		foreach( $basket->getAddresses() as $type => $orderAddress ) {
281
			$newBasket->setAddress( $orderAddress, $type );
0 ignored issues
show
Bug introduced by
The method setAddress() 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...
282
		}
283
284
		$type = \Aimeos\MShop\Order\Item\Base\Service\Base::TYPE_PAYMENT;
285
286
		foreach( $basket->getService( $type ) as $orderService ) {
287
			$newBasket->addService( $orderService, $type );
0 ignored issues
show
Bug introduced by
The method addService() 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...
288
		}
289
290
		$newBasket = $this->addDeliveryService( $context, $newBasket );
0 ignored issues
show
Documentation introduced by
$newBasket is of type object<Aimeos\MShop\Attribute\Item\Iface>, but the function expects a object<Aimeos\MShop\Order\Item\Base\Iface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
291
		$newBasket = $this->addBasketCoupons( $context, $newBasket, array_keys( $basket->getCoupons() ) );
292
293
		return $manager->store( $newBasket );
294
	}
295
296
297
	/**
298
	 * Creates and stores a new invoice for the given order basket
299
	 *
300
	 * @param \Aimeos\MShop\Context\Item\Iface Context object
301
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Complete order with product, addresses and services saved to the storage
302
	 * @return \Aimeos\MShop\Order\Item\Iface New invoice item associated to the order saved to the storage
303
	 */
304
	protected function createOrderInvoice( \Aimeos\MShop\Context\Item\Iface $context, \Aimeos\MShop\Order\Item\Base\Iface $basket )
305
	{
306
		$manager = \Aimeos\MShop\Factory::createManager( $context, 'order' );
307
308
		$item = $manager->createItem();
309
		$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...
310
		$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...
311
312
		return $manager->saveItem( $item );
313
	}
314
315
316
	/**
317
	 * Creates a new payment for the given order and invoice
318
	 *
319
	 * @param \Aimeos\MShop\Context\Item\Iface Context object
320
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Complete order with product, addresses and services
321
	 * @param \Aimeos\MShop\Order\Item\Iface New invoice item associated to the order
322
	 */
323
	protected function createPayment( \Aimeos\MShop\Context\Item\Iface $context, \Aimeos\MShop\Order\Item\Base\Iface $basket,
324
		\Aimeos\MShop\Order\Item\Iface $invoice )
325
	{
326
		$manager = \Aimeos\MShop\Factory::createManager( $context, 'service' );
327
328
		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...
329
		{
330
			$item = $manager->getItem( $service->getServiceId() );
331
			$provider = $manager->getProvider( $item, 'payment' );
332
333
			$provider->repay( $invoice );
334
		}
335
	}
336
}
337