Passed
Push — master ( bada6f...d61b94 )
by Aimeos
04:42
created

CatalogAddPerfData::save()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
nc 1
nop 2
dl 0
loc 9
c 0
b 0
f 0
cc 1
rs 10
1
<?php
2
3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2015-2021
6
 */
7
8
9
namespace Aimeos\Upscheme\Task;
10
11
12
/**
13
 * Adds catalog and product performance records
14
 */
15
class CatalogAddPerfData extends Base
16
{
17
	private $maxBatch;
18
	private $numCatLevels;
19
	private $numCategories;
20
	private $numCatProducts;
21
	private $numProdVariants;
22
	private $attributes = [];
23
24
25
	/**
26
	 * Returns the list of task names which this task depends on.
27
	 *
28
	 * @return string[] List of task names
29
	 */
30
	public function after() : array
31
	{
32
		return ['MShopAddCodeDataUnitperf', 'AttributeAddPerfData', 'MShopSetLocale', 'Catalog', 'Media', 'Price', 'Product', 'Stock', 'Text'];
33
	}
34
35
36
	/**
37
	 * Returns the list of task names which depends on this task.
38
	 *
39
	 * @return string[] List of task names
40
	 */
41
	public function before() : array
42
	{
43
		return ['IndexRebuildPerf'];
44
	}
45
46
47
	/**
48
	 * Insert catalog nodes and product/catalog relations.
49
	 */
50
	public function up()
51
	{
52
		$this->info( 'Adding catalog performance data', 'v' );
53
54
		$treeFcn = function( array $parents, $catParentId, $numCatPerLevel, $level, $catLabel, $catIdx ) use ( &$treeFcn ) {
55
56
			$catItem = $this->addCatalogItem( $catParentId, $catLabel, $catIdx );
57
			array_unshift( $parents, $catItem );
58
59
			if( $level > 0 )
60
			{
61
				for( $i = 0; $i < $numCatPerLevel; $i++ ) {
62
					$treeFcn( $parents, $catItem->getId(), $numCatPerLevel, $level - 1, $catLabel . '-' . ( $i + 1 ), $i );
63
				}
64
			}
65
			else
66
			{
67
				$fcn = function( array $parents, $catLabel ) {
68
69
					srand( getmypid() ); mt_srand( getmypid() );
70
					$this->addProductItems( $parents, $catLabel );
71
72
					foreach( $parents as $catItem ) {
73
						$this->save( 'catalog', $catItem );
74
					}
75
				};
76
77
//				$this->context()->getProcess()->start( $fcn, [$parents, $catLabel] );
78
				$fcn( $parents, $catLabel );
79
			}
80
		};
81
82
83
		$this->init();
84
85
		$config = $this->context()->getConfig();
0 ignored issues
show
Bug introduced by
The method context() does not exist on Aimeos\Upscheme\Task\CatalogAddPerfData. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

85
		$config = $this->/** @scrutinizer ignore-call */ context()->getConfig();
Loading history...
86
		$treeidx = $config->get( 'setup/unitperf/treeindex' );
87
		$this->maxBatch = $config->get( 'setup/unitperf/max-batch', 10000 );
88
		$this->numCatLevels = $config->get( 'setup/unitperf/num-catlevels', 1 );
89
		$this->numCategories = $config->get( 'setup/unitperf/num-categories', 10 );
90
		$this->numCatProducts = $config->get( 'setup/unitperf/num-catproducts', 1000 );
91
		$this->numProdVariants = $config->get( 'setup/unitperf/num-prodvariants', 100 );
92
93
		$catRootItem = $this->addCatalogItem( null, 'home', 0 );
94
		$end = $numCatPerLevel = round( pow( $this->numCategories, 1 / $this->numCatLevels ) );
95
		$begin = 0;
96
97
		if( $treeidx !== null ) {
98
			$begin = $treeidx; $end = $treeidx + 1;
99
		}
100
101
		for( $i = $begin; $i < $end; $i++ ) {
102
			$treeFcn( [$catRootItem], $catRootItem->getId(), $numCatPerLevel, $this->numCatLevels - 1, $i + 1, $i );
103
		}
104
105
		$this->context()->getProcess()->wait();
106
	}
107
108
109
	protected function addCatalogItem( $parentId, $catLabel, $catIdx )
110
	{
111
		$catalogManager = \Aimeos\MShop::create( $this->context(), 'catalog' );
112
113
		$item = $catalogManager->create()
114
			->setLabel( 'category-' . $catLabel )
115
			->setCode( 'cat-' . $catLabel )
116
			->setStatus( 1 );
117
		$item->pos = $catIdx;
118
119
		$item = $this->addCatalogTexts( $item, $catLabel );
120
121
		while( true )
122
		{
123
			try {
124
				return $catalogManager->insert( $item, $parentId );
125
			} catch( \Aimeos\MW\DB\Exception $e ) {
126
				if( $e->getCode() !== 40001 ) { throw $e; } // transaction deadlock
127
			}
128
		}
129
	}
130
131
132
	protected function addCatalogProducts( array $catItems, array $items, $num )
133
	{
134
		$catalogListManager = \Aimeos\MShop::create( $this->context(), 'catalog/lists' );
135
		$defListItem = $catalogListManager->create()->setType( 'default' );
136
		$start = 0;
137
138
		foreach( $catItems as $idx => $catItem )
139
		{
140
			$catItem = clone $catItem; // forget stored product references afterwards
141
			$fraction = pow( 10, $idx );
142
143
			foreach( $items as $item )
144
			{
145
				if( $item->pos % $fraction === 0 )
146
				{
147
					$litem = ( clone $defListItem )->setRefId( $item->getId() )->setPosition( $start + round( $item->pos / $fraction ) );
148
					$catItem->addListItem( 'product', $litem );
149
				}
150
			}
151
152
			$start += $num * $catItem->pos * round( count( $items ) / pow( 10, $idx + 1 ) );
153
			$this->save( 'catalog', $catItem );
154
		}
155
	}
156
157
158
	protected function addCatalogTexts( \Aimeos\MShop\Catalog\Item\Iface $catItem, $catLabel )
159
	{
160
		$textManager = \Aimeos\MShop::create( $this->context(), 'text' );
161
		$catalogListManager = \Aimeos\MShop::create( $this->context(), 'catalog/lists' );
162
163
		$textItem = $textManager->create()
164
			->setContent( 'Category ' . $catLabel )
165
			->setLabel( 'cat-' . $catLabel )
166
			->setLanguageId( 'en' )
167
			->setType( 'name' )
168
			->setStatus( 1 );
169
170
		$listItem = $catalogListManager->create()->setType( 'default' );
171
172
		return $catItem->addListItem( 'text', $listItem, $textItem );
173
	}
174
175
176
	protected function addProductAttributes( \Aimeos\MShop\Product\Item\Iface $prodItem, array $attrIds )
177
	{
178
		$productListManager = \Aimeos\MShop::create( $this->context(), 'product/lists' );
179
180
		$listItem = $productListManager->create()->setType( 'default' );
181
182
		foreach( $attrIds as $attrId ) {
183
			$prodItem->addListItem( 'attribute', ( clone $listItem )->setRefId( $attrId ) );
184
		}
185
186
		$listItem = $productListManager->create()->setType( 'config' );
187
188
		foreach( $this->attributes['sticker'] as $attrId => $label ) {
189
			$prodItem->addListItem( 'attribute', ( clone $listItem )->setRefId( $attrId ) );
190
		}
191
192
		return $prodItem;
193
	}
194
195
196
	protected function addProductItems( array $catItems, $catLabel )
197
	{
198
		$articles = $this->shuffle( [
199
			'shirt', 'skirt', 'jacket', 'pants', 'socks', 'blouse', 'slip', 'sweater', 'dress', 'top',
200
			'anorak', 'babydoll', 'swimsuit', 'trunks', 'bathrobe', 'beret', 'bra', 'bikini', 'blazer', 'bodysuit',
201
			'bolero', 'bowler', 'trousers', 'bustier', 'cape', 'catsuit', 'chucks', 'corduroys', 'corsage', 'cutaway',
202
			'lingerie', 'tricorn', 'bow tie', 'tails', 'leggings', 'galoshes', 'string', 'belt', 'hotpants', 'hat',
203
			'jumpsuit', 'jumper', 'caftan', 'hood', 'kimono', 'headscarf', 'scarf', 'corset', 'costume', 'tie',
204
			'cummerbund', 'robe', 'underpants', 'dungarees', 'undershirt', 'camisole', 'mantle', 'bodice', 'topless', 'moonboots',
205
			'cap', 'nightie', 'negligee', 'overalls', 'parka', 'poncho', 'bloomers', 'pumps', 'pajamas', 'farthingale',
206
			'sari', 'veil', 'apron', 'swimsuit', 'shorts', 'tuxedo', 'stocking', 'suspender', 'tanga', 'tankini',
207
			'toga', 'tunic', 'turban', 'jerkin', 'coat', 'suit', 'vest', 'gloves', 'bag', 'briefcase',
208
			'shoes', 'sandals', 'flip-flops', 'ballerinas', 'slingbacks', 'clogs', 'moccasins', 'sneakers', 'boots', 'slippers',
209
		] );
210
211
		$productManager = \Aimeos\MShop::create( $this->context(), 'product' );
212
		$productManager->begin();
213
214
		$newItem = $productManager->create()->setType( $this->numProdVariants > 0 ? 'select' : 'default' );
215
		$slice = (int) ceil( $this->maxBatch / ( $this->numProdVariants ?: 1 ) );
216
217
		$property = $this->shuffle( $this->attributes['property'] );
218
		$material = $this->shuffle( $this->attributes['material'] );
219
		$color = $this->shuffle( $this->attributes['color'] );
220
221
		$items = [];
222
		$num = 1;
223
224
		for( $i = 1; $i <= $this->numCatProducts; $i++ )
225
		{
226
			$text = current( $color ) . ' ' . current( $property ) . ' ' . current( $material ) . ' ' . current( $articles );
227
228
			$item = ( clone $newItem )
229
				->setLabel( $text . ' (' . $catLabel . ')' )
230
				->setCode( 'p-' . $i . ':' . $catLabel )
231
				->setStatus( 1 );
232
233
			$item = $this->addProductAttributes( $item, [key( $color ), key( $property ), key( $material )] );
234
			$item = $this->addProductTexts( $item, $text, $catLabel );
235
			$item = $this->addProductMedia( $item, $i );
236
			$item = $this->addProductPrices( $item, $i );
237
			$item = $this->addProductVariants( $item, $i );
238
			$item = $this->addProductSuggestions( $item, $catItems );
239
240
			$item->pos = $i - 1; // 0 based category position
0 ignored issues
show
Bug Best Practice introduced by
The property pos does not exist on Aimeos\MShop\Product\Item\Iface. Since you implemented __set, consider adding a @property annotation.
Loading history...
241
			$items[] = $item;
242
243
			next( $color );
244
			if( current( $color ) === false )
245
			{
246
				reset( $color ); next( $property );
247
248
				if( current( $property ) === false )
249
				{
250
					reset( $property ); next( $material );
251
252
					if( current( $material ) === false )
253
					{
254
						reset( $material ); next( $articles );
255
256
						if( current( $articles ) === false ) {
257
							reset( $articles );
258
						}
259
					}
260
				}
261
			}
262
263
			if( $i % $slice === 0 )
264
			{
265
				$productManager->save( $items );
266
				$this->addCatalogProducts( $catItems, $items, $num++ );
267
				$this->addStock( $items );
268
				$items = [];
269
			}
270
		}
271
272
		$productManager->save( $items );
273
		$this->addCatalogProducts( $catItems, $items, $num++ );
274
		$this->addStock( $items );
275
276
		$productManager->commit();
277
	}
278
279
280
	protected function addProductMedia( \Aimeos\MShop\Product\Item\Iface $prodItem, $idx )
281
	{
282
		$prefix = 'https://aimeos.org/media/';
283
284
		$mediaManager = \Aimeos\MShop::create( $this->context(), 'media' );
285
		$productListManager = \Aimeos\MShop::create( $this->context(), 'product/lists' );
286
287
		$litem = $productListManager->create()->setType( 'default' );
288
		$newItem = $mediaManager->create()->setType( 'default' );
289
290
		foreach( array_values( $this->shuffle( range( 0, 3 ) ) ) as $pos => $i )
291
		{
292
			$num = ( ( $idx + $i ) % 4 ) + 1;
293
			$mediaItem = ( clone $newItem )
294
				->setLabel( ( $pos + 1 ) . '. picture for ' . $prodItem->getLabel() )
295
				->setPreviews( [1 => $prefix . 'unitperf/' . $num . '.jpg'] )
296
				->setUrl( $prefix . 'unitperf/' . $num . '-big.jpg' )
297
				->setMimeType( 'image/jpeg' )
298
				->setStatus( 1 );
299
300
			$prodItem->addListItem( 'media', ( clone $litem )->setPosition( $pos ), $mediaItem );
301
		}
302
303
		$mediaItem = ( clone $newItem )
304
			->setPreviews( [1 => $prefix . 'unitperf/download-preview.jpg'] )
305
			->setUrl( $prefix . 'unitperf/download.pdf' )
306
			->setMimeType( 'application/pdf' )
307
			->setLabel( 'PDF download' )
308
			->setStatus( 1 );
309
310
		$litem = $productListManager->create()->setType( 'download' );
311
312
		return $prodItem->addListItem( 'media', ( clone $litem ), $mediaItem );
313
	}
314
315
316
	protected function addProductPrices( \Aimeos\MShop\Product\Item\Iface $prodItem, $idx )
317
	{
318
		$priceManager = \Aimeos\MShop::create( $this->context(), 'price' );
319
		$productListManager = \Aimeos\MShop::create( $this->context(), 'product/lists' );
320
321
		$litem = $productListManager->create()->setType( 'default' );
322
		$newItem = $priceManager->create()->setType( 'default' );
323
		$base = rand( 0, 896 );
324
325
		for( $i = 0; $i < 3; $i++ )
326
		{
327
			$priceItem = ( clone $newItem )
328
				->setLabel( $prodItem->getLabel() . ': from ' . ( 1 + $i * 5 ) )
329
				->setValue( 100 + ( ( $base + $idx ) % 900 ) - $i * 10 )
330
				->setQuantity( 1 + $i * 10 )
331
				->setCurrencyId( 'EUR' )
332
				->setRebate( $i * 10 )
333
				->setStatus( 1 );
334
335
			$prodItem->addListItem( 'price', ( clone $litem )->setPosition( $i ), $priceItem );
336
		}
337
338
		return $prodItem;
339
	}
340
341
342
	protected function addProductSuggestions( \Aimeos\MShop\Product\Item\Iface $prodItem, array $catItems )
343
	{
344
		if( ( $catItem = reset( $catItems ) ) !== false )
345
		{
346
			$productListManager = \Aimeos\MShop::create( $this->context(), 'product/lists' );
347
348
			$listItem = $productListManager->create()->setType( 'suggestion' );
349
			$listItems = $catItem->getListItems( 'product' );
350
			$ids = []; $num = 5;
351
352
			while( ( $litem = $listItems->pop() ) !== null && $num > 0 )
353
			{
354
				if( !in_array( $litem->getRefId(), $ids ) )
355
				{
356
					$prodItem->addListItem( 'product', ( clone $listItem )->setRefId( $litem->getRefId() ) );
357
					$ids[] = $litem->getRefId();
358
					$num--;
359
				}
360
			}
361
		}
362
363
		return $prodItem;
364
	}
365
366
367
	protected function addProductTexts( \Aimeos\MShop\Product\Item\Iface $prodItem, $label, $catLabel )
368
	{
369
		$textManager = \Aimeos\MShop::create( $this->context(), 'text' );
370
		$productListManager = \Aimeos\MShop::create( $this->context(), 'product/lists' );
371
372
		$listItem = $productListManager->create()->setType( 'default' );
373
374
		$textItem = $textManager->create()
375
			->setContent( str_replace( ' ', '_', $label . '_' . $catLabel ) )
376
			->setLabel( $label . '(' . $catLabel . ')' )
377
			->setLanguageId( 'en' )
378
			->setType( 'url' )
379
			->setStatus( 1 );
380
381
		$prodItem->addListItem( 'text', ( clone $listItem ), $textItem );
382
383
		$textItem = $textManager->create()
384
			->setLanguageId( 'en' )
385
			->setContent( $label )
386
			->setLabel( $label )
387
			->setType( 'name' )
388
			->setStatus( 1 );
389
390
		$prodItem->addListItem( 'text', ( clone $listItem )->setPosition( 0 ), $textItem );
391
392
		$textItem = $textManager->create()
393
			->setContent( 'Short description for ' . $label )
394
			->setLabel( $label . ' (short)' )
395
			->setLanguageId( 'en' )
396
			->setType( 'short' )
397
			->setStatus( 1 );
398
399
		$prodItem->addListItem( 'text', ( clone $listItem )->setPosition( 1 ), $textItem );
400
401
		$textItem = $textManager->create()
402
			->setContent( 'Long description for ' . $label . '. This may include some "lorem ipsum" text' )
403
			->setLabel( $label . ' (long)' )
404
			->setLanguageId( 'en' )
405
			->setType( 'long' )
406
			->setStatus( 1 );
407
408
		$prodItem->addListItem( 'text', ( clone $listItem )->setPosition( 2 ), $textItem );
409
410
		return $prodItem;
411
	}
412
413
414
	protected function addProductVariants( \Aimeos\MShop\Product\Item\Iface $prodItem, $idx )
415
	{
416
		$productManager = \Aimeos\MShop::create( $this->context(), 'product' );
417
		$productListManager = \Aimeos\MShop::create( $this->context(), 'product/lists' );
418
419
		$defListItem = $productListManager->create()->setType( 'default' );
420
		$varListItem = $productListManager->create()->setType( 'variant' );
421
		$newItem = $productManager->create()->setType( 'default' );
422
423
		$length = $this->attributes['length'];
424
		$width = $this->attributes['width'];
425
		$size = $this->attributes['size'];
426
427
		for( $i = 0; $i < $this->numProdVariants; $i++ )
428
		{
429
			$text = current( $length ) . ', ' . current( $width ) . ', ' . $prodItem->getLabel() . ' (' . current( $size ) . ')';
430
431
			$item = ( clone $newItem )
432
				->setCode( 'v-' . $idx . '/' . $i . ':' . $prodItem->getCode() )
433
				->setLabel( $text )
434
				->setStatus( 1 );
435
436
			$item->addListItem( 'attribute', ( clone $varListItem )->setRefId( key( $length ) ) );
437
			$item->addListItem( 'attribute', ( clone $varListItem )->setRefId( key( $width ) ) );
438
			$item->addListItem( 'attribute', ( clone $varListItem )->setRefId( key( $size ) ) );
439
440
			$prodItem->addListItem( 'product', clone $defListItem, $item );
441
442
			next( $width );
443
			if( current( $width ) === false )
444
			{
445
				reset( $width ); next( $length );
446
447
				if( current( $length ) === false )
448
				{
449
					reset( $length ); next( $size );
450
451
					if( current( $size ) === false ) {
452
						reset( $size );
453
					}
454
				}
455
			}
456
		}
457
458
		return $prodItem;
459
	}
460
461
462
	public function addStock( array $items )
463
	{
464
		$stockManager = \Aimeos\MShop::create( $this->context(), 'stock' );
465
466
		$stockItem = $stockManager->create()->setType( 'default' );
467
		$stocklevels = $this->shuffle( [null, 100, 80, 60, 40, 20, 10, 5, 2, 0] );
468
		$list = [];
469
470
		foreach( $items as $item )
471
		{
472
			foreach( $item->getRefItems( 'product', 'default', 'default' ) as $refItem )
473
			{
474
				$sitem = clone $stockItem;
475
				$sitem->setProductId( $refItem->getId() );
476
				$sitem->setStockLevel( current( $stocklevels ) );
477
478
				if( next( $stocklevels ) === false ) {
479
					reset( $stocklevels );
480
				}
481
482
				$list[] = $sitem;
483
			}
484
485
			$sitem = clone $stockItem;
486
			$list[] = $sitem->setProductId( $item->getId() );
487
		}
488
489
		$stockManager->begin();
490
		$stockManager->save( $list, false );
491
		$stockManager->commit();
492
	}
493
494
495
	protected function init()
496
	{
497
		$manager = \Aimeos\MShop::create( $this->context(), 'attribute' );
498
499
		$search = $manager->filter()->slice( 0, 0x7fffffff );
500
		$search->setSortations( [$search->sort( '+', 'attribute.position' )] );
501
502
		foreach( $manager->search( $search ) as $id => $item ) {
503
			$this->attributes[$item->getType()][$id] = $item->getLabel();
504
		}
505
	}
506
507
508
	protected function save( $domain, $item )
509
	{
510
		$manager = \Aimeos\MShop::create( $this->context(), $domain );
511
512
		$manager->begin();
513
		$item = $manager->save( $item );
514
		$manager->commit();
515
516
		return $item;
517
	}
518
519
520
	protected function shuffle( array $list )
521
	{
522
		$keys = array_keys( $list );
523
		shuffle( $keys );
524
		$result = [];
525
526
		foreach( $keys as $key ) {
527
			$result[$key] = $list[$key];
528
		}
529
530
		return $result;
531
	}
532
}
533