Passed
Push — master ( 1cfa87...002d11 )
by Aimeos
03:19
created

Standard::sites()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 7
nc 4
nop 1
dl 0
loc 15
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2015-2022
6
 * @package Controller
7
 * @subpackage Customer
8
 */
9
10
11
namespace Aimeos\Controller\Jobs\Customer\Email\Watch;
12
13
14
/**
15
 * Product notification e-mail job controller.
16
 *
17
 * @package Controller
18
 * @subpackage Customer
19
 */
20
class Standard
21
	extends \Aimeos\Controller\Jobs\Base
22
	implements \Aimeos\Controller\Jobs\Iface
23
{
24
	use \Aimeos\Controller\Jobs\Mail;
25
26
27
	private $sites = [];
28
29
30
	/**
31
	 * Returns the localized name of the job.
32
	 *
33
	 * @return string Name of the job
34
	 */
35
	public function getName() : string
36
	{
37
		return $this->context()->translate( 'controller/jobs', 'Product notification e-mails' );
38
	}
39
40
41
	/**
42
	 * Returns the localized description of the job.
43
	 *
44
	 * @return string Description of the job
45
	 */
46
	public function getDescription() : string
47
	{
48
		return $this->context()->translate( 'controller/jobs', 'Sends e-mails for watched products' );
49
	}
50
51
52
	/**
53
	 * Executes the job.
54
	 *
55
	 * @throws \Aimeos\Controller\Jobs\Exception If an error occurs
56
	 */
57
	public function run()
58
	{
59
		$manager = \Aimeos\MShop::create( $this->context(), 'customer' );
60
61
		$search = $manager->filter( true );
62
		$func = $search->make( 'customer:has', ['product', 'watch'] );
63
		$search->add( $search->is( $func, '!=', null ) )->order( 'customer.id' );
64
65
		$start = 0;
66
67
		do
68
		{
69
			$customers = $manager->search( $search->slice( $start ), ['product' => ['watch']] );
70
			$customers = $this->notify( $customers );
71
			$customers = $manager->save( $customers );
72
73
			$count = count( $customers );
0 ignored issues
show
Bug introduced by
It seems like $customers can also be of type Aimeos\MShop\Common\Item\Iface; however, parameter $value of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

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

73
			$count = count( /** @scrutinizer ignore-type */ $customers );
Loading history...
74
			$start += $count;
75
		}
76
		while( $count >= $search->getLimit() );
77
	}
78
79
80
	/**
81
	 * Sends product notifications for the given customers in their language
82
	 *
83
	 * @param \Aimeos\Map $customers List of customer items implementing \Aimeos\MShop\Customer\Item\Iface
84
	 * @return \Aimeos\Map List of customer items implementing \Aimeos\MShop\Customer\Item\Iface
85
	 */
86
	protected function notify( \Aimeos\Map $customers ) : \Aimeos\Map
87
	{
88
		$date = date( 'Y-m-d H:i:s' );
89
		$context = $this->context();
90
91
92
		foreach( $customers as $customer )
93
		{
94
			$listItems = $customer->getListItems( 'product', null, null, false );
95
			$products = $this->products( $listItems );
96
97
			try
98
			{
99
				if( !empty( $products ) )
100
				{
101
					$sites = $this->sites( $customer->getSiteId() );
102
103
					$view = $this->view( $customer->getPaymentAddress(), $sites->getTheme()->filter()->last() );
104
					$view->products = $products;
105
106
					$this->send( $view, $customer->getPaymentAddress(), $sites->getLogo()->filter()->last() );
107
				}
108
109
				$str = sprintf( 'Sent product notification e-mail to "%1$s"', $customer->getPaymentAddress()->getEmail() );
110
				$context->logger()->debug( $str, 'email/customer/watch' );
111
			}
112
			catch( \Exception $e )
113
			{
114
				$str = 'Error while trying to send product notification e-mail for customer ID "%1$s": %2$s';
115
				$msg = sprintf( $str, $customer->getId(), $e->getMessage() ) . PHP_EOL . $e->getTraceAsString();
116
				$context->logger()->error( $msg, 'email/customer/watch' );
117
			}
118
119
			$remove = $listItems->diffKeys( $products )->filter( function( $listItem ) use ( $date ) {
120
				return $listItem->getDateEnd() < $date;
121
			} );
122
123
			$customer->deleteListItems( $remove );
124
		}
125
126
		return $customers;
127
	}
128
129
130
	/**
131
	 * Returns a filtered list of products for which a notification should be sent
132
	 *
133
	 * @param \Aimeos\Map $listItems List of customer list items
134
	 * @return array Associative list of list IDs as key and product items values
135
	 */
136
	protected function products( \Aimeos\Map $listItems ) : array
137
	{
138
		$result = [];
139
		$priceManager = \Aimeos\MShop::create( $this->context(), 'price' );
140
141
		foreach( $listItems as $id => $listItem )
142
		{
143
			try
144
			{
145
				if( $product = $listItem->getRefItem() )
146
				{
147
					$config = $listItem->getConfig();
148
					$prices = $product->getRefItems( 'price', 'default', 'default' );
149
					$price = $priceManager->getLowestPrice( $prices, 1, $config['currency'] ?? null );
150
151
					if( $config['stock'] ?? null || $config['price'] ?? null
152
						&& $product->inStock() && ( $config['pricevalue'] ?? 0 ) > $price->getValue()
153
					) {
154
						$result[$id] = $product->set( 'price', $price );
155
					}
156
				}
157
			}
158
			catch( \Exception $e ) { ; } // no price available
159
		}
160
161
		return $result;
162
	}
163
164
165
	/**
166
	 * Sends the notification e-mail for the given customer address and products
167
	 *
168
	 * @param \Aimeos\MW\View\Iface $view View object
169
	 * @param \Aimeos\MShop\Common\Item\Address\Iface $address Address item
170
	 * @param string|null $logoPath Path to the logo
171
	 */
172
	protected function send( \Aimeos\MW\View\Iface $view, \Aimeos\MShop\Common\Item\Address\Iface $address, string $logoPath = null )
173
	{
174
		$context = $this->context();
175
		$config = $context->config();
176
177
		$msg = $this->call( 'mailTo', $address );
178
		$view->logo = $msg->embed( $this->call( 'mailLogo', $logoPath ), basename( (string) $logoPath ) );
179
180
		$msg->subject( $context->translate( 'client', 'Your watched products' ) )
181
			->html( $view->render( $config->get( 'controller/jobs/customer/email/watch/template-html', 'customer/email/watch/html' ) ) )
182
			->text( $view->render( $config->get( 'controller/jobs/customer/email/watch/template-text', 'customer/email/watch/text' ) ) )
183
			->send();
184
	}
185
186
187
	/**
188
	 * Returns the list of site items from the given site ID up to the root site
189
	 *
190
	 * @param string|null $siteId Site ID like "1.2.4."
191
	 * @return \Aimeos\Map List of site items
192
	 */
193
	protected function sites( string $siteId = null ) : \Aimeos\Map
194
	{
195
		if( !$siteId && !isset( $this->sites[''] ) ) {
196
			$this->sites[''] = map( \Aimeos\MShop::create( $this->context(), 'locale/site' )->find( 'default' ) );
197
		}
198
199
		if( !isset( $this->sites[(string) $siteId] ) )
200
		{
201
			$manager = \Aimeos\MShop::create( $this->context(), 'locale/site' );
202
			$siteIds = explode( '.', trim( $siteId, '.' ) );
0 ignored issues
show
Bug introduced by
It seems like $siteId can also be of type null; however, parameter $string of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

202
			$siteIds = explode( '.', trim( /** @scrutinizer ignore-type */ $siteId, '.' ) );
Loading history...
203
204
			$this->sites[$siteId] = $manager->getPath( end( $siteIds ) );
205
		}
206
207
		return $this->sites[$siteId];
208
	}
209
210
211
	/**
212
	 * Returns the view populated with common data
213
	 *
214
	 * @param \Aimeos\MShop\Common\Item\Address\Iface $address Address item
215
	 * @param string|null $theme Theme name
216
	 * @return \Aimeos\MW\View\Iface View object
217
	 */
218
	protected function view( \Aimeos\MShop\Common\Item\Address\Iface $address, string $theme = null ) : \Aimeos\MW\View\Iface
219
	{
220
		$view = $this->call( 'mailView', $address->getLanguageId() );
221
		$view->intro = $this->call( 'mailIntro', $address );
222
		$view->css = $this->call( 'mailCss', $theme );
223
		$view->urlparams = [
224
			'site' => $this->context()->locale()->getSiteItem()->getCode(),
225
			'locale' => $address->getLanguageId(),
226
		];
227
228
		return $view;
229
	}
230
}
231