Passed
Push — master ( 0e8507...c5cb7a )
by Aimeos
07:58
created

Standard::getProductItems()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 2
dl 0
loc 13
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2014
6
 * @copyright Aimeos (aimeos.org), 2015-2020
7
 * @package Controller
8
 * @subpackage Customer
9
 */
10
11
12
namespace Aimeos\Controller\Jobs\Customer\Email\Watch;
13
14
15
/**
16
 * Product notification e-mail job controller.
17
 *
18
 * @package Controller
19
 * @subpackage Customer
20
 */
21
class Standard
22
	extends \Aimeos\Controller\Jobs\Base
23
	implements \Aimeos\Controller\Jobs\Iface
24
{
25
	private $client;
26
	private $types;
0 ignored issues
show
introduced by
The private property $types is not used, and could be removed.
Loading history...
27
28
29
	/**
30
	 * Returns the localized name of the job.
31
	 *
32
	 * @return string Name of the job
33
	 */
34
	public function getName() : string
35
	{
36
		return $this->getContext()->getI18n()->dt( 'controller/jobs', 'Product notification e-mails' );
37
	}
38
39
40
	/**
41
	 * Returns the localized description of the job.
42
	 *
43
	 * @return string Description of the job
44
	 */
45
	public function getDescription() : string
46
	{
47
		return $this->getContext()->getI18n()->dt( 'controller/jobs', 'Sends e-mails for watched products' );
48
	}
49
50
51
	/**
52
	 * Executes the job.
53
	 *
54
	 * @throws \Aimeos\Controller\Jobs\Exception If an error occurs
55
	 */
56
	public function run()
57
	{
58
		$langIds = [];
59
		$context = $this->getContext();
60
61
		$localeManager = \Aimeos\MShop::create( $context, 'locale' );
62
		$custManager = \Aimeos\MShop::create( $context, 'customer' );
63
64
		$localeItems = $localeManager->search( $localeManager->filter() );
65
66
		foreach( $localeItems as $localeItem )
67
		{
68
			$langId = $localeItem->getLanguageId();
69
70
			if( isset( $langIds[$langId] ) ) {
71
				continue;
72
			}
73
74
			$langIds[$langId] = true;
75
			// fetch language specific text and media items for products
76
			$context->getLocale()->setLanguageId( $langId );
77
78
			$search = $custManager->filter( true );
79
			$func = $search->createFunction( 'customer:has', ['product', 'watch'] );
80
			$expr = array(
81
				$search->compare( '==', 'customer.languageid', $langId ),
82
				$search->compare( '!=', $func, null ),
83
				$search->getConditions(),
84
			);
85
			$search->setConditions( $search->combine( '&&', $expr ) );
86
			$search->setSortations( array( $search->sort( '+', 'customer.id' ) ) );
87
88
			$start = 0;
89
90
			do
91
			{
92
				$search->setSlice( $start );
93
				$customers = $custManager->search( $search );
94
95
				$this->execute( $context, $customers );
96
97
				$count = count( $customers );
98
				$start += $count;
99
			}
100
			while( $count >= $search->getSliceSize() );
101
		}
102
	}
103
104
105
	/**
106
	 * Sends product notifications for the given customers in their language
107
	 *
108
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context item object
109
	 * @param \Aimeos\Map $customers List of customer items implementing \Aimeos\MShop\Customer\Item\Iface
110
	 */
111
	protected function execute( \Aimeos\MShop\Context\Item\Iface $context, \Aimeos\Map $customers )
112
	{
113
		$prodIds = $custIds = [];
114
		$listItems = $this->getListItems( $context, $customers->keys() );
115
		$listManager = \Aimeos\MShop::create( $context, 'customer/lists' );
116
117
		foreach( $listItems as $id => $listItem )
118
		{
119
			$refId = $listItem->getRefId();
120
			$custIds[$listItem->getParentId()][$id] = $refId;
121
			$prodIds[$refId] = $refId;
122
		}
123
124
		$date = date( 'Y-m-d H:i:s' );
125
		$products = $this->getProducts( $context, $prodIds, 'default' );
126
127
		foreach( $custIds as $custId => $list )
128
		{
129
			$custListItems = $listIds = [];
130
131
			foreach( $list as $listId => $prodId )
132
			{
133
				$listItem = $listItems[$listId];
134
135
				if( $listItem->getDateEnd() < $date ) {
136
					$listIds[] = $listId;
137
				}
138
139
				$custListItems[$listId] = $listItems[$listId];
140
			}
141
142
			try
143
			{
144
				$custProducts = $this->getProductList( $products, $custListItems );
145
146
				if( !empty( $custProducts ) && ( $custItem = $customers->get( $custId ) ) !== null )
147
				{
148
					$addr = $custItem->getPaymentAddress();
149
					$this->sendMail( $context, $addr, $custProducts );
150
151
					$str = sprintf( 'Sent product notification e-mail to "%1$s"', $addr->getEmail() );
152
					$context->getLogger()->log( $str, \Aimeos\MW\Logger\Base::DEBUG );
153
154
					$listIds += array_keys( $custProducts );
155
				}
156
			}
157
			catch( \Exception $e )
158
			{
159
				$str = 'Error while trying to send product notification e-mail for customer ID "%1$s": %2$s';
160
				$context->getLogger()->log( sprintf( $str, $custId, $e->getMessage() ) );
161
			}
162
163
			$listManager->deleteItems( $listIds );
164
		}
165
	}
166
167
168
	/**
169
	 * Returns the product notification e-mail client
170
	 *
171
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context item object
172
	 * @return \Aimeos\Client\Html\Iface Product notification e-mail client
173
	 */
174
	protected function getClient( \Aimeos\MShop\Context\Item\Iface $context ) : \Aimeos\Client\Html\Iface
175
	{
176
		if( !isset( $this->client ) ) {
177
			$this->client = \Aimeos\Client\Html\Email\Watch\Factory::create( $context );
178
		}
179
180
		return $this->client;
181
	}
182
183
184
	/**
185
	 * Returns the list items for the given customer IDs and list type ID
186
	 *
187
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context item object
188
	 * @param array $custIds List of customer IDs
189
	 * @return \Aimeos\Map List of customer list items implementing \Aimeos\MShop\Common\Item\Lists\Iface
190
	 */
191
	protected function getListItems( \Aimeos\MShop\Context\Item\Iface $context, \Aimeos\Map $custIds ) : \Aimeos\Map
192
	{
193
		$listManager = \Aimeos\MShop::create( $context, 'customer/lists' );
194
195
		$search = $listManager->filter();
196
		$expr = array(
197
			$search->compare( '==', 'customer.lists.domain', 'product' ),
198
			$search->compare( '==', 'customer.lists.parentid', $custIds->toArray() ),
199
			$search->compare( '==', 'customer.lists.type', 'watch' ),
200
		);
201
		$search->setConditions( $search->combine( '&&', $expr ) );
202
		$search->setSlice( 0, 0x7fffffff );
203
204
		return $listManager->search( $search );
205
	}
206
207
208
	/**
209
	 * Returns a filtered list of products for which a notification should be sent
210
	 *
211
	 * @param \Aimeos\MShop\Product\Item\Iface[] $products List of product items
212
	 * @param \Aimeos\MShop\Common\Item\Lists\Iface[] $listItems List of customer list items
213
	 * @return array Multi-dimensional associative list of list IDs as key and product / price item maps as values
214
	 */
215
	protected function getProductList( \Aimeos\Map $products, array $listItems ) : array
216
	{
217
		$result = [];
218
		$priceManager = \Aimeos\MShop::create( $this->getContext(), 'price' );
219
220
		foreach( $listItems as $id => $listItem )
221
		{
222
			try
223
			{
224
				$refId = $listItem->getRefId();
225
				$config = $listItem->getConfig();
226
227
				if( ( $product = $products->get( $refId ) ) !== null )
228
				{
229
					$prices = $product->getRefItems( 'price', 'default', 'default' );
230
					$currencyId = ( isset( $config['currency'] ) ? $config['currency'] : null );
231
232
					$price = $priceManager->getLowestPrice( $prices, 1, $currencyId );
233
234
					if( isset( $config['stock'] ) && $config['stock'] == 1 ||
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (IssetNode && $config['s...'] > $price->getValue(), Probably Intended Meaning: IssetNode && ($config['s...] > $price->getValue())
Loading history...
235
						isset( $config['price'] ) && $config['price'] == 1 &&
236
						isset( $config['pricevalue'] ) && $config['pricevalue'] > $price->getValue()
237
					) {
238
						$result[$id]['item'] = $product;
239
						$result[$id]['currency'] = $currencyId;
240
						$result[$id]['price'] = $price;
241
					}
242
				}
243
			}
244
			catch( \Exception $e ) {; } // no price available
245
		}
246
247
		return $result;
248
	}
249
250
251
	/**
252
	 * Returns the products for the given IDs which are in stock
253
	 *
254
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context item object
255
	 * @param array $prodIds List of product IDs
256
	 * @param string $stockType Stock type code
257
	 */
258
	protected function getProducts( \Aimeos\MShop\Context\Item\Iface $context, array $prodIds, string $stockType )
259
	{
260
		$stockMap = [];
261
262
		$manager = \Aimeos\MShop::create( $context, 'product' );
263
		$filter = $manager->filter( true )->add( ['product.id' => $prodIds] )->slice( 0, count( $prodIds ) );
264
		$productItems = $manager->search( $filter, ['text', 'price', 'media'] );
265
266
		foreach( $this->getStockItems( $context, $productItems->keys()->toArray(), $stockType ) as $stockItem ) {
267
			$stockMap[$stockItem->getProductId()] = true;
268
		}
269
270
		foreach( $productItems as $productId => $productItem )
271
		{
272
			if( !isset( $stockMap[$productId] ) ) {
273
				unset( $productItems[$productId] );
274
			}
275
		}
276
277
		return $productItems;
278
	}
279
280
281
	/**
282
	 * Returns the stock items for the given product IDs
283
	 *
284
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context item object
285
	 * @param array $prodIds List of product IDs
286
	 * @param string $stockType Stock type code
287
	 * @return \Aimeos\Map Associative list of stock IDs as keys and stock items implementing \Aimeos\MShop\Stock\Item\Iface
288
	 */
289
	protected function getStockItems( \Aimeos\MShop\Context\Item\Iface $context, array $prodIds, string $stockType ) : \Aimeos\Map
290
	{
291
		$stockManager = \Aimeos\MShop::create( $context, 'stock' );
292
293
		$search = $stockManager->filter( true );
294
		$expr = array(
295
			$search->compare( '==', 'stock.productid', $prodIds ),
296
			$search->compare( '==', 'stock.type', $stockType ),
297
			$search->combine( '||', array(
298
				$search->compare( '==', 'stock.stocklevel', null ),
299
				$search->compare( '>', 'stock.stocklevel', 0 ),
300
			) ),
301
			$search->getConditions(),
302
		);
303
		$search->setConditions( $search->combine( '&&', $expr ) );
304
		$search->setSlice( 0, 100000 ); // performance speedup
305
306
		return $stockManager->search( $search );
307
	}
308
309
310
	/**
311
	 * Sends the notification e-mail for the given customer address and products
312
	 *
313
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context item object
314
	 * @param \Aimeos\MShop\Common\Item\Address\Iface $address Payment address of the customer
315
	 * @param array $products List of products a notification should be sent for
316
	 */
317
	protected function sendMail( \Aimeos\MShop\Context\Item\Iface $context,
318
		\Aimeos\MShop\Common\Item\Address\Iface $address, array $products )
319
	{
320
		$view = $context->getView();
321
		$view->extProducts = $products;
322
		$view->extAddressItem = $address;
323
324
		$params = [
325
			'locale' => $context->getLocale()->getLanguageId(),
326
			'site' => $context->getLocale()->getSiteItem()->getCode(),
327
		];
328
329
		$helper = new \Aimeos\MW\View\Helper\Param\Standard( $view, $params );
330
		$view->addHelper( 'param', $helper );
331
332
		$helper = new \Aimeos\MW\View\Helper\Number\Locale( $view, $context->getLocale()->getLanguageId() );
333
		$view->addHelper( 'number', $helper );
334
335
		$helper = new \Aimeos\MW\View\Helper\Translate\Standard( $view, $context->getI18n( $address->getLanguageId() ) );
336
		$view->addHelper( 'translate', $helper );
337
338
		$mailer = $context->getMail();
339
		$message = $mailer->createMessage();
340
341
		$helper = new \Aimeos\MW\View\Helper\Mail\Standard( $view, $message );
342
		$view->addHelper( 'mail', $helper );
343
344
		$client = $this->getClient( $context );
345
		$client->setView( $view );
346
		$client->getHeader();
347
		$client->getBody();
348
349
		$mailer->send( $message );
350
	}
351
}
352