1 | <?php |
||
2 | |||
3 | /** |
||
4 | * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0 |
||
5 | * @copyright Aimeos (aimeos.org), 2015-2020 |
||
6 | * @package MShop |
||
7 | * @subpackage Index |
||
8 | */ |
||
9 | |||
10 | |||
11 | namespace Aimeos\MShop\Index\Manager; |
||
12 | |||
13 | |||
14 | /** |
||
15 | * Base class for all database based index managers |
||
16 | * |
||
17 | * @package MShop |
||
18 | * @subpackage Index |
||
19 | */ |
||
20 | abstract class DBBase |
||
21 | extends \Aimeos\MShop\Common\Manager\Base |
||
22 | implements \Aimeos\MShop\Product\Manager\Iface |
||
23 | { |
||
24 | private $manager; |
||
25 | |||
26 | |||
27 | /** |
||
28 | * Initializes the manager object |
||
29 | * |
||
30 | * @param \Aimeos\MShop\Context\Item\Iface $context Context object |
||
31 | */ |
||
32 | public function __construct( \Aimeos\MShop\Context\Item\Iface $context ) |
||
33 | { |
||
34 | parent::__construct( $context ); |
||
35 | |||
36 | $this->setResourceName( 'db-product' ); |
||
37 | $this->manager = \Aimeos\MShop::create( $this->getContext(), 'product' ); |
||
38 | } |
||
39 | |||
40 | |||
41 | /** |
||
42 | * Removes old entries from the storage. |
||
43 | * |
||
44 | * @param string[] $siteids List of IDs for sites whose entries should be deleted |
||
45 | * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls |
||
46 | */ |
||
47 | public function clear( array $siteids ) : \Aimeos\MShop\Common\Manager\Iface |
||
48 | { |
||
49 | foreach( $this->getSubManagers() as $submanager ) { |
||
50 | $submanager->clear( $siteids ); |
||
51 | } |
||
52 | |||
53 | return $this; |
||
54 | } |
||
55 | |||
56 | |||
57 | /** |
||
58 | * Creates a new empty item instance |
||
59 | * |
||
60 | * @param array $values Values the item should be initialized with |
||
61 | * @return \Aimeos\MShop\Product\Item\Iface New product item object |
||
62 | */ |
||
63 | public function createItem( array $values = [] ) : \Aimeos\MShop\Common\Item\Iface |
||
64 | { |
||
65 | return $this->manager->createItem( $values ); |
||
66 | } |
||
67 | |||
68 | |||
69 | /** |
||
70 | * Creates a search object and optionally sets its base criteria |
||
71 | * |
||
72 | * @param bool $default True to add the default criteria |
||
73 | * @return \Aimeos\MW\Criteria\Iface Criteria object |
||
74 | */ |
||
75 | public function createSearch( bool $default = false ) : \Aimeos\MW\Criteria\Iface |
||
76 | { |
||
77 | return $this->manager->createSearch( $default ); |
||
78 | } |
||
79 | |||
80 | |||
81 | /** |
||
82 | * Removes multiple items. |
||
83 | * |
||
84 | * @param \Aimeos\MShop\Common\Item\Iface[]|string[] $itemIds List of item objects or IDs of the items |
||
85 | * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls |
||
86 | */ |
||
87 | public function deleteItems( array $itemIds ) : \Aimeos\MShop\Common\Manager\Iface |
||
88 | { |
||
89 | if( empty( $itemIds ) ) { return $this; } |
||
90 | |||
91 | foreach( $this->getSubManagers() as $submanager ) { |
||
92 | $submanager->deleteItems( $itemIds ); |
||
93 | } |
||
94 | |||
95 | return $this; |
||
96 | } |
||
97 | |||
98 | |||
99 | /** |
||
100 | * Returns the item specified by its code and domain/type if necessary |
||
101 | * |
||
102 | * @param string $code Code of the item |
||
103 | * @param string[] $ref List of domains to fetch list items and referenced items for |
||
104 | * @param string|null $domain Domain of the item if necessary to identify the item uniquely |
||
105 | * @param string|null $type Type code of the item if necessary to identify the item uniquely |
||
106 | * @param bool $default True to add default criteria |
||
107 | * @return \Aimeos\MShop\Common\Item\Iface Item object |
||
108 | */ |
||
109 | public function findItem( string $code, array $ref = [], string $domain = 'product', string $type = null, |
||
110 | bool $default = false ) : \Aimeos\MShop\Common\Item\Iface |
||
111 | { |
||
112 | return $this->manager->findItem( $code, $ref, $domain, $type, $default ); |
||
113 | } |
||
114 | |||
115 | |||
116 | /** |
||
117 | * Returns the product item for the given ID |
||
118 | * |
||
119 | * @param string $id Id of item |
||
120 | * @param string[] $ref List of domains to fetch list items and referenced items for |
||
121 | * @param bool $default Add default criteria |
||
122 | * @return \Aimeos\MShop\Product\Item\Iface Product item object |
||
123 | */ |
||
124 | public function getItem( string $id, array $ref = [], bool $default = false ) : \Aimeos\MShop\Common\Item\Iface |
||
125 | { |
||
126 | return $this->manager->getItem( $id, $ref, $default ); |
||
127 | } |
||
128 | |||
129 | |||
130 | /** |
||
131 | * Returns a list of attribute objects describing the available criteria for searching |
||
132 | * |
||
133 | * @param bool $withsub True to return attributes of sub-managers too |
||
134 | * @return array List of items implementing \Aimeos\MW\Criteria\Attribute\Iface |
||
135 | */ |
||
136 | public function getSearchAttributes( bool $withsub = true ) : array |
||
137 | { |
||
138 | return $this->manager->getSearchAttributes( $withsub ); |
||
139 | } |
||
140 | |||
141 | |||
142 | /** |
||
143 | * Rebuilds the customer index |
||
144 | * |
||
145 | * @param \Aimeos\MShop\Product\Item\Iface[] $items Associative list of product IDs and items values |
||
146 | * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls |
||
147 | */ |
||
148 | public function rebuild( array $items = [] ) : \Aimeos\MShop\Index\Manager\Iface |
||
149 | { |
||
150 | foreach( $this->getSubManagers() as $submanager ) { |
||
151 | $submanager->rebuild( $items ); |
||
152 | } |
||
153 | |||
154 | return $this; |
||
155 | } |
||
156 | |||
157 | |||
158 | /** |
||
159 | * Stores a new item into the index |
||
160 | * |
||
161 | * @param \Aimeos\MShop\Product\Item\Iface $item Product item |
||
162 | * @param bool $fetch True if the new ID should be set in the item |
||
163 | * @return \Aimeos\MShop\Product\Item\Iface Saved item |
||
164 | */ |
||
165 | public function saveItem( \Aimeos\MShop\Product\Item\Iface $item, bool $fetch = true ) : \Aimeos\MShop\Product\Item\Iface |
||
166 | { |
||
167 | $item = $this->manager->saveItem( $item, true ); |
||
168 | $this->rebuild( [$item->getId() => $item] ); |
||
169 | |||
170 | return $item; |
||
171 | } |
||
172 | |||
173 | |||
174 | /** |
||
175 | * Adds or updates a list of items |
||
176 | * |
||
177 | * @param \Aimeos\MShop\Product\Item\Iface[] $items List of items whose data should be saved |
||
178 | * @param bool $fetch True if the new ID should be returned in the item |
||
179 | * @return \Aimeos\MShop\Product\Item\Iface[] Saved items |
||
180 | */ |
||
181 | public function saveItems( array $items, bool $fetch = true ) : array |
||
182 | { |
||
183 | $list = []; |
||
184 | |||
185 | foreach( $this->manager->saveItems( $items, true ) as $item ) { |
||
186 | $list[$item->getId()] = $item; |
||
187 | } |
||
188 | |||
189 | $this->rebuild( $list ); |
||
190 | return $list; |
||
191 | } |
||
192 | |||
193 | |||
194 | /** |
||
195 | * Removes all entries not touched after the given timestamp |
||
196 | * |
||
197 | * @param string $timestamp Timestamp in ISO format (YYYY-MM-DD HH:mm:ss) |
||
198 | * @param string $path Configuration path to the SQL statement to execute |
||
199 | * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls |
||
200 | */ |
||
201 | protected function cleanupBase( string $timestamp, string $path ) : \Aimeos\MShop\Index\Manager\Iface |
||
202 | { |
||
203 | $context = $this->getContext(); |
||
204 | $siteid = $context->getLocale()->getSiteId(); |
||
205 | |||
206 | |||
207 | $this->begin(); |
||
208 | |||
209 | $dbm = $context->getDatabaseManager(); |
||
210 | $dbname = $this->getResourceName(); |
||
211 | $conn = $dbm->acquire( $dbname ); |
||
212 | |||
213 | try |
||
214 | { |
||
215 | $stmt = $this->getCachedStatement( $conn, $path ); |
||
216 | |||
217 | $stmt->bind( 1, $timestamp ); // ctime |
||
218 | $stmt->bind( 2, $siteid ); |
||
219 | |||
220 | $stmt->execute()->finish(); |
||
221 | |||
222 | $dbm->release( $conn, $dbname ); |
||
223 | } |
||
224 | catch( \Exception $e ) |
||
225 | { |
||
226 | $dbm->release( $conn, $dbname ); |
||
227 | $this->rollback(); |
||
228 | throw $e; |
||
229 | } |
||
230 | |||
231 | $this->commit(); |
||
232 | |||
233 | foreach( $this->getSubManagers() as $submanager ) { |
||
234 | $submanager->cleanup( $timestamp ); |
||
235 | } |
||
236 | |||
237 | return $this; |
||
238 | } |
||
239 | |||
240 | |||
241 | /** |
||
242 | * Removes several items from the index |
||
243 | * |
||
244 | * @param string[] $ids List of product IDs |
||
245 | * @param string $path Configuration path to the SQL statement to execute |
||
246 | * @param bool $siteidcheck If siteid should be used in the statement |
||
247 | * @param string $name Name of the ID column |
||
248 | * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls |
||
249 | */ |
||
250 | protected function deleteItemsBase( array $ids, string $path, bool $siteidcheck = true, |
||
251 | string $name = 'prodid' ) : \Aimeos\MShop\Common\Manager\Iface |
||
252 | { |
||
253 | if( empty( $ids ) ) { return $this; } |
||
254 | |||
255 | foreach( $this->getSubManagers() as $submanager ) { |
||
256 | $submanager->deleteItems( $ids ); |
||
257 | } |
||
258 | |||
259 | return parent::deleteItemsBase( $ids, $path, $siteidcheck, $name ); |
||
260 | } |
||
261 | |||
262 | |||
263 | /** |
||
264 | * Returns the product manager instance |
||
265 | * |
||
266 | * @return \Aimeos\MShop\Product\Manager\Iface Product manager object |
||
267 | */ |
||
268 | protected function getManager() : \Aimeos\MShop\Common\Manager\Iface |
||
269 | { |
||
270 | return $this->manager; |
||
271 | } |
||
272 | |||
273 | |||
274 | /** |
||
275 | * Returns the string replacements for the SQL statements |
||
276 | * |
||
277 | * @param \Aimeos\MW\Criteria\Iface $search Search critera object |
||
278 | * @param \Aimeos\MW\Criteria\Attribute\Iface[] $attributes Associative list of search keys and criteria attribute items |
||
279 | * @param \Aimeos\MW\Criteria\Plugin\Iface[] $plugins Associative list of item keys and criteria plugin objects |
||
280 | * @param string[] $joins Associative list of SQL joins |
||
281 | * @param \Aimeos\MW\Criteria\Attribute\Iface[] $columns Additional columns to retrieve values from |
||
282 | * @return array Array of keys, find and replace arrays |
||
283 | */ |
||
284 | protected function getSQLReplacements( \Aimeos\MW\Criteria\Iface $search, array $attributes, array $plugins, |
||
285 | array $joins, array $columns = [] ) : array |
||
286 | { |
||
287 | $list = []; |
||
288 | $types = $this->getSearchTypes( $attributes ); |
||
289 | $funcs = $this->getSearchFunctions( $attributes ); |
||
290 | $translations = $this->getSearchTranslations( $attributes ); |
||
291 | |||
292 | $colstring = ''; |
||
293 | foreach( $columns as $name => $entry ) { |
||
294 | $colstring .= $entry->getInternalCode() . ', '; |
||
295 | } |
||
296 | |||
297 | $find = array( ':columns', ':joins', ':cond', ':start', ':size' ); |
||
298 | $replace = array( |
||
299 | $colstring, |
||
300 | implode( "\n", array_unique( $joins ) ), |
||
301 | $search->getConditionSource( $types, $translations, $plugins, $funcs ), |
||
302 | $search->getSliceStart(), |
||
303 | $search->getSliceSize(), |
||
304 | ); |
||
305 | |||
306 | if( empty( $search->getSortations() ) && ( $attribute = reset( $attributes ) ) !== false ) |
||
307 | { |
||
308 | $search = ( clone $search )->setSortations( [$search->sort( '+', $attribute->getCode() )] ); |
||
309 | } |
||
310 | elseif( !empty( $search->getSortations() ) ) |
||
311 | { |
||
312 | $names = $search->translate( $search->getSortations(), [], $funcs ); |
||
0 ignored issues
–
show
|
|||
313 | $cols = $search->translate( $search->getSortations(), $translations, $funcs ); |
||
314 | |||
315 | $list = $translations = []; |
||
316 | foreach( $cols as $idx => $col ) |
||
317 | { |
||
318 | $list[] = 'MIN(' . $col . ') AS "s' . $idx . '"'; |
||
319 | $translations[$names[$idx]] = '"s' . $idx . '"'; |
||
320 | } |
||
321 | } |
||
322 | |||
323 | $find[] = ':mincols'; |
||
324 | $replace[] = !empty( $list ) ? ', ' . implode( ', ', $list ) : ''; |
||
325 | |||
326 | $find[] = ':order'; |
||
327 | $replace[] = $search->getSortationSource( $types, $translations, $funcs ); |
||
328 | |||
329 | return [$find, $replace]; |
||
330 | } |
||
331 | |||
332 | |||
333 | /** |
||
334 | * Optimizes the catalog customer index if necessary |
||
335 | * |
||
336 | * @param string $path Configuration path to the SQL statements to execute |
||
337 | * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls |
||
338 | */ |
||
339 | protected function optimizeBase( string $path ) : \Aimeos\MShop\Index\Manager\Iface |
||
340 | { |
||
341 | $context = $this->getContext(); |
||
342 | |||
343 | $dbm = $context->getDatabaseManager(); |
||
344 | $dbname = $this->getResourceName(); |
||
345 | $conn = $dbm->acquire( $dbname ); |
||
346 | |||
347 | try |
||
348 | { |
||
349 | foreach( (array) $this->getSqlConfig( $path ) as $sql ) { |
||
350 | $conn->create( $sql )->execute()->finish(); |
||
351 | } |
||
352 | |||
353 | $dbm->release( $conn, $dbname ); |
||
354 | } |
||
355 | catch( \Exception $e ) |
||
356 | { |
||
357 | $dbm->release( $conn, $dbname ); |
||
358 | throw $e; |
||
359 | } |
||
360 | |||
361 | foreach( $this->getSubManagers() as $submanager ) { |
||
362 | $submanager->optimize(); |
||
363 | } |
||
364 | |||
365 | return $this; |
||
366 | } |
||
367 | |||
368 | |||
369 | /** |
||
370 | * Searches for items matching the given criteria. |
||
371 | * |
||
372 | * @param \Aimeos\MW\Criteria\Iface $search Search criteria |
||
373 | * @param string[] $ref List of domains to fetch list items and referenced items for |
||
374 | * @param int &$total Total number of items matched by the given criteria |
||
375 | * @param string $cfgPathSearch Configuration path to the search SQL statement |
||
376 | * @param string $cfgPathCount Configuration path to the count SQL statement |
||
377 | * @return \Aimeos\MShop\Product\Item\Iface[] List of product items |
||
378 | */ |
||
379 | protected function searchItemsIndexBase( \Aimeos\MW\Criteria\Iface $search, |
||
380 | array $ref, int &$total = null, string $cfgPathSearch, string $cfgPathCount ) : \Aimeos\Map |
||
381 | { |
||
382 | $list = $ids = []; |
||
383 | $context = $this->getContext(); |
||
384 | |||
385 | $dbm = $context->getDatabaseManager(); |
||
386 | $dbname = $this->getResourceName(); |
||
387 | $conn = $dbm->acquire( $dbname ); |
||
388 | |||
389 | try |
||
390 | { |
||
391 | $required = array( 'product' ); |
||
392 | |||
393 | /** mshop/index/manager/sitemode |
||
394 | * Mode how items from levels below or above in the site tree are handled |
||
395 | * |
||
396 | * By default, only items from the current site are fetched from the |
||
397 | * storage. If the ai-sites extension is installed, you can create a |
||
398 | * tree of sites. Then, this setting allows you to define for the |
||
399 | * whole index domain if items from parent sites are inherited, |
||
400 | * sites from child sites are aggregated or both. |
||
401 | * |
||
402 | * Available constants for the site mode are: |
||
403 | * * 0 = only items from the current site |
||
404 | * * 1 = inherit items from parent sites |
||
405 | * * 2 = aggregate items from child sites |
||
406 | * * 3 = inherit and aggregate items at the same time |
||
407 | * |
||
408 | * You also need to set the mode in the locale manager |
||
409 | * (mshop/locale/manager/standard/sitelevel) to one of the constants. |
||
410 | * If you set it to the same value, it will work as described but you |
||
411 | * can also use different modes. For example, if inheritance and |
||
412 | * aggregation is configured the locale manager but only inheritance |
||
413 | * in the domain manager because aggregating items makes no sense in |
||
414 | * this domain, then items wil be only inherited. Thus, you have full |
||
415 | * control over inheritance and aggregation in each domain. |
||
416 | * |
||
417 | * @param int Constant from Aimeos\MShop\Locale\Manager\Base class |
||
418 | * @category Developer |
||
419 | * @since 2018.01 |
||
420 | * @see mshop/locale/manager/standard/sitelevel |
||
421 | */ |
||
422 | $level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL; |
||
423 | $level = $context->getConfig()->get( 'mshop/index/manager/sitemode', $level ); |
||
424 | |||
425 | $results = $this->searchItemsBase( $conn, $search, $cfgPathSearch, $cfgPathCount, $required, $total, $level ); |
||
426 | |||
427 | while( ( $row = $results->fetch() ) !== null ) { |
||
428 | $ids[] = $row['id']; |
||
429 | } |
||
430 | |||
431 | $dbm->release( $conn, $dbname ); |
||
432 | } |
||
433 | catch( \Exception $e ) |
||
434 | { |
||
435 | $dbm->release( $conn, $dbname ); |
||
436 | throw $e; |
||
437 | } |
||
438 | |||
439 | $manager = \Aimeos\MShop::create( $context, 'product' ); |
||
440 | $prodSearch = $manager->createSearch(); |
||
441 | $prodSearch->setConditions( $prodSearch->compare( '==', 'product.id', $ids ) ); |
||
442 | $prodSearch->setSlice( 0, $search->getSliceSize() ); |
||
443 | $items = $manager->searchItems( $prodSearch, $ref ); |
||
444 | |||
445 | foreach( $ids as $id ) |
||
446 | { |
||
447 | if( isset( $items[$id] ) ) { |
||
448 | $list[$id] = $items[$id]; |
||
449 | } |
||
450 | } |
||
451 | |||
452 | return map( $list ); |
||
453 | } |
||
454 | |||
455 | |||
456 | /** |
||
457 | * Returns the sub-manager instances available for the manager |
||
458 | * |
||
459 | * @return array Associative list of the sub-domain as key and the manager object as value |
||
460 | */ |
||
461 | abstract protected function getSubManagers() : array; |
||
462 | } |
||
463 |
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.