Passed
Push — master ( 823da0...f95364 )
by Aimeos
05:35
created

Standard::saveText()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 10
nc 2
nop 8
dl 0
loc 14
rs 9.9332
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
4
 * @copyright Metaways Infosystems GmbH, 2012
5
 * @copyright Aimeos (aimeos.org), 2015-2021
6
 * @package MShop
7
 * @subpackage Index
8
 */
9
10
11
namespace Aimeos\MShop\Index\Manager\Text;
12
13
14
/**
15
 * Submanager for text.
16
 *
17
 * @package MShop
18
 * @subpackage Index
19
 */
20
class Standard
21
	extends \Aimeos\MShop\Index\Manager\DBBase
22
	implements \Aimeos\MShop\Index\Manager\Text\Iface, \Aimeos\MShop\Common\Manager\Factory\Iface
23
{
24
	private $searchConfig = array(
25
		// @deprecated Removed 2019.01
26
		'index.text.id' => array(
27
			'code' => 'index.text.id',
28
			'internalcode' => 'mindte."prodid"',
29
			'internaldeps' => array( 'LEFT JOIN "mshop_index_text" AS mindte ON mindte."prodid" = mpro."id"' ),
30
			'label' => 'Product index text ID',
31
			'type' => 'string',
32
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
33
			'public' => false,
34
		),
35
		'index.text:url' => array(
36
			'code' => 'index.text:url()',
37
			'internalcode' => ':site AND mindte."url"',
38
			'label' => 'Product URL',
39
			'type' => 'string',
40
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
41
			'public' => false,
42
		),
43
		'index.text:name' => array(
44
			'code' => 'index.text:name()',
45
			'internalcode' => ':site AND mindte."langid" = $1 AND mindte."name"',
46
			'label' => 'Product name, parameter(<language ID>)',
47
			'type' => 'string',
48
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
49
			'public' => false,
50
		),
51
		'sort:index.text:name' => array(
52
			'code' => 'sort:index.text:name()',
53
			'internalcode' => 'mindte."name"',
54
			'label' => 'Sort by product name, parameter(<language ID>)',
55
			'type' => 'string',
56
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_STR,
57
			'public' => false,
58
		),
59
		'index.text:relevance' => array(
60
			'code' => 'index.text:relevance()',
61
			'internalcode' => ':site AND mindte."langid" = $1 AND POSITION( $2 IN mindte."content" )',
62
			'label' => 'Product texts, parameter(<language ID>,<search term>)',
63
			'type' => 'float',
64
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_FLOAT,
65
			'public' => false,
66
		),
67
		'sort:index.text:relevance' => array(
68
			'code' => 'sort:index.text:relevance()',
69
			'internalcode' => '-POSITION( $2 IN mindte."content" )',
70
			'label' => 'Product texts, parameter(<language ID>,<search term>)',
71
			'type' => 'float',
72
			'internaltype' => \Aimeos\MW\DB\Statement\Base::PARAM_FLOAT,
73
			'public' => false,
74
		),
75
	);
76
77
	private $languageIds;
78
	private $subManagers;
79
80
81
	/**
82
	 * Initializes the manager instance.
83
	 *
84
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context object
85
	 */
86
	public function __construct( \Aimeos\MShop\Context\Item\Iface $context )
87
	{
88
		parent::__construct( $context );
89
90
		$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
91
		$level = $context->config()->get( 'mshop/index/manager/sitemode', $level );
92
93
		$this->searchConfig['index.text:relevance']['function'] = $this->getFunctionRelevance();
94
95
		foreach( ['index.text:name', 'index.text:url', 'index.text:relevance'] as $key )
96
		{
97
			$expr = $this->getSiteString( 'mindte."siteid"', $level );
98
			$this->searchConfig[$key]['internalcode'] = str_replace( ':site', $expr, $this->searchConfig[$key]['internalcode'] );
99
		}
100
	}
101
102
103
	/**
104
	 * Counts the number products that are available for the values of the given key.
105
	 *
106
	 * @param \Aimeos\MW\Criteria\Iface $search Search criteria
107
	 * @param string $key Search key (usually the ID) to aggregate products for
108
	 * @param string|null $value Search key for aggregating the value column
109
	 * @param string|null $type Type of the aggregation, empty string for count or "sum" or "avg" (average)
110
	 * @return \Aimeos\Map List of ID values as key and the number of counted products as value
111
	 */
112
	public function aggregate( \Aimeos\MW\Criteria\Iface $search, $key, string $value = null, string $type = null ) : \Aimeos\Map
113
	{
114
		return [];
115
	}
116
117
118
	/**
119
	 * Removes old entries from the storage.
120
	 *
121
	 * @param iterable $siteids List of IDs for sites whose entries should be deleted
122
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
123
	 */
124
	public function clear( iterable $siteids ) : \Aimeos\MShop\Common\Manager\Iface
125
	{
126
		parent::clear( $siteids );
127
128
		return $this->clearBase( $siteids, 'mshop/index/manager/text/delete' );
129
	}
130
131
132
	/**
133
	 * Removes all entries not touched after the given timestamp in the index.
134
	 * This can be a long lasting operation.
135
	 *
136
	 * @param string $timestamp Timestamp in ISO format (YYYY-MM-DD HH:mm:ss)
137
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
138
	 */
139
	public function cleanup( string $timestamp ) : \Aimeos\MShop\Index\Manager\Iface
140
	{
141
		/** mshop/index/manager/text/cleanup/mysql
142
		 * Deletes the index text records that haven't been touched
143
		 *
144
		 * @see mshop/index/manager/text/cleanup/ansi
145
		 */
146
147
		/** mshop/index/manager/text/cleanup/ansi
148
		 * Deletes the index text records that haven't been touched
149
		 *
150
		 * During the rebuild process of the product index, the entries of all
151
		 * active products will be removed and readded. Thus, no stale data for
152
		 * these products will remain in the database.
153
		 *
154
		 * All products that have been disabled since the last rebuild will be
155
		 * still part of the index. The cleanup statement removes all records
156
		 * that belong to products that haven't been touched during the index
157
		 * rebuild because these are the disabled ones.
158
		 *
159
		 * The SQL statement should conform to the ANSI standard to be
160
		 * compatible with most relational database systems. This also
161
		 * includes using double quotes for table and column names.
162
		 *
163
		 * @param string SQL statement for deleting the outdated text index records
164
		 * @since 2014.03
165
		 * @category Developer
166
		 * @see mshop/index/manager/text/count/ansi
167
		 * @see mshop/index/manager/text/delete/ansi
168
		 * @see mshop/index/manager/text/insert/ansi
169
		 * @see mshop/index/manager/text/search/ansi
170
		 * @see mshop/index/manager/text/text/ansi
171
		 */
172
		return $this->cleanupBase( $timestamp, 'mshop/index/manager/text/cleanup' );
173
	}
174
175
176
	/**
177
	 * Removes multiple items.
178
	 *
179
	 * @param \Aimeos\MShop\Common\Item\Iface[]|string[] $itemIds List of item objects or IDs of the items
180
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
181
	 */
182
	public function delete( $itemIds ) : \Aimeos\MShop\Common\Manager\Iface
183
	{
184
		/** mshop/index/manager/text/delete/mysql
185
		 * Deletes the items matched by the given IDs from the database
186
		 *
187
		 * @see mshop/index/manager/text/delete/ansi
188
		 */
189
190
		/** mshop/index/manager/text/delete/ansi
191
		 * Deletes the items matched by the given IDs from the database
192
		 *
193
		 * Removes the records specified by the given IDs from the index database.
194
		 * The records must be from the site that is configured via the
195
		 * context item.
196
		 *
197
		 * The ":cond" placeholder is replaced by the name of the ID column and
198
		 * the given ID or list of IDs while the site ID is bound to the question
199
		 * mark.
200
		 *
201
		 * The SQL statement should conform to the ANSI standard to be
202
		 * compatible with most relational database systems. This also
203
		 * includes using double quotes for table and column names.
204
		 *
205
		 * @param string SQL statement for deleting index text records
206
		 * @since 2014.03
207
		 * @category Developer
208
		 * @see mshop/index/manager/text/count/ansi
209
		 * @see mshop/index/manager/text/cleanup/ansi
210
		 * @see mshop/index/manager/text/insert/ansi
211
		 * @see mshop/index/manager/text/search/ansi
212
		 * @see mshop/index/manager/text/text/ansi
213
		 */
214
		return $this->deleteItemsBase( $itemIds, 'mshop/index/manager/text/delete', true, 'prodid' );
215
	}
216
217
218
	/**
219
	 * Returns the available manager types
220
	 *
221
	 * @param bool $withsub Return also the resource type of sub-managers if true
222
	 * @return string[] Type of the manager and submanagers, subtypes are separated by slashes
223
	 */
224
	public function getResourceType( bool $withsub = true ) : array
225
	{
226
		$path = 'mshop/index/manager/text/submanagers';
227
		return $this->getResourceTypeBase( 'index/text', $path, [], $withsub );
228
	}
229
230
231
	/**
232
	 * Returns a list of objects describing the available criterias for searching.
233
	 *
234
	 * @param bool $withsub Return also attributes of sub-managers if true
235
	 * @return array List of items implementing \Aimeos\MW\Criteria\Attribute\Iface
236
	 */
237
	public function getSearchAttributes( bool $withsub = true ) : array
238
	{
239
		$list = parent::getSearchAttributes( $withsub );
240
241
		/** mshop/index/manager/text/submanagers
242
		 * List of manager names that can be instantiated by the index text manager
243
		 *
244
		 * Managers provide a generic interface to the underlying storage.
245
		 * Each manager has or can have sub-managers caring about particular
246
		 * aspects. Each of these sub-managers can be instantiated by its
247
		 * parent manager using the getSubManager() method.
248
		 *
249
		 * The search keys from sub-managers can be normally used in the
250
		 * manager as well. It allows you to search for items of the manager
251
		 * using the search keys of the sub-managers to further limit the
252
		 * retrieved list of items.
253
		 *
254
		 * @param array List of sub-manager names
255
		 * @since 2014.03
256
		 * @category Developer
257
		 */
258
		$path = 'mshop/index/manager/text/submanagers';
259
260
		return $list + $this->getSearchAttributesBase( $this->searchConfig, $path, [], $withsub );
261
	}
262
263
264
	/**
265
	 * Returns a new manager for product extensions.
266
	 *
267
	 * @param string $manager Name of the sub manager type in lower case
268
	 * @param string|null $name Name of the implementation, will be from configuration (or Default) if null
269
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager for different extensions, e.g stock, tags, locations, etc.
270
	 */
271
	public function getSubManager( string $manager, string $name = null ) : \Aimeos\MShop\Common\Manager\Iface
272
	{
273
		/** mshop/index/manager/text/name
274
		 * Class name of the used index text manager implementation
275
		 *
276
		 * Each default index text manager can be replaced by an alternative imlementation.
277
		 * To use this implementation, you have to set the last part of the class
278
		 * name as configuration value so the manager factory knows which class it
279
		 * has to instantiate.
280
		 *
281
		 * For example, if the name of the default class is
282
		 *
283
		 *  \Aimeos\MShop\Index\Manager\Text\Standard
284
		 *
285
		 * and you want to replace it with your own version named
286
		 *
287
		 *  \Aimeos\MShop\Index\Manager\Text\Mytext
288
		 *
289
		 * then you have to set the this configuration option:
290
		 *
291
		 *  mshop/index/manager/text/name = Mytext
292
		 *
293
		 * The value is the last part of your own class name and it's case sensitive,
294
		 * so take care that the configuration value is exactly named like the last
295
		 * part of the class name.
296
		 *
297
		 * The allowed characters of the class name are A-Z, a-z and 0-9. No other
298
		 * characters are possible! You should always start the last part of the class
299
		 * name with an upper case character and continue only with lower case characters
300
		 * or numbers. Avoid chamel case names like "MyText"!
301
		 *
302
		 * @param string Last part of the class name
303
		 * @since 2014.03
304
		 * @category Developer
305
		 */
306
307
		/** mshop/index/manager/text/decorators/excludes
308
		 * Excludes decorators added by the "common" option from the index text manager
309
		 *
310
		 * Decorators extend the functionality of a class by adding new aspects
311
		 * (e.g. log what is currently done), executing the methods of the underlying
312
		 * class only in certain conditions (e.g. only for logged in users) or
313
		 * modify what is returned to the caller.
314
		 *
315
		 * This option allows you to remove a decorator added via
316
		 * "mshop/common/manager/decorators/default" before they are wrapped
317
		 * around the index text manager.
318
		 *
319
		 *  mshop/index/manager/text/decorators/excludes = array( 'decorator1' )
320
		 *
321
		 * This would remove the decorator named "decorator1" from the list of
322
		 * common decorators ("\Aimeos\MShop\Common\Manager\Decorator\*") added via
323
		 * "mshop/common/manager/decorators/default" for the index text manager.
324
		 *
325
		 * @param array List of decorator names
326
		 * @since 2014.03
327
		 * @category Developer
328
		 * @see mshop/common/manager/decorators/default
329
		 * @see mshop/index/manager/text/decorators/global
330
		 * @see mshop/index/manager/text/decorators/local
331
		 */
332
333
		/** mshop/index/manager/text/decorators/global
334
		 * Adds a list of globally available decorators only to the index text manager
335
		 *
336
		 * Decorators extend the functionality of a class by adding new aspects
337
		 * (e.g. log what is currently done), executing the methods of the underlying
338
		 * class only in certain conditions (e.g. only for logged in users) or
339
		 * modify what is returned to the caller.
340
		 *
341
		 * This option allows you to wrap global decorators
342
		 * ("\Aimeos\MShop\Common\Manager\Decorator\*") around the index text
343
		 * manager.
344
		 *
345
		 *  mshop/index/manager/text/decorators/global = array( 'decorator1' )
346
		 *
347
		 * This would add the decorator named "decorator1" defined by
348
		 * "\Aimeos\MShop\Common\Manager\Decorator\Decorator1" only to the index
349
		 * text manager.
350
		 *
351
		 * @param array List of decorator names
352
		 * @since 2014.03
353
		 * @category Developer
354
		 * @see mshop/common/manager/decorators/default
355
		 * @see mshop/index/manager/text/decorators/excludes
356
		 * @see mshop/index/manager/text/decorators/local
357
		 */
358
359
		/** mshop/index/manager/text/decorators/local
360
		 * Adds a list of local decorators only to the index text manager
361
		 *
362
		 * Decorators extend the functionality of a class by adding new aspects
363
		 * (e.g. log what is currently done), executing the methods of the underlying
364
		 * class only in certain conditions (e.g. only for logged in users) or
365
		 * modify what is returned to the caller.
366
		 *
367
		 * This option allows you to wrap local decorators
368
		 * ("\Aimeos\MShop\Index\Manager\Text\Decorator\*") around the index text
369
		 * manager.
370
		 *
371
		 *  mshop/index/manager/text/decorators/local = array( 'decorator2' )
372
		 *
373
		 * This would add the decorator named "decorator2" defined by
374
		 * "\Aimeos\MShop\Index\Manager\Text\Decorator\Decorator2" only to the index
375
		 * text manager.
376
		 *
377
		 * @param array List of decorator names
378
		 * @since 2014.03
379
		 * @category Developer
380
		 * @see mshop/common/manager/decorators/default
381
		 * @see mshop/index/manager/text/decorators/excludes
382
		 * @see mshop/index/manager/text/decorators/global
383
		 */
384
385
		return $this->getSubManagerBase( 'index', 'text/' . $manager, $name );
386
	}
387
388
389
	/**
390
	 * Optimizes the index if necessary.
391
	 * Execution of this operation can take a very long time and shouldn't be
392
	 * called through a web server enviroment.
393
	 *
394
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
395
	 */
396
	public function optimize() : \Aimeos\MShop\Index\Manager\Iface
397
	{
398
		/** mshop/index/manager/text/optimize/mysql
399
		 * Optimizes the stored text data for retrieving the records faster
400
		 *
401
		 * @see mshop/index/manager/text/optimize/ansi
402
		 */
403
404
		/** mshop/index/manager/text/optimize/ansi
405
		 * Optimizes the stored text data for retrieving the records faster
406
		 *
407
		 * The SQL statement should reorganize the data in the DBMS storage to
408
		 * optimize access to the records of the table or tables. Some DBMS
409
		 * offer specialized statements to optimize indexes and records. This
410
		 * statement doesn't return any records.
411
		 *
412
		 * The SQL statement should conform to the ANSI standard to be
413
		 * compatible with most relational database systems. This also
414
		 * includes using double quotes for table and column names.
415
		 *
416
		 * @param string SQL statement for optimizing the stored text data
417
		 * @since 2014.09
418
		 * @category Developer
419
		 * @see mshop/index/manager/text/aggregate/ansi
420
		 * @see mshop/index/manager/text/cleanup/ansi
421
		 * @see mshop/index/manager/text/count/ansi
422
		 * @see mshop/index/manager/text/insert/ansi
423
		 * @see mshop/index/manager/text/search/ansi
424
		 * @see mshop/index/manager/text/text/ansi
425
		 */
426
		return $this->optimizeBase( 'mshop/index/manager/text/optimize' );
427
	}
428
429
430
	/**
431
	 * Rebuilds the index text for searching products or specified list of products.
432
	 * This can be a long lasting operation.
433
	 *
434
	 * @param \Aimeos\MShop\Product\Item\Iface[] $items Associative list of product IDs as keys and items as values
435
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
436
	 */
437
	public function rebuild( iterable $items = [] ) : \Aimeos\MShop\Index\Manager\Iface
438
	{
439
		if( map( $items )->isEmpty() ) { return $this; }
440
441
		\Aimeos\MW\Common\Base::checkClassList( \Aimeos\MShop\Product\Item\Iface::class, $items );
442
443
		$context = $this->context();
444
		$dbm = $context->db();
445
		$dbname = $this->getResourceName();
446
		$conn = $dbm->acquire( $dbname );
447
448
		try
449
		{
450
			/** mshop/index/manager/text/insert/mysql
451
			 * Inserts a new text record into the product index database
452
			 *
453
			 * @see mshop/index/manager/text/insert/ansi
454
			 */
455
456
			/** mshop/index/manager/text/insert/ansi
457
			 * Inserts a new text record into the product index database
458
			 *
459
			 * During the product index rebuild, texts related to a product
460
			 * will be stored in the index for this product. All records
461
			 * are deleted before the new ones are inserted.
462
			 *
463
			 * The SQL statement must be a string suitable for being used as
464
			 * prepared statement. It must include question marks for binding
465
			 * the values from the order item to the statement before they are
466
			 * sent to the database server. The number of question marks must
467
			 * be the same as the number of columns listed in the INSERT
468
			 * statement. The order of the columns must correspond to the
469
			 * order in the rebuild() method, so the correct values are
470
			 * bound to the columns.
471
			 *
472
			 * The SQL statement should conform to the ANSI standard to be
473
			 * compatible with most relational database systems. This also
474
			 * includes using double quotes for table and column names.
475
			 *
476
			 * @param string SQL statement for inserting records
477
			 * @since 2014.03
478
			 * @category Developer
479
			 * @see mshop/index/manager/text/cleanup/ansi
480
			 * @see mshop/index/manager/text/count/ansi
481
			 * @see mshop/index/manager/text/delete/ansi
482
			 * @see mshop/index/manager/text/insert/ansi
483
			 * @see mshop/index/manager/text/search/ansi
484
			 * @see mshop/index/manager/text/text/ansi
485
			 */
486
			$stmt = $this->getCachedStatement( $conn, 'mshop/index/manager/text/insert' );
487
488
			foreach( $items as $item ) {
489
				$this->saveTexts( $stmt, $item );
490
			}
491
492
			$dbm->release( $conn, $dbname );
493
		}
494
		catch( \Exception $e )
495
		{
496
			$dbm->release( $conn, $dbname );
497
			throw $e;
498
		}
499
500
		foreach( $this->getSubManagers() as $submanager ) {
501
			$submanager->rebuild( $items );
502
		}
503
504
		return $this;
505
	}
506
507
508
	/**
509
	 * Removes the products from the product index.
510
	 *
511
	 * @param array|string $ids Product ID or list of IDs
512
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
513
	 */
514
	public function remove( $ids ) : \Aimeos\MShop\Index\Manager\Iface
515
	{
516
		parent::remove( $ids )->delete( $ids );
517
		return $this;
518
	}
519
520
521
	/**
522
	 * Searches for items matching the given criteria.
523
	 *
524
	 * @param \Aimeos\MW\Criteria\Iface $search Search criteria object
525
	 * @param string[] $ref List of domains to fetch list items and referenced items for
526
	 * @param int|null &$total Number of items that are available in total
527
	 * @return \Aimeos\Map List of items implementing \Aimeos\MShop\Product\Item\Iface with ids as keys
528
	 */
529
	public function search( \Aimeos\MW\Criteria\Iface $search, array $ref = [], int &$total = null ) : \Aimeos\Map
530
	{
531
		/** mshop/index/manager/text/search/mysql
532
		 * Retrieves the records matched by the given criteria in the database
533
		 *
534
		 * @see mshop/index/manager/text/search/ansi
535
		 */
536
537
		/** mshop/index/manager/text/search/ansi
538
		 * Retrieves the records matched by the given criteria in the database
539
		 *
540
		 * Fetches the records matched by the given criteria from the product index
541
		 * database. The records must be from one of the sites that are
542
		 * configured via the context item. If the current site is part of
543
		 * a tree of sites, the SELECT statement can retrieve all records
544
		 * from the current site and the complete sub-tree of sites.
545
		 *
546
		 * As the records can normally be limited by criteria from sub-managers,
547
		 * their tables must be joined in the SQL context. This is done by
548
		 * using the "internaldeps" property from the definition of the ID
549
		 * column of the sub-managers. These internal dependencies specify
550
		 * the JOIN between the tables and the used columns for joining. The
551
		 * ":joins" placeholder is then replaced by the JOIN strings from
552
		 * the sub-managers.
553
		 *
554
		 * To limit the records matched, conditions can be added to the given
555
		 * criteria object. It can contain comparisons like column names that
556
		 * must match specific values which can be combined by AND, OR or NOT
557
		 * operators. The resulting string of SQL conditions replaces the
558
		 * ":cond" placeholder before the statement is sent to the database
559
		 * server.
560
		 *
561
		 * If the records that are retrieved should be ordered by one or more
562
		 * columns, the generated string of column / sort direction pairs
563
		 * replaces the ":order" placeholder. In case no ordering is required,
564
		 * the complete ORDER BY part including the "\/*-orderby*\/...\/*orderby-*\/"
565
		 * markers is removed to speed up retrieving the records. Columns of
566
		 * sub-managers can also be used for ordering the result set but then
567
		 * no index can be used.
568
		 *
569
		 * The number of returned records can be limited and can start at any
570
		 * number between the begining and the end of the result set. For that
571
		 * the ":size" and ":start" placeholders are replaced by the
572
		 * corresponding values from the criteria object. The default values
573
		 * are 0 for the start and 100 for the size value.
574
		 *
575
		 * The SQL statement should conform to the ANSI standard to be
576
		 * compatible with most relational database systems. This also
577
		 * includes using double quotes for table and column names.
578
		 *
579
		 * @param string SQL statement for searching items
580
		 * @since 2014.03
581
		 * @category Developer
582
		 * @see mshop/index/manager/text/aggregate/ansi
583
		 * @see mshop/index/manager/text/cleanup/ansi
584
		 * @see mshop/index/manager/text/count/ansi
585
		 * @see mshop/index/manager/text/insert/ansi
586
		 * @see mshop/index/manager/text/optimize/ansi
587
		 * @see mshop/index/manager/text/text/ansi
588
		 */
589
		$cfgPathSearch = 'mshop/index/manager/text/search';
590
591
		/** mshop/index/manager/text/count/mysql
592
		 * Counts the number of records matched by the given criteria in the database
593
		 *
594
		 * @see mshop/index/manager/text/count/ansi
595
		 */
596
597
		/** mshop/index/manager/text/count/ansi
598
		 * Counts the number of records matched by the given criteria in the database
599
		 *
600
		 * Counts all records matched by the given criteria from the product index
601
		 * database. The records must be from one of the sites that are
602
		 * configured via the context item. If the current site is part of
603
		 * a tree of sites, the statement can count all records from the
604
		 * current site and the complete sub-tree of sites.
605
		 *
606
		 * As the records can normally be limited by criteria from sub-managers,
607
		 * their tables must be joined in the SQL context. This is done by
608
		 * using the "internaldeps" property from the definition of the ID
609
		 * column of the sub-managers. These internal dependencies specify
610
		 * the JOIN between the tables and the used columns for joining. The
611
		 * ":joins" placeholder is then replaced by the JOIN strings from
612
		 * the sub-managers.
613
		 *
614
		 * To limit the records matched, conditions can be added to the given
615
		 * criteria object. It can contain comparisons like column names that
616
		 * must match specific values which can be combined by AND, OR or NOT
617
		 * operators. The resulting string of SQL conditions replaces the
618
		 * ":cond" placeholder before the statement is sent to the database
619
		 * server.
620
		 *
621
		 * Both, the strings for ":joins" and for ":cond" are the same as for
622
		 * the "search" SQL statement.
623
		 *
624
		 * Contrary to the "search" statement, it doesn't return any records
625
		 * but instead the number of records that have been found. As counting
626
		 * thousands of records can be a long running task, the maximum number
627
		 * of counted records is limited for performance reasons.
628
		 *
629
		 * The SQL statement should conform to the ANSI standard to be
630
		 * compatible with most relational database systems. This also
631
		 * includes using double quotes for table and column names.
632
		 *
633
		 * @param string SQL statement for counting items
634
		 * @since 2014.03
635
		 * @category Developer
636
		 * @see mshop/index/manager/text/aggregate/ansi
637
		 * @see mshop/index/manager/text/cleanup/ansi
638
		 * @see mshop/index/manager/text/insert/ansi
639
		 * @see mshop/index/manager/text/optimize/ansi
640
		 * @see mshop/index/manager/text/search/ansi
641
		 * @see mshop/index/manager/text/text/ansi
642
		 */
643
		$cfgPathCount = 'mshop/index/manager/text/count';
644
645
		return $this->searchItemsIndexBase( $search, $ref, $total, $cfgPathSearch, $cfgPathCount );
646
	}
647
648
649
	/**
650
	 * Returns the search function for searching by relevance
651
	 *
652
	 * @return \Closure Relevance search function
653
	 */
654
	protected function getFunctionRelevance()
655
	{
656
		return function( $source, array $params ) {
657
658
			if( isset( $params[1] ) ) {
659
				$params[1] = mb_strtolower( $params[1] );
660
			}
661
662
			return $params;
663
		};
664
	}
665
666
667
	/**
668
	 * Returns the language IDs available for the current site
669
	 *
670
	 * @return string[] List of ISO language codes
671
	 */
672
	protected function getLanguageIds() : array
673
	{
674
		if( !isset( $this->languageIds ) )
675
		{
676
			$list = [];
677
			$manager = \Aimeos\MShop::create( $this->context(), 'locale' );
678
			$items = $manager->search( $manager->filter()->slice( 0, 10000 ) );
679
680
			foreach( $items as $item ) {
681
				$list[$item->getLanguageId()] = null;
682
			}
683
684
			$this->languageIds = array_keys( $list );
685
		}
686
687
		return $this->languageIds;
688
	}
689
690
691
	/**
692
	 * Saves the text items referenced indirectly by products
693
	 *
694
	 * @param \Aimeos\MW\DB\Statement\Iface $stmt Prepared SQL statement with place holders
695
	 * @param \Aimeos\MShop\Product\Item\Iface $item Product item containing associated text items
696
	 */
697
	protected function saveTexts( \Aimeos\MW\DB\Statement\Iface $stmt, \Aimeos\MShop\Product\Item\Iface $item )
698
	{
699
		$texts = [];
700
		$config = $this->context()->config();
701
702
		/** mshop/index/manager/text/types
703
		 * List of text types that should be added to the product index
704
		 *
705
		 * By default, all available texts of a product are indexed. This setting
706
		 * allows you to name only those text types that should be added. All
707
		 * others will be left out so products won't be found if users search
708
		 * for words that are part of those skipped texts. This is most useful
709
		 * for avoiding product matches due to texts that should be internal only.
710
		 *
711
		 * @param array|string|null Type name or list of type names, null for all
712
		 * @category Developer
713
		 * @since 2019.04
714
		 */
715
		$types = $config->get( 'mshop/index/manager/text/types' );
716
717
		/** mshop/index/manager/text/attribute-types
718
		 * List of attribute types that should be added to the product index
719
		 *
720
		 * By default, hidden attributes are not displayed. This setting
721
		 * allows you to name only those attribute types that should be added. All
722
		 * others will be left out so products won't be found if users search
723
		 * for words that are part of those skipped attributes.
724
		 *
725
		 * @param array|string|null Type name or list of type names, null for all
726
		 * @category Developer
727
		 * @since 2020.10
728
		 */
729
		$attrTypes = $config->get( 'mshop/index/manager/text/attribute-types', ['variant', 'default'] );
730
731
		foreach( $item->getRefItems( 'text', 'url', 'default' ) as $text ) {
732
			$texts[$text->getLanguageId()]['url'] = \Aimeos\MW\Str::slug( $text->getContent() );
733
		}
734
735
		foreach( $item->getRefItems( 'text', 'name', 'default' ) as $text ) {
736
			$texts[$text->getLanguageId()]['name'] = $text->getContent();
737
		}
738
739
		$products = $item->getRefItems( 'product', null, 'default' )->push( $item );
740
741
		foreach( $products as $product )
742
		{
743
			foreach( $this->getLanguageIds() as $langId )
744
			{
745
				$texts[$langId]['content'][] = $product->getCode();
746
747
				foreach( $product->getRefItems( 'catalog' ) as $catItem ) {
748
					$texts[$langId]['content'][] = $catItem->getName();
749
				}
750
751
				foreach( $product->getRefItems( 'supplier' ) as $supItem ) {
752
					$texts[$langId]['content'][] = $supItem->getName();
753
				}
754
755
				foreach( $product->getRefItems( 'attribute', null, $attrTypes ) as $attrItem ) {
756
					$texts[$langId]['content'][] = $attrItem ->getName();
757
				}
758
			}
759
760
			foreach( $product->getRefItems( 'text', $types ) as $text ) {
761
				$texts[$text->getLanguageId()]['content'][] = $text->getContent();
762
			}
763
		}
764
765
		$this->saveTextMap( $stmt, $item, $texts );
766
	}
767
768
769
	/**
770
	 * Saves the mapped texts for the given item
771
	 *
772
	 * @param \Aimeos\MW\DB\Statement\Iface $stmt Prepared SQL statement with place holders
773
	 * @param \Aimeos\MShop\Product\Item\Iface $item Product item containing associated text items
774
	 * @param array $map Associative list of text types as keys and content as value
775
	 */
776
	protected function saveTextMap( \Aimeos\MW\DB\Statement\Iface $stmt, \Aimeos\MShop\Product\Item\Iface $item, array $texts )
777
	{
778
		$date = date( 'Y-m-d H:i:s' );
779
		$siteid = $this->context()->locale()->getSiteId();
780
781
		foreach( $texts as $langId => $map )
782
		{
783
			if( $langId == '' ) {
784
				continue;
785
			}
786
787
			$url = $map['url'] ?? $item->getName( 'url', $langId );
788
789
			if( isset( $texts[''] ) ) {
790
				$map['content'] = array_merge( $map['content'], $texts['']['content'] );
791
			}
792
793
			if( !isset( $map['name'] ) )
794
			{
795
				if( isset( $texts['']['name'] ) ) {
796
					$map['name'] = $texts['']['name'];
797
				} else {
798
					$map['content'][] = $map['name'] = $item->getLabel();
799
				}
800
			}
801
802
			$content = ' ' . join( ' ', $map['content'] ); // extra space for SQL POSITION() > 0
803
			$this->saveText( $stmt, $item->getId(), $siteid, $langId, $url, $map['name'], $content, $date );
0 ignored issues
show
Bug introduced by
It seems like $item->getId() can also be of type null; however, parameter $id of Aimeos\MShop\Index\Manag...xt\Standard::saveText() 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

803
			$this->saveText( $stmt, /** @scrutinizer ignore-type */ $item->getId(), $siteid, $langId, $url, $map['name'], $content, $date );
Loading history...
804
		}
805
	}
806
807
808
	/**
809
	 * Saves the text record with given set of parameters.
810
	 *
811
	 * @param \Aimeos\MW\DB\Statement\Iface $stmt Prepared SQL statement with place holders
812
	 * @param string $id ID of the product item
813
	 * @param string $siteid Site ID
814
	 * @param string $lang Two letter ISO language code
815
	 * @param string $url Product name in URL
816
	 * @param string $name Name of the product
817
	 * @param string $content Text content to store
818
	 * @param string $date Current timestamp in "YYYY-MM-DD HH:mm:ss" format
819
	 */
820
	protected function saveText( \Aimeos\MW\DB\Statement\Iface $stmt, string $id, string $siteid, string $lang,
821
		string $url, string $name, string $content, string $date )
822
	{
823
		$stmt->bind( 1, $id, \Aimeos\MW\DB\Statement\Base::PARAM_INT );
824
		$stmt->bind( 2, $lang );
825
		$stmt->bind( 3, $url );
826
		$stmt->bind( 4, $name );
827
		$stmt->bind( 5, mb_strtolower( $content ) ); // for case insensitive searches
828
		$stmt->bind( 6, $date ); //mtime
829
		$stmt->bind( 7, $siteid );
830
831
		try {
832
			$stmt->execute()->finish();
833
		} catch( \Aimeos\MW\DB\Exception $e ) { ; } // Ignore duplicates
834
	}
835
836
837
	/**
838
	 * Returns the list of sub-managers available for the index attribute manager.
839
	 *
840
	 * @return \Aimeos\MShop\Index\Manager\Iface[] Associative list of the sub-domain as key and the manager object as value
841
	 */
842
	protected function getSubManagers() : array
843
	{
844
		if( $this->subManagers === null )
845
		{
846
			$this->subManagers = [];
847
			$config = $this->context()->config();
848
849
			/** mshop/index/manager/text/submanagers
850
			 * A list of sub-manager names used for indexing associated items to texts
851
			 *
852
			 * All items referenced by a product (e.g. texts, prices, media,
853
			 * etc.) are added to the product index via specialized index
854
			 * managers. You can add the name of new sub-managers to add more
855
			 * data to the index or remove existing ones if you don't want to
856
			 * index that data at all.
857
			 *
858
			 * This option configures the sub-managers that cares about
859
			 * indexing data associated to product texts.
860
			 *
861
			 * @param string List of index sub-manager names
862
			 * @since 2014.09
863
			 * @category User
864
			 * @category Developer
865
			 * @see mshop/index/manager/submanagers
866
			 */
867
			foreach( $config->get( 'mshop/index/manager/text/submanagers', [] ) as $domain )
868
			{
869
				$name = $config->get( 'mshop/index/manager/text/' . $domain . '/name' );
870
				$this->subManagers[$domain] = $this->object()->getSubManager( $domain, $name );
871
			}
872
873
			return $this->subManagers;
874
		}
875
876
		return $this->subManagers;
877
	}
878
}
879