Passed
Push — master ( 494e53...aeeda4 )
by Aimeos
09:49
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-2022
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\Base\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\Base\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\Base\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\Base\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\Base\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\Base\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\ContextIface $context Context object
85
	 */
86
	public function __construct( \Aimeos\MShop\ContextIface $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->siteString( '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\Base\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\Base\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\Base\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( ( $items = map( $items ) )->isEmpty() ) { return $this; }
440
441
		$items->implements( \Aimeos\MShop\Product\Item\Iface::class, true );
442
443
		$context = $this->context();
444
		$conn = $context->db( $this->getResourceName() );
445
446
			/** mshop/index/manager/text/insert/mysql
447
			 * Inserts a new text record into the product index database
448
			 *
449
			 * @see mshop/index/manager/text/insert/ansi
450
			 */
451
452
			/** mshop/index/manager/text/insert/ansi
453
			 * Inserts a new text record into the product index database
454
			 *
455
			 * During the product index rebuild, texts related to a product
456
			 * will be stored in the index for this product. All records
457
			 * are deleted before the new ones are inserted.
458
			 *
459
			 * The SQL statement must be a string suitable for being used as
460
			 * prepared statement. It must include question marks for binding
461
			 * the values from the order item to the statement before they are
462
			 * sent to the database server. The number of question marks must
463
			 * be the same as the number of columns listed in the INSERT
464
			 * statement. The order of the columns must correspond to the
465
			 * order in the rebuild() method, so the correct values are
466
			 * bound to the columns.
467
			 *
468
			 * The SQL statement should conform to the ANSI standard to be
469
			 * compatible with most relational database systems. This also
470
			 * includes using double quotes for table and column names.
471
			 *
472
			 * @param string SQL statement for inserting records
473
			 * @since 2014.03
474
			 * @category Developer
475
			 * @see mshop/index/manager/text/cleanup/ansi
476
			 * @see mshop/index/manager/text/count/ansi
477
			 * @see mshop/index/manager/text/delete/ansi
478
			 * @see mshop/index/manager/text/insert/ansi
479
			 * @see mshop/index/manager/text/search/ansi
480
			 * @see mshop/index/manager/text/text/ansi
481
			 */
482
			$stmt = $this->getCachedStatement( $conn, 'mshop/index/manager/text/insert' );
483
484
			foreach( $items as $item ) {
485
				$this->saveTexts( $stmt, $item );
486
			}
487
488
		foreach( $this->getSubManagers() as $submanager ) {
489
			$submanager->rebuild( $items );
490
		}
491
492
		return $this;
493
	}
494
495
496
	/**
497
	 * Removes the products from the product index.
498
	 *
499
	 * @param iterable|string $ids Product ID or list of IDs
500
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
501
	 */
502
	public function remove( $ids ) : \Aimeos\MShop\Index\Manager\Iface
503
	{
504
		parent::remove( $ids )->delete( $ids );
505
		return $this;
506
	}
507
508
509
	/**
510
	 * Searches for items matching the given criteria.
511
	 *
512
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria object
513
	 * @param string[] $ref List of domains to fetch list items and referenced items for
514
	 * @param int|null &$total Number of items that are available in total
515
	 * @return \Aimeos\Map List of items implementing \Aimeos\MShop\Product\Item\Iface with ids as keys
516
	 */
517
	public function search( \Aimeos\Base\Criteria\Iface $search, array $ref = [], int &$total = null ) : \Aimeos\Map
518
	{
519
		/** mshop/index/manager/text/search/mysql
520
		 * Retrieves the records matched by the given criteria in the database
521
		 *
522
		 * @see mshop/index/manager/text/search/ansi
523
		 */
524
525
		/** mshop/index/manager/text/search/ansi
526
		 * Retrieves the records matched by the given criteria in the database
527
		 *
528
		 * Fetches the records matched by the given criteria from the product index
529
		 * database. The records must be from one of the sites that are
530
		 * configured via the context item. If the current site is part of
531
		 * a tree of sites, the SELECT statement can retrieve all records
532
		 * from the current site and the complete sub-tree of sites.
533
		 *
534
		 * As the records can normally be limited by criteria from sub-managers,
535
		 * their tables must be joined in the SQL context. This is done by
536
		 * using the "internaldeps" property from the definition of the ID
537
		 * column of the sub-managers. These internal dependencies specify
538
		 * the JOIN between the tables and the used columns for joining. The
539
		 * ":joins" placeholder is then replaced by the JOIN strings from
540
		 * the sub-managers.
541
		 *
542
		 * To limit the records matched, conditions can be added to the given
543
		 * criteria object. It can contain comparisons like column names that
544
		 * must match specific values which can be combined by AND, OR or NOT
545
		 * operators. The resulting string of SQL conditions replaces the
546
		 * ":cond" placeholder before the statement is sent to the database
547
		 * server.
548
		 *
549
		 * If the records that are retrieved should be ordered by one or more
550
		 * columns, the generated string of column / sort direction pairs
551
		 * replaces the ":order" placeholder. In case no ordering is required,
552
		 * the complete ORDER BY part including the "\/*-orderby*\/...\/*orderby-*\/"
553
		 * markers is removed to speed up retrieving the records. Columns of
554
		 * sub-managers can also be used for ordering the result set but then
555
		 * no index can be used.
556
		 *
557
		 * The number of returned records can be limited and can start at any
558
		 * number between the begining and the end of the result set. For that
559
		 * the ":size" and ":start" placeholders are replaced by the
560
		 * corresponding values from the criteria object. The default values
561
		 * are 0 for the start and 100 for the size value.
562
		 *
563
		 * The SQL statement should conform to the ANSI standard to be
564
		 * compatible with most relational database systems. This also
565
		 * includes using double quotes for table and column names.
566
		 *
567
		 * @param string SQL statement for searching items
568
		 * @since 2014.03
569
		 * @category Developer
570
		 * @see mshop/index/manager/text/aggregate/ansi
571
		 * @see mshop/index/manager/text/cleanup/ansi
572
		 * @see mshop/index/manager/text/count/ansi
573
		 * @see mshop/index/manager/text/insert/ansi
574
		 * @see mshop/index/manager/text/optimize/ansi
575
		 * @see mshop/index/manager/text/text/ansi
576
		 */
577
		$cfgPathSearch = 'mshop/index/manager/text/search';
578
579
		/** mshop/index/manager/text/count/mysql
580
		 * Counts the number of records matched by the given criteria in the database
581
		 *
582
		 * @see mshop/index/manager/text/count/ansi
583
		 */
584
585
		/** mshop/index/manager/text/count/ansi
586
		 * Counts the number of records matched by the given criteria in the database
587
		 *
588
		 * Counts all records matched by the given criteria from the product index
589
		 * database. The records must be from one of the sites that are
590
		 * configured via the context item. If the current site is part of
591
		 * a tree of sites, the statement can count all records from the
592
		 * current site and the complete sub-tree of sites.
593
		 *
594
		 * As the records can normally be limited by criteria from sub-managers,
595
		 * their tables must be joined in the SQL context. This is done by
596
		 * using the "internaldeps" property from the definition of the ID
597
		 * column of the sub-managers. These internal dependencies specify
598
		 * the JOIN between the tables and the used columns for joining. The
599
		 * ":joins" placeholder is then replaced by the JOIN strings from
600
		 * the sub-managers.
601
		 *
602
		 * To limit the records matched, conditions can be added to the given
603
		 * criteria object. It can contain comparisons like column names that
604
		 * must match specific values which can be combined by AND, OR or NOT
605
		 * operators. The resulting string of SQL conditions replaces the
606
		 * ":cond" placeholder before the statement is sent to the database
607
		 * server.
608
		 *
609
		 * Both, the strings for ":joins" and for ":cond" are the same as for
610
		 * the "search" SQL statement.
611
		 *
612
		 * Contrary to the "search" statement, it doesn't return any records
613
		 * but instead the number of records that have been found. As counting
614
		 * thousands of records can be a long running task, the maximum number
615
		 * of counted records is limited for performance reasons.
616
		 *
617
		 * The SQL statement should conform to the ANSI standard to be
618
		 * compatible with most relational database systems. This also
619
		 * includes using double quotes for table and column names.
620
		 *
621
		 * @param string SQL statement for counting items
622
		 * @since 2014.03
623
		 * @category Developer
624
		 * @see mshop/index/manager/text/aggregate/ansi
625
		 * @see mshop/index/manager/text/cleanup/ansi
626
		 * @see mshop/index/manager/text/insert/ansi
627
		 * @see mshop/index/manager/text/optimize/ansi
628
		 * @see mshop/index/manager/text/search/ansi
629
		 * @see mshop/index/manager/text/text/ansi
630
		 */
631
		$cfgPathCount = 'mshop/index/manager/text/count';
632
633
		return $this->searchItemsIndexBase( $search, $ref, $total, $cfgPathSearch, $cfgPathCount );
634
	}
635
636
637
	/**
638
	 * Returns the search function for searching by relevance
639
	 *
640
	 * @return \Closure Relevance search function
641
	 */
642
	protected function getFunctionRelevance()
643
	{
644
		return function( $source, array $params ) {
645
646
			if( isset( $params[1] ) ) {
647
				$params[1] = mb_strtolower( $params[1] );
648
			}
649
650
			return $params;
651
		};
652
	}
653
654
655
	/**
656
	 * Returns the language IDs available for the current site
657
	 *
658
	 * @return string[] List of ISO language codes
659
	 */
660
	protected function getLanguageIds() : array
661
	{
662
		if( !isset( $this->languageIds ) )
663
		{
664
			$list = [];
665
			$manager = \Aimeos\MShop::create( $this->context(), 'locale' );
666
			$items = $manager->search( $manager->filter()->slice( 0, 10000 ) );
667
668
			foreach( $items as $item ) {
669
				$list[$item->getLanguageId()] = null;
670
			}
671
672
			$this->languageIds = array_keys( $list );
673
		}
674
675
		return $this->languageIds;
676
	}
677
678
679
	/**
680
	 * Saves the text items referenced indirectly by products
681
	 *
682
	 * @param \Aimeos\Base\DB\Statement\Iface $stmt Prepared SQL statement with place holders
683
	 * @param \Aimeos\MShop\Product\Item\Iface $item Product item containing associated text items
684
	 */
685
	protected function saveTexts( \Aimeos\Base\DB\Statement\Iface $stmt, \Aimeos\MShop\Product\Item\Iface $item )
686
	{
687
		$texts = [];
688
		$config = $this->context()->config();
689
690
		/** mshop/index/manager/text/types
691
		 * List of text types that should be added to the product index
692
		 *
693
		 * By default, all available texts of a product are indexed. This setting
694
		 * allows you to name only those text types that should be added. All
695
		 * others will be left out so products won't be found if users search
696
		 * for words that are part of those skipped texts. This is most useful
697
		 * for avoiding product matches due to texts that should be internal only.
698
		 *
699
		 * @param array|string|null Type name or list of type names, null for all
700
		 * @category Developer
701
		 * @since 2019.04
702
		 */
703
		$types = $config->get( 'mshop/index/manager/text/types' );
704
705
		/** mshop/index/manager/text/attribute-types
706
		 * List of attribute types that should be added to the product index
707
		 *
708
		 * By default, hidden attributes are not displayed. This setting
709
		 * allows you to name only those attribute types that should be added. All
710
		 * others will be left out so products won't be found if users search
711
		 * for words that are part of those skipped attributes.
712
		 *
713
		 * @param array|string|null Type name or list of type names, null for all
714
		 * @category Developer
715
		 * @since 2020.10
716
		 */
717
		$attrTypes = $config->get( 'mshop/index/manager/text/attribute-types', ['variant', 'default'] );
718
719
		foreach( $item->getRefItems( 'text', 'url', 'default' ) as $text ) {
720
			$texts[$text->getLanguageId()]['url'] = \Aimeos\Base\Str::slug( $text->getContent() );
721
		}
722
723
		foreach( $item->getRefItems( 'text', 'name', 'default' ) as $text ) {
724
			$texts[$text->getLanguageId()]['name'] = $text->getContent();
725
		}
726
727
		$products = $item->getRefItems( 'product', null, 'default' )->push( $item );
728
729
		foreach( $products as $product )
730
		{
731
			foreach( $this->getLanguageIds() as $langId )
732
			{
733
				$texts[$langId]['content'][] = $product->getCode();
734
735
				foreach( $product->getRefItems( 'catalog' ) as $catItem ) {
736
					$texts[$langId]['content'][] = $catItem->getName();
737
				}
738
739
				foreach( $product->getRefItems( 'supplier' ) as $supItem ) {
740
					$texts[$langId]['content'][] = $supItem->getName();
741
				}
742
743
				foreach( $product->getRefItems( 'attribute', null, $attrTypes ) as $attrItem ) {
744
					$texts[$langId]['content'][] = $attrItem ->getName();
745
				}
746
			}
747
748
			foreach( $product->getRefItems( 'text', $types ) as $text ) {
749
				$texts[$text->getLanguageId()]['content'][] = $text->getContent();
750
			}
751
		}
752
753
		$this->saveTextMap( $stmt, $item, $texts );
754
	}
755
756
757
	/**
758
	 * Saves the mapped texts for the given item
759
	 *
760
	 * @param \Aimeos\Base\DB\Statement\Iface $stmt Prepared SQL statement with place holders
761
	 * @param \Aimeos\MShop\Product\Item\Iface $item Product item containing associated text items
762
	 * @param array $map Associative list of text types as keys and content as value
763
	 */
764
	protected function saveTextMap( \Aimeos\Base\DB\Statement\Iface $stmt, \Aimeos\MShop\Product\Item\Iface $item, array $texts )
765
	{
766
		$date = date( 'Y-m-d H:i:s' );
767
		$siteid = $this->context()->locale()->getSiteId();
768
769
		foreach( $texts as $langId => $map )
770
		{
771
			if( $langId == '' ) {
772
				continue;
773
			}
774
775
			$url = $map['url'] ?? $item->getName( 'url', $langId );
776
777
			if( isset( $texts[''] ) ) {
778
				$map['content'] = array_merge( $map['content'], $texts['']['content'] );
779
			}
780
781
			if( !isset( $map['name'] ) )
782
			{
783
				if( isset( $texts['']['name'] ) ) {
784
					$map['name'] = $texts['']['name'];
785
				} else {
786
					$map['content'][] = $map['name'] = $item->getLabel();
787
				}
788
			}
789
790
			$content = ' ' . join( ' ', $map['content'] ); // extra space for SQL POSITION() > 0
791
			$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

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