Completed
Push — master ( 14839e...9c4bb8 )
by Aimeos
07:11 queued 01:55
created

Standard::getDescription()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 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
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
0 ignored issues
show
Coding Style introduced by
The extends keyword must be on the same line as the class name
Loading history...
Coding Style introduced by
Expected 0 spaces between "Base" and comma; 1 found
Loading history...
23
	implements \Aimeos\Controller\Jobs\Iface
0 ignored issues
show
Coding Style introduced by
The implements keyword must be on the same line as the class name
Loading history...
24
{
25
	private $client;
26
	private $warehouses;
27
28
29
	/**
30
	 * Returns the localized name of the job.
31
	 *
32
	 * @return string Name of the job
33
	 */
34
	public function getName()
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()
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 = array();
59
		$context = $this->getContext();
60
		$typeId = $this->getListTypeItem( 'watch' )->getId();
61
62
		$localeManager = \Aimeos\MShop\Factory::createManager( $context, 'locale' );
63
		$custManager = \Aimeos\MShop\Factory::createManager( $context, 'customer' );
64
65
		$localeItems = $localeManager->searchItems( $localeManager->createSearch() );
66
67
		foreach( $localeItems as $localeItem )
68
		{
69
			$langId = $localeItem->getLanguageId();
70
71
			if( isset( $langIds[$langId] ) ) {
72
				continue;
73
			}
74
75
			$langIds[$langId] = true;
76
			// fetch language specific text and media items for products
77
			$context->getLocale()->setLanguageId( $langId );
78
79
			$search = $custManager->createSearch( true );
80
			$expr = array(
81
				$search->compare( '==', 'customer.languageid', $langId ),
82
				$search->compare( '==', 'customer.lists.typeid', $typeId ),
83
				$search->compare( '==', 'customer.lists.domain', 'product' ),
84
				$search->getConditions(),
85
			);
86
			$search->setConditions( $search->combine( '&&', $expr ) );
87
			$search->setSortations( array( $search->sort( '+', 'customer.id' ) ) );
88
89
			$start = 0;
90
91
			do
92
			{
93
				$customers = $custManager->searchItems( $search );
94
95
				$this->execute( $context, $customers, $typeId );
96
97
				$count = count( $customers );
98
				$start += $count;
99
				$search->setSlice( $start );
100
			}
101
			while( $count >= $search->getSliceSize() );
102
		}
103
	}
104
105
106
	/**
107
	 * Sends product notifications for the given customers in their language
108
	 *
109
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context item object
110
	 * @param array $customers List of customer items implementing \Aimeos\MShop\Customer\Item\Iface
111
	 * @param string $listTypeId Customer list type ID
112
	 */
113
	protected function execute( \Aimeos\MShop\Context\Item\Iface $context, array $customers, $listTypeId )
114
	{
115
		$prodIds = $custIds = array();
116
		$whItem = $this->getWarehouseItem( 'default' );
117
		$listManager = \Aimeos\MShop\Factory::createManager( $context, 'customer/lists' );
118
		$listItems = $this->getListItems( $context, array_keys( $customers ), $listTypeId );
119
120
		foreach( $listItems as $id => $listItem )
121
		{
122
			$refId = $listItem->getRefId();
123
			$custIds[ $listItem->getParentId() ][$id] = $refId;
124
			$prodIds[$refId] = $refId;
125
		}
126
127
		$date = date( 'Y-m-d H:i:s' );
128
		$products = $this->getProducts( $context, $prodIds, $whItem->getId() );
129
130
		foreach( $custIds as $custId => $list )
131
		{
132
			$custListItems = $listIds = array();
133
134
			foreach( $list as $listId => $prodId )
135
			{
136
				$listItem = $listItems[$listId];
137
138
				if( $listItem->getDateEnd() < $date ) {
139
					$listIds[] = $listId;
140
				}
141
142
				$custListItems[$listId] = $listItems[$listId];
143
			}
144
145
			try
146
			{
147
				$custProducts = $this->getListProducts( $custListItems, $products );
148
149
				if( !empty( $custProducts ) )
150
				{
151
					$this->sendMail( $context, $customers[$custId]->getPaymentAddress(), $custProducts );
152
					$listIds += array_keys( $custProducts );
153
				}
154
			}
155
			catch( \Exception $e )
156
			{
157
				$str = 'Error while trying to send product notification e-mail for customer ID "%1$s": %2$s';
158
				$msg = sprintf( $str, $custId, $e->getMessage() );
159
				$context->getLogger()->log( $msg );
160
			}
161
162
			$listManager->deleteItems( $listIds );
163
		}
164
	}
165
166
167
	/**
168
	 * Returns the product notification e-mail client
169
	 *
170
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context item object
171
	 * @return \Aimeos\Client\Html\Iface Product notification e-mail client
172
	 */
173 View Code Duplication
	protected function getClient( \Aimeos\MShop\Context\Item\Iface $context )
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
174
	{
175
		if( !isset( $this->client ) )
176
		{
177
			$templatePaths = $this->getAimeos()->getCustomPaths( 'client/html' );
178
			$this->client = \Aimeos\Client\Html\Email\Watch\Factory::createClient( $context, $templatePaths );
179
		}
180
181
		return $this->client;
182
	}
183
184
185
	/**
186
	 * Returns the list items for the given customer IDs and list type ID
187
	 *
188
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context item object
189
	 * @param array $custIds List of customer IDs
190
	 * @param string $listTypeId Customer list type ID
191
	 * @return array List of customer list items implementing \Aimeos\MShop\Common\Item\Lists\Iface
192
	 */
193
	protected function getListItems( \Aimeos\MShop\Context\Item\Iface $context, array $custIds, $listTypeId )
194
	{
195
		$listManager = \Aimeos\MShop\Factory::createManager( $context, 'customer/lists' );
196
197
		$search = $listManager->createSearch();
198
		$expr = array(
199
			$search->compare( '==', 'customer.lists.parentid', $custIds ),
200
			$search->compare( '==', 'customer.lists.typeid', $listTypeId ),
201
			$search->compare( '==', 'customer.lists.domain', 'product' ),
202
		);
203
		$search->setConditions( $search->combine( '&&', $expr ) );
204
		$search->setSlice( 0, 0x7fffffff );
205
206
		return $listManager->searchItems( $search );
207
	}
208
209
210
	/**
211
	 * Returns a filtered list of products for which a notification should be sent
212
	 *
213
	 * @param array $listItems List of customer list items implementing \Aimeos\MShop\Common\Item\Lists\Iface
214
	 * @param array $products List of product items implementing \Aimeos\MShop\Product\Item\Iface
215
	 * @return array Multi-dimensional associative list of list IDs as key and product / price item maps as values
216
	 */
217
	protected function getListProducts( array $listItems, array $products )
218
	{
219
		$result = array();
220
		$priceManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'price' );
221
222
		foreach( $listItems as $id => $listItem )
223
		{
224
			try
225
			{
226
				$refId = $listItem->getRefId();
227
				$config = $listItem->getConfig();
228
229
				if( isset( $products[$refId] ) )
230
				{
231
					$prices = $products[$refId]->getRefItems( 'price', 'default', 'default' );
232
					$currencyId = ( isset( $config['currency'] ) ? $config['currency'] : null );
233
234
					$price = $priceManager->getLowestPrice( $prices, 1, $currencyId );
235
236
					if( isset( $config['stock'] ) && $config['stock'] == 1 ||
237
						isset( $config['price'] ) && $config['price'] == 1 &&
238
						isset( $config['pricevalue'] ) && $config['pricevalue'] > $price->getValue()
239
					) {
240
						$result[$id]['item'] = $products[$refId];
241
						$result[$id]['price'] = $price;
242
					}
243
				}
244
			}
245
			catch( \Exception $e ) { ; } // no price available
246
		}
247
248
		return $result;
249
	}
250
251
252
	/**
253
	 * Returns the products for the given IDs which are in stock in the warehouse
254
	 *
255
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context item object
256
	 * @param array $prodIds List of product IDs
257
	 * @param string $whId Unique warehouse ID
258
	 */
259
	protected function getProducts( \Aimeos\MShop\Context\Item\Iface $context, array $prodIds, $whId )
260
	{
261
		$productManager = \Aimeos\MShop\Factory::createManager( $context, 'product' );
262
		$search = $productManager->createSearch( true );
263
		$domains = array( 'text', 'price', 'media' );
264
265
		$stockExpr = array(
266
			$search->compare( '==', 'product.stock.stocklevel', null ),
267
			$search->compare( '>', 'product.stock.stocklevel', 0 ),
268
		);
269
270
		$expr = array(
271
			$search->compare( '==', 'product.id', $prodIds ),
272
			$search->getConditions(),
273
			$search->compare( '==', 'product.stock.warehouseid', $whId ),
274
			$search->combine( '||', $stockExpr ),
275
		);
276
		$search->setConditions( $search->combine( '&&', $expr ) );
277
		$search->setSlice( 0, 0x7fffffff );
278
279
		return $productManager->searchItems( $search, $domains );
280
	}
281
282
283
	/**
284
	 * Returns the customer list type item for the given type code.
285
	 *
286
	 * @param string $code Unique code of the list type item
287
	 * @return \Aimeos\MShop\Common\Item\Type\Iface List type item
288
	 * @throws \Aimeos\Controller\Jobs\Exception If the list type item wasn't found
289
	 */
290 View Code Duplication
	protected function getListTypeItem( $code )
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
291
	{
292
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'customer/lists/type' );
293
294
		$search = $manager->createSearch( true );
295
		$search->setConditions( $search->compare( '==', 'customer.lists.type.code', $code ) );
296
		$result = $manager->searchItems( $search );
297
298
		if( ( $item = reset( $result ) ) === false ) {
299
			throw new \Aimeos\Controller\Jobs\Exception( sprintf( 'List type for domain "%1$s" and code "%2$s" not found', 'customer', $code ) );
300
		}
301
302
		return $item;
303
	}
304
305
306
	/**
307
	 * Returns the warehouse item for the given code.
308
	 *
309
	 * @param string $code Unique code of the warehouse item
310
	 * @return \Aimeos\MShop\Product\Item\Stock\Warehouse\Iface Warehouse item
311
	 * @throws \Aimeos\Controller\Jobs\Exception If the warehouse item wasn't found
312
	 */
313
	protected function getWarehouseItem( $code )
314
	{
315
		if( !isset( $this->warehouses ) )
316
		{
317
			$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'product/stock/warehouse' );
318
			$search = $manager->createSearch( true );
319
320
			$this->warehouses = array();
321
			foreach( $manager->searchItems( $search ) as $whItem ) {
322
				$this->warehouses[ $whItem->getCode() ] = $whItem;
323
			}
324
		}
325
326
		if( !isset( $this->warehouses[$code] ) ) {
327
			throw new \Aimeos\Controller\Jobs\Exception( sprintf( 'No warehouse "%1$s" found', $code ) );
328
		}
329
330
		return $this->warehouses[$code];
331
	}
332
333
334
	/**
335
	 * Sends the notification e-mail for the given customer address and products
336
	 *
337
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context item object
338
	 * @param \Aimeos\MShop\Common\Item\Address\Iface $address Payment address of the customer
339
	 * @param array $products List of products a notification should be sent for
340
	 */
341
	protected function sendMail( \Aimeos\MShop\Context\Item\Iface $context,
342
		\Aimeos\MShop\Common\Item\Address\Iface $address, array $products )
343
	{
344
		$view = $context->getView();
345
		$view->extProducts = $products;
1 ignored issue
show
Bug introduced by
Accessing extProducts on the interface Aimeos\MW\View\Iface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
346
		$view->extAddressItem = $address;
1 ignored issue
show
Bug introduced by
Accessing extAddressItem on the interface Aimeos\MW\View\Iface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
347
348
		$helper = new \Aimeos\MW\View\Helper\Translate\Standard( $view, $context->getI18n( $address->getLanguageId() ) );
349
		$view->addHelper( 'translate', $helper );
350
351
		$mailer = $context->getMail();
352
		$message = $mailer->createMessage();
353
354
		$helper = new \Aimeos\MW\View\Helper\Mail\Standard( $view, $message );
355
		$view->addHelper( 'mail', $helper );
356
357
		$client = $this->getClient( $context );
358
		$client->setView( $view );
359
		$client->getHeader();
360
		$client->getBody();
361
362
		$mailer->send( $message );
363
	}
364
}
365