Passed
Push — master ( 81e62b...fa1949 )
by Aimeos
02:44
created

Standard::add()   B

Complexity

Conditions 8
Paths 60

Size

Total Lines 38
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 19
c 4
b 0
f 0
dl 0
loc 38
rs 8.4444
cc 8
nc 60
nop 1
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2017-2021
6
 * @package Controller
7
 * @subpackage Frontend
8
 */
9
10
11
namespace Aimeos\Controller\Frontend\Customer;
12
13
14
/**
15
 * Default implementation of the customer frontend controller
16
 *
17
 * @package Controller
18
 * @subpackage Frontend
19
 */
20
class Standard
21
	extends \Aimeos\Controller\Frontend\Base
22
	implements Iface, \Aimeos\Controller\Frontend\Common\Iface
23
{
24
	private $domains = [];
25
	private $manager;
26
	private $item;
27
28
29
	/**
30
	 * Initializes the controller
31
	 *
32
	 * @param \Aimeos\MShop\Context\Item\Iface $context Common MShop context object
33
	 */
34
	public function __construct( \Aimeos\MShop\Context\Item\Iface $context )
35
	{
36
		parent::__construct( $context );
37
38
		$this->manager = \Aimeos\MShop::create( $context, 'customer' );
39
40
		if( ( $userid = $context->getUserId() ) === null )
41
		{
42
			/** controller/frontend/customer/groupids
43
			 * List of groups new customers should be assigned to
44
			 *
45
			 * Newly created customers will be assigned automatically to the groups
46
			 * given by their IDs. This is especially useful if those groups limit
47
			 * functionality for those users.
48
			 *
49
			 * @param array List of group IDs
50
			 * @since 2017.07
51
			 * @category User
52
			 * @category Developer
53
			 */
54
			$groupIds = (array) $context->getConfig()->get( 'controller/frontend/customer/groupids', [] );
55
			$this->item = $this->manager->create()->setGroups( $groupIds );
56
		}
57
		else
58
		{
59
			$this->item = $this->manager->get( $userid, [], true );
60
		}
61
	}
62
63
64
	/**
65
	 * Clones objects in controller and resets values
66
	 */
67
	public function __clone()
68
	{
69
		$this->item = clone $this->item;
70
	}
71
72
73
	/**
74
	 * Creates a new customer item object pre-filled with the given values but not yet stored
75
	 *
76
	 * @param array $values Values added to the customer item (new or existing) like "customer.code"
77
	 * @return \Aimeos\Controller\Frontend\Customer\Iface Customer controller for fluent interface
78
	 * @since 2019.04
79
	 */
80
	public function add( array $values ) : Iface
81
	{
82
		foreach( $values as $key => $value )
83
		{
84
			if( is_scalar( $value ) ) {
85
				$values[$key] = strip_tags( (string) $value ); // prevent XSS
86
			}
87
		}
88
89
		$password = $values['customer.password'] ?? '';
90
		$item = $this->item->fromArray( $values );
91
		$addrItem = $item->getPaymentAddress();
92
93
		if( $item->getLabel() === '' )
94
		{
95
			$label = $addrItem->getLastname();
96
97
			if( ( $firstName = $addrItem->getFirstname() ) !== '' ) {
98
				$label = $firstName . ' ' . $label;
99
			}
100
101
			if( ( $company = $addrItem->getCompany() ) !== '' ) {
102
				$label .= ' (' . $company . ')';
103
			}
104
105
			$item = $item->setLabel( $label );
106
		}
107
108
		if( $item->getCode() === '' ) {
109
			$item = $item->setCode( $addrItem->getEmail() );
110
		}
111
112
		if( $password ) {
113
			$item = $item->setPassword( $password );
114
		}
115
116
		$this->item = $item;
117
		return $this;
118
	}
119
120
121
	/**
122
	 * Adds the given address item to the customer object (not yet stored)
123
	 *
124
	 * @param \Aimeos\MShop\Common\Item\Address\Iface $item Address item to add
125
	 * @param int|null $idx Key in the list of address items or null to add the item at the end
126
	 * @return \Aimeos\Controller\Frontend\Customer\Iface Customer controller for fluent interface
127
	 * @since 2019.04
128
	 */
129
	public function addAddressItem( \Aimeos\MShop\Common\Item\Address\Iface $item, int $idx = null ) : Iface
130
	{
131
		$this->item = $this->item->addAddressItem( $item, $idx );
132
		return $this;
133
	}
134
135
136
	/**
137
	 * Adds the given list item to the customer object (not yet stored)
138
	 *
139
	 * @param string $domain Domain name the referenced item belongs to
140
	 * @param \Aimeos\MShop\Common\Item\Lists\Iface $item List item to add
141
	 * @param \Aimeos\MShop\Common\Item\Iface|null $refItem Referenced item to add or null if list item contains refid value
142
	 * @return \Aimeos\Controller\Frontend\Customer\Iface Customer controller for fluent interface
143
	 * @since 2019.04
144
	 */
145
	public function addListItem( string $domain, \Aimeos\MShop\Common\Item\Lists\Iface $item,
146
		\Aimeos\MShop\Common\Item\Iface $refItem = null ) : Iface
147
	{
148
		if( $domain === 'customer/group' ) {
149
			throw new Exception( sprintf( 'You are not allowed to manage groups' ) );
150
		}
151
152
		$this->item = $this->item->addListItem( $domain, $item, $refItem );
153
		return $this;
154
	}
155
156
157
	/**
158
	 * Adds the given property item to the customer object (not yet stored)
159
	 *
160
	 * @param \Aimeos\MShop\Common\Item\Property\Iface $item Property item to add
161
	 * @return \Aimeos\Controller\Frontend\Customer\Iface Customer controller for fluent interface
162
	 * @since 2019.04
163
	 */
164
	public function addPropertyItem( \Aimeos\MShop\Common\Item\Property\Iface $item ) : Iface
165
	{
166
		$this->item = $this->item->addPropertyItem( $item );
167
		return $this;
168
	}
169
170
171
	/**
172
	 * Creates a new address item object pre-filled with the given values
173
	 *
174
	 * @param array $values Associative list of key/value pairs for populating the item
175
	 * @return \Aimeos\MShop\Customer\Item\Address\Iface Address item
176
	 * @since 2019.04
177
	 */
178
	public function createAddressItem( array $values = [] ) : \Aimeos\MShop\Customer\Item\Address\Iface
179
	{
180
		return $this->manager->createAddressItem()->fromArray( $values );
0 ignored issues
show
Bug introduced by
The method createAddressItem() does not exist on Aimeos\MShop\Common\Manager\Iface. Did you maybe mean create()? ( Ignorable by Annotation )

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

180
		return $this->manager->/** @scrutinizer ignore-call */ createAddressItem()->fromArray( $values );

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...
181
	}
182
183
184
	/**
185
	 * Creates a new list item object pre-filled with the given values
186
	 *
187
	 * @param array $values Associative list of key/value pairs for populating the item
188
	 * @return \Aimeos\MShop\Common\Item\Lists\Iface List item
189
	 * @since 2019.04
190
	 */
191
	public function createListItem( array $values = [] ) : \Aimeos\MShop\Common\Item\Lists\Iface
192
	{
193
		return $this->manager->createListItem()->fromArray( $values );
0 ignored issues
show
Bug introduced by
The method createListItem() does not exist on Aimeos\MShop\Common\Manager\Iface. Did you maybe mean create()? ( Ignorable by Annotation )

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

193
		return $this->manager->/** @scrutinizer ignore-call */ createListItem()->fromArray( $values );

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...
194
	}
195
196
197
	/**
198
	 * Creates a new property item object pre-filled with the given values
199
	 *
200
	 * @param array $values Associative list of key/value pairs for populating the item
201
	 * @return \Aimeos\MShop\Common\Item\Property\Iface Property item
202
	 * @since 2019.04
203
	 */
204
	public function createPropertyItem( array $values = [] ) : \Aimeos\MShop\Common\Item\Property\Iface
205
	{
206
		return $this->manager->createPropertyItem()->fromArray( $values );
0 ignored issues
show
Bug introduced by
The method createPropertyItem() does not exist on Aimeos\MShop\Common\Manager\Iface. Did you maybe mean create()? ( Ignorable by Annotation )

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

206
		return $this->manager->/** @scrutinizer ignore-call */ createPropertyItem()->fromArray( $values );

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...
207
	}
208
209
210
	/**
211
	 * Deletes a customer item that belongs to the current authenticated user
212
	 *
213
	 * @return \Aimeos\Controller\Frontend\Customer\Iface Customer controller for fluent interface
214
	 * @since 2019.04
215
	 */
216
	public function delete() : Iface
217
	{
218
		if( $this->item && $this->item->getId() ) {
219
			\Aimeos\MShop::create( $this->getContext(), 'customer' )->delete( $this->item->getId() );
220
		}
221
222
		return $this;
223
	}
224
225
226
	/**
227
	 * Removes the given address item from the customer object (not yet stored)
228
	 *
229
	 * @param \Aimeos\MShop\Common\Item\Address\Iface $item Address item to remove
230
	 * @return \Aimeos\Controller\Frontend\Customer\Iface Customer controller for fluent interface
231
	 */
232
	public function deleteAddressItem( \Aimeos\MShop\Common\Item\Address\Iface $item ) : Iface
233
	{
234
		$this->item = $this->item->deleteAddressItem( $item );
235
		return $this;
236
	}
237
238
239
	/**
240
	 * Removes the given list item from the customer object (not yet stored)
241
	 *
242
	 * @param string $domain Domain name the referenced item belongs to
243
	 * @param \Aimeos\MShop\Common\Item\Lists\Iface $item List item to remove
244
	 * @param \Aimeos\MShop\Common\Item\Iface|null $refItem Referenced item to remove or null if only list item should be removed
245
	 * @return \Aimeos\Controller\Frontend\Customer\Iface Customer controller for fluent interface
246
	 */
247
	public function deleteListItem( string $domain, \Aimeos\MShop\Common\Item\Lists\Iface $listItem,
248
		\Aimeos\MShop\Common\Item\Iface $refItem = null ) : Iface
249
	{
250
		if( $domain === 'customer/group' ) {
251
			throw new Exception( sprintf( 'You are not allowed to manage groups' ) );
252
		}
253
254
		$this->item = $this->item->deleteListItem( $domain, $listItem, $refItem );
255
		return $this;
256
	}
257
258
259
	/**
260
	 * Removes the given property item from the customer object (not yet stored)
261
	 *
262
	 * @param \Aimeos\MShop\Common\Item\Property\Iface $item Property item to remove
263
	 * @return \Aimeos\Controller\Frontend\Customer\Iface Customer controller for fluent interface
264
	 */
265
	public function deletePropertyItem( \Aimeos\MShop\Common\Item\Property\Iface $item ) : Iface
266
	{
267
		$this->item = $this->item->deletePropertyItem( $item );
268
		return $this;
269
	}
270
271
272
	/**
273
	 * Returns the customer item for the given customer code (usually e-mail address)
274
	 *
275
	 * This method doesn't check if the customer item belongs to the logged in user!
276
	 *
277
	 * @param string $code Unique customer code
278
	 * @return \Aimeos\MShop\Customer\Item\Iface Customer item including the referenced domains items
279
	 * @since 2019.04
280
	 */
281
	public function find( string $code ) : \Aimeos\MShop\Customer\Item\Iface
282
	{
283
		return $this->manager->find( $code, $this->domains, 'customer', null, true );
284
	}
285
286
287
	/**
288
	 * Returns the customer item for the current authenticated user
289
	 *
290
	 * @return \Aimeos\MShop\Customer\Item\Iface Customer item including the referenced domains items
291
	 * @since 2019.04
292
	 */
293
	public function get() : \Aimeos\MShop\Customer\Item\Iface
294
	{
295
		return $this->item;
296
	}
297
298
299
	/**
300
	 * Adds or updates a modified customer item in the storage
301
	 *
302
	 * @return \Aimeos\Controller\Frontend\Customer\Iface Customer controller for fluent interface
303
	 * @since 2019.04
304
	 */
305
	public function store() : Iface
306
	{
307
		( $id = $this->item->getId() ) !== null ? $this->checkId( $id ) : $this->checkLimit();
308
		$context = $this->getContext();
309
310
		if( $id === null )
311
		{
312
			$msg = $this->item->toArray();
313
314
			// Show only generated passwords in account creation e-mails
315
			if( $this->item->getPassword() === '' )
316
			{
317
				$msg['customer.password'] = substr( sha1( microtime( true ) . getmypid() . rand() ), -8 );
318
				$this->item->setPassword( $msg['customer.password'] );
319
			}
320
321
			$context->getMessageQueue( 'mq-email', 'customer/email/account' )->add( json_encode( $msg ) );
322
		}
323
324
		$this->item = $this->manager->save( $this->item );
325
		return $this;
326
	}
327
328
329
	/**
330
	 * Sets the domains that will be used when working with the customer item
331
	 *
332
	 * @param array $domains Domain names of the referenced items that should be fetched too
333
	 * @return \Aimeos\Controller\Frontend\Customer\Iface Customer controller for fluent interface
334
	 * @since 2019.04
335
	 */
336
	public function uses( array $domains ) : Iface
337
	{
338
		$this->domains = $domains;
339
340
		if( ( $id = $this->getContext()->getUserId() ) !== null ) {
341
			$this->item = $this->manager->get( $id, $domains, true );
342
		}
343
344
		return $this;
345
	}
346
347
348
	/**
349
	 * Checks if the current user is allowed to create more customer accounts
350
	 *
351
	 * @throws \Aimeos\Controller\Frontend\Customer\Exception If access isn't allowed
352
	 */
353
	protected function checkLimit()
354
	{
355
		$total = 0;
356
		$context = $this->getContext();
357
		$config = $context->getConfig();
358
359
		/** controller/frontend/customer/limit-count
360
		 * Maximum number of customers within the time frame
361
		 *
362
		 * Creating new customers is limited to avoid abuse and mitigate denial of
363
		 * service attacks. The number of customer accountss created within the
364
		 * time frame configured by "controller/frontend/customer/limit-seconds"
365
		 * are counted before a new customer account (identified by the IP address)
366
		 * is created. If the number of accounts is higher than the configured value,
367
		 * an error message will be shown to the user instead of creating a new account.
368
		 *
369
		 * @param integer Number of customer accounts allowed within the time frame
370
		 * @since 2017.07
371
		 * @category Developer
372
		 * @see controller/frontend/customer/limit-seconds
373
		 */
374
		$count = $config->get( 'controller/frontend/customer/limit-count', 3 );
375
376
		/** controller/frontend/customer/limit-seconds
377
		 * Customer account limitation time frame in seconds
378
		 *
379
		 * Creating new customer accounts is limited to avoid abuse and mitigate
380
		 * denial of service attacks. Within the configured time frame, only a
381
		 * limited number of customer accounts can be created. All accounts from
382
		 * the same source (identified by the IP address) within the last X
383
		 * seconds are counted. If the total value is higher then the number
384
		 * configured in "controller/frontend/customer/limit-count", an error
385
		 * message will be shown to the user instead of creating a new account.
386
		 *
387
		 * @param integer Number of seconds to check customer accounts within
388
		 * @since 2017.07
389
		 * @category Developer
390
		 * @see controller/frontend/customer/limit-count
391
		 */
392
		$seconds = $config->get( 'controller/frontend/customer/limit-seconds', 14400 );
393
394
		$search = $this->manager->filter()->slice( 0, 0 );
395
		$expr = [
396
			$search->compare( '==', 'customer.editor', $context->getEditor() ),
397
			$search->compare( '>=', 'customer.ctime', date( 'Y-m-d H:i:s', time() - $seconds ) ),
398
		];
399
		$search->setConditions( $search->and( $expr ) );
400
401
		$this->manager->search( $search, [], $total );
402
403
		if( $total >= $count ) {
404
			throw new \Aimeos\Controller\Frontend\Customer\Exception( sprintf( 'Temporary limit reached' ) );
405
		}
406
	}
407
408
409
	/**
410
	 * Checks if the current user is allowed to retrieve the customer data for the given ID
411
	 *
412
	 * @param string $id Unique customer ID
413
	 * @return string Unique customer ID
414
	 * @throws \Aimeos\Controller\Frontend\Customer\Exception If access isn't allowed
415
	 */
416
	protected function checkId( string $id ) : string
417
	{
418
		if( $id != $this->getContext()->getUserId() )
419
		{
420
			$msg = sprintf( 'Not allowed to access customer data for ID "%1$s"', $id );
421
			throw new \Aimeos\Controller\Frontend\Customer\Exception( $msg );
422
		}
423
424
		return $id;
425
	}
426
}
427