Passed
Push — master ( 801d58...b615cd )
by Aimeos
05:02
created

Base::deleteItemsBase()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 30
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 16
dl 0
loc 30
rs 9.7333
c 0
b 0
f 0
cc 3
nc 3
nop 4
1
<?php
2
3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2011
6
 * @copyright Aimeos (aimeos.org), 2015-2023
7
 * @package MShop
8
 * @subpackage Customer
9
 */
10
11
12
namespace Aimeos\MShop\Customer\Manager;
13
14
15
/**
16
 * Base class with common methods for all customer implementations.
17
 *
18
 * @package MShop
19
 * @subpackage Customer
20
 */
21
abstract class Base
22
	extends \Aimeos\MShop\Common\Manager\Base
23
{
24
	use \Aimeos\MShop\Common\Manager\ListsRef\Traits;
25
	use \Aimeos\MShop\Common\Manager\AddressRef\Traits;
26
	use \Aimeos\MShop\Common\Manager\PropertyRef\Traits;
27
28
29
	private ?\Aimeos\MShop\Customer\Item\Iface $user = null;
30
	private ?\Aimeos\MShop\Common\Helper\Password\Iface $helper = null;
31
	private ?string $salt;
32
33
34
	/**
35
	 * Initializes a new customer manager object using the given context object.
36
	 *
37
	 * @param \Aimeos\MShop\ContextIface $context Context object with required objects
38
	 */
39
	public function __construct( \Aimeos\MShop\ContextIface $context )
40
	{
41
		parent::__construct( $context );
42
43
		/** mshop/customer/manager/resource
44
		 * Name of the database connection resource to use
45
		 *
46
		 * You can configure a different database connection for each data domain
47
		 * and if no such connection name exists, the "db" connection will be used.
48
		 * It's also possible to use the same database connection for different
49
		 * data domains by configuring the same connection name using this setting.
50
		 *
51
		 * @param string Database connection name
52
		 * @since 2023.04
53
		 */
54
		$this->setResourceName( $context->config()->get( 'mshop/customer/manager/resource', 'db-customer' ) );
55
56
		/** mshop/customer/manager/salt
57
		 * Password salt for all customer passwords of the installation
58
		 *
59
		 * The default password salt is used if no user-specific salt can be
60
		 * stored in the database along with the user data. It's highly recommended
61
		 * to set the salt to a random string of at least eight chars using
62
		 * characters, digits and special characters
63
		 *
64
		 * @param string Installation wide password salt
65
		 * @since 2014.03
66
		 * @category Developer
67
		 * @category User
68
		 * @see mshop/customer/manager/password/name
69
		 * @sse mshop/customer/manager/password/options
70
		 */
71
		$this->salt = $context->config()->get( 'mshop/customer/manager/salt', 'mshop' );
72
	}
73
74
75
	/**
76
	 * Counts the number items that are available for the values of the given key.
77
	 *
78
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria
79
	 * @param array|string $key Search key or list of key to aggregate items for
80
	 * @param string|null $value Search key for aggregating the value column
81
	 * @param string|null $type Type of the aggregation, empty string for count or "sum" or "avg" (average)
82
	 * @return \Aimeos\Map List of the search keys as key and the number of counted items as value
83
	 */
84
	public function aggregate( \Aimeos\Base\Criteria\Iface $search, $key, string $value = null, string $type = null ) : \Aimeos\Map
85
	{
86
		/** mshop/customer/manager/aggregate/mysql
87
		 * Counts the number of records grouped by the values in the key column and matched by the given criteria
88
		 *
89
		 * @see mshop/customer/manager/aggregate/ansi
90
		 */
91
92
		/** mshop/customer/manager/aggregate/ansi
93
		 * Counts the number of records grouped by the values in the key column and matched by the given criteria
94
		 *
95
		 * Groups all records by the values in the key column and counts their
96
		 * occurence. The matched records can be limited by the given criteria
97
		 * from the customer database. The records must be from one of the sites
98
		 * that are configured via the context item. If the current site is part
99
		 * of a tree of sites, the statement can count all records from the
100
		 * current site and the complete sub-tree of sites.
101
		 *
102
		 * As the records can normally be limited by criteria from sub-managers,
103
		 * their tables must be joined in the SQL context. This is done by
104
		 * using the "internaldeps" property from the definition of the ID
105
		 * column of the sub-managers. These internal dependencies specify
106
		 * the JOIN between the tables and the used columns for joining. The
107
		 * ":joins" placeholder is then replaced by the JOIN strings from
108
		 * the sub-managers.
109
		 *
110
		 * To limit the records matched, conditions can be added to the given
111
		 * criteria object. It can contain comparisons like column names that
112
		 * must match specific values which can be combined by AND, OR or NOT
113
		 * operators. The resulting string of SQL conditions replaces the
114
		 * ":cond" placeholder before the statement is sent to the database
115
		 * server.
116
		 *
117
		 * This statement doesn't return any records. Instead, it returns pairs
118
		 * of the different values found in the key column together with the
119
		 * number of records that have been found for that key values.
120
		 *
121
		 * The SQL statement should conform to the ANSI standard to be
122
		 * compatible with most relational database systems. This also
123
		 * includes using double quotes for table and column names.
124
		 *
125
		 * @param string SQL statement for aggregating customer items
126
		 * @since 2021.04
127
		 * @category Developer
128
		 * @see mshop/customer/manager/insert/ansi
129
		 * @see mshop/customer/manager/update/ansi
130
		 * @see mshop/customer/manager/newid/ansi
131
		 * @see mshop/customer/manager/delete/ansi
132
		 * @see mshop/customer/manager/search/ansi
133
		 * @see mshop/customer/manager/count/ansi
134
		 */
135
136
		$cfgkey = 'mshop/customer/manager/aggregate';
137
		return $this->aggregateBase( $search, $key, $cfgkey, ['customer'], $value, $type );
138
	}
139
140
141
	/**
142
	 * Creates a filter object.
143
	 *
144
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
145
	 * @param bool $site TRUE for adding site criteria to limit items by the site of related items
146
	 * @return \Aimeos\Base\Criteria\Iface Returns the filter object
147
	 */
148
	public function filter( ?bool $default = false, bool $site = false ) : \Aimeos\Base\Criteria\Iface
149
	{
150
		return $this->filterBase( 'customer', $default );
151
	}
152
153
154
	/**
155
	 * Returns the item specified by its code and domain/type if necessary
156
	 *
157
	 * @param string $code Code of the item
158
	 * @param string[] $ref List of domains to fetch list items and referenced items for
159
	 * @param string|null $domain Domain of the item if necessary to identify the item uniquely
160
	 * @param string|null $type Type code of the item if necessary to identify the item uniquely
161
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
162
	 * @return \Aimeos\MShop\Customer\Item\Iface Item object
163
	 */
164
	public function find( string $code, array $ref = [], string $domain = null, string $type = null,
165
		?bool $default = false ) : \Aimeos\MShop\Common\Item\Iface
166
	{
167
		return $this->findBase( array( 'customer.code' => $code ), $ref, $default );
168
	}
169
170
171
	/**
172
	 * Returns the customer item object specificed by its ID.
173
	 *
174
	 * @param string $id Unique customer ID referencing an existing customer
175
	 * @param string[] $ref List of domains to fetch list items and referenced items for
176
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
177
	 * @return \Aimeos\MShop\Customer\Item\Iface Returns the customer item of the given id
178
	 * @throws \Aimeos\MShop\Exception If item couldn't be found
179
	 */
180
	public function get( string $id, array $ref = [], ?bool $default = false ) : \Aimeos\MShop\Common\Item\Iface
181
	{
182
		return $this->getItemBase( 'customer.id', $id, $ref, $default );
183
	}
184
185
186
	/**
187
	 * Adds the customer to the groups listed in the customer item
188
	 *
189
	 * @param \Aimeos\MShop\Customer\Item\Iface $item Customer item
190
	 * @return \Aimeos\MShop\Customer\Item\Iface $item Modified customer item
191
	 */
192
	protected function addGroups( \Aimeos\MShop\Customer\Item\Iface $item ): \Aimeos\MShop\Customer\Item\Iface
193
	{
194
		$pos = 0;
195
		$groupIds = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $groupIds is dead and can be removed.
Loading history...
196
197
		$manager = $this->object()->getSubManager( 'lists' );
198
		$listItems = $item->getListItems( 'group', 'default', null, false );
199
200
		foreach( $item->getGroups() as $refId )
201
		{
202
			if( ( $litem = $item->getListItem( 'group', 'default', $refId, false ) ) !== null ) {
203
				unset( $listItems[$litem->getId()], $listItems['__group_default_' . $refId] );
204
			} else {
205
				$litem = $manager->create()->setType( 'default' );
206
			}
207
208
			$item->addListItem( 'group', $litem->setRefId( $refId )->setPosition( $pos++ ) );
209
		}
210
211
		return $item->deleteListItems( $listItems->toArray() );
212
	}
213
214
215
	/**
216
	 * Creates a new customer item.
217
	 *
218
	 * @param array $values List of attributes for customer item
219
	 * @param \Aimeos\MShop\Common\Item\Lists\Iface[] $listItems List of list items
220
	 * @param \Aimeos\MShop\Common\Item\Iface[] $refItems List of referenced items
221
	 * @param \Aimeos\MShop\Common\Item\Address\Iface[] $addrItems List of address items
222
	 * @param \Aimeos\MShop\Common\Item\Property\Iface[] $propItems List of property items
223
	 * @return \Aimeos\MShop\Customer\Item\Iface New customer item
224
	 */
225
	protected function createItemBase( array $values = [], array $listItems = [], array $refItems = [],
226
		array $addrItems = [], array $propItems = [] ) : \Aimeos\MShop\Common\Item\Iface
227
	{
228
		$helper = $this->getPasswordHelper();
229
		$address = new \Aimeos\MShop\Common\Item\Address\Standard( 'customer.', $values );
230
231
		return new \Aimeos\MShop\Customer\Item\Standard(
232
			$address, $values, $listItems, $refItems, $addrItems, $propItems, $helper, $this->salt
233
		);
234
	}
235
236
237
	/**
238
	 * Deletes items.
239
	 *
240
	 * @param \Aimeos\MShop\Common\Item\Iface|\Aimeos\Map|array|string $items List of item objects or IDs of the items
241
	 * @param string $cfgpath Configuration path to the SQL statement
242
	 * @param bool $siteid If siteid should be used in the statement
243
	 * @param string $name Name of the ID column
244
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
245
	 */
246
	protected function deleteItemsBase( $items, string $cfgpath, bool $siteid = true,
247
		string $name = 'id' ) : \Aimeos\MShop\Common\Manager\Iface
248
	{
249
		if( map( $items )->isEmpty() ) {
250
			return $this;
251
		}
252
253
		$search = $this->object()->filter();
254
		$search->setConditions( $search->compare( '==', $name, $items ) );
255
256
		$types = array( $name => \Aimeos\Base\DB\Statement\Base::PARAM_STR );
257
		$translations = array( $name => '"' . $name . '"' );
258
259
		$cond = $search->getConditionSource( $types, $translations );
260
		$sql = str_replace( ':cond', $cond, $this->getSqlConfig( $cfgpath ) );
261
262
		$context = $this->context();
263
		$conn = $context->db( $this->getResourceName() );
264
265
		$stmt = $conn->create( $sql );
266
267
		if( $siteid )
268
		{
269
			$stmt->bind( 1, $context->locale()->getSiteId() . '%' );
270
			$stmt->bind( 2, $this->getUser()?->getSiteId() );
271
		}
272
273
		$stmt->execute()->finish();
274
275
		return $this;
276
	}
277
278
279
	/**
280
	 * Returns a password helper object based on the configuration.
281
	 *
282
	 * @return \Aimeos\MShop\Common\Helper\Password\Iface Password helper object
283
	 * @throws \LogicException If the name is invalid or the class isn't found
284
	 */
285
	protected function getPasswordHelper() : \Aimeos\MShop\Common\Helper\Password\Iface
286
	{
287
		if( $this->helper ) {
288
			return $this->helper;
289
		}
290
291
		$config = $this->context()->config();
292
293
		/** mshop/customer/manager/password/name
294
		 * Last part of the name for building the password helper item
295
		 *
296
		 * The password helper encode given passwords and salts using the
297
		 * implemented hashing method in the required format. String format and
298
		 * hash algorithm needs to be the same when comparing the encoded
299
		 * password to the one provided by the user after login.
300
		 *
301
		 * @param string Name of the password helper implementation
302
		 * @since 2015.01
303
		 * @category Developer
304
		 * @see mshop/customer/manager/salt
305
		 * @see mshop/customer/manager/password/options
306
		 */
307
		$name = $config->get( 'mshop/customer/manager/password/name', 'Standard' );
308
309
		/** mshop/customer/manager/password/options
310
		 * List of options used by the password helper classes
311
		 *
312
		 * Each hash method may need an arbitrary number of options specific
313
		 * for the hash method. This may include the number of iterations the
314
		 * method is applied or the separator between salt and password.
315
		 *
316
		 * @param string Associative list of key/value pairs
317
		 * @since 2015.01
318
		 * @category Developer
319
		 * @see mshop/customer/manager/password/name
320
		 * @sse mshop/customer/manager/salt
321
		 */
322
		$options = $config->get( 'mshop/customer/manager/password/options', [] );
323
324
		if( ctype_alnum( $name ) === false ) {
325
			throw new \LogicException( sprintf( 'Invalid characters in class name "%1$s"', $name ), 400 );
326
		}
327
328
		$classname = '\Aimeos\MShop\Common\Helper\Password\\' . $name;
329
		$interface = \Aimeos\MShop\Common\Helper\Password\Iface::class;
330
331
		return $this->helper = \Aimeos\Utils::create( $classname, [$options], $interface );
332
	}
333
334
335
	/**
336
	 * Returns the currently authenticated user
337
	 *
338
	 * @return \Aimeos\MShop\Customer\Item\Iface|null Customer item or NULL if not available
339
	 */
340
	protected function getUser() : ?\Aimeos\MShop\Customer\Item\Iface
341
	{
342
		if( !isset( $this->user ) && ( $userid = $this->context()->user() ) !== null ) {
343
			$this->user = $this->search( $this->filter( true )->add( 'customer.id', '==', $userid ) )->first();
344
		}
345
346
		return $this->user;
347
	}
348
}
349