OptionsHelper.php 9.86 KB
<?php
    
    namespace artbox\catalog\filter;
    
    use artbox\core\models\Language;
    use yii\base\BaseObject;
    use yii\db\Exception;
    use yii\db\Query;
    
    class OptionsHelper extends BaseObject
    {
        protected $newVariantOptions = [];
        
        protected $newProductOptions;
        
        protected $toInsert = [];
        
        protected $data = [];
        
        protected $bitMap = [];
        
        protected $optionVariants = [];
        
        public function loadOptions(int $productId)
        {
            $query = ( new Query() )->select(
                'variant.id AS variant_id, o2.id AS option_id, (g.title || \' : \' || o3.title) AS title'
            )
                                    ->from('variant')
                                    ->leftJoin('option_to_variant o', 'variant.id = o.variant_id')
                                    ->leftJoin('option o2', 'o.option_id = o2.id')
                                    ->leftJoin('option_lang o3', 'o2.id = o3.option_id')
                                    ->leftJoin("group", 'o2.group_id = "group".id')
                                    ->leftJoin('group_lang g', '"group".id = g.group_id')
                                    ->where(
                                        [
                                            'g.language_id' => Language::getCurrent()->id,
                                        ]
                                    )
                                    ->andWhere(
                                        [
                                            'o3.language_id' => Language::getCurrent()->id,
                                        ]
                                    )
                                    ->andWhere(
                                        [
                                            'product_id' => $productId,
                                        ]
                                    );
            
            $result = $query->all();
            
            foreach ($result as $value) {
                if (!isset($this->data[ $value[ 'option_id' ] ])) {
                    $this->data[ $value[ 'option_id' ] ] = $value[ 'title' ];
                }
                
                if (!isset($this->bitMap[ $value[ 'variant_id' ] ])) {
                    $this->bitMap[ $value[ 'variant_id' ] ] = count($this->bitMap);
                }
                
                if (isset($this->optionVariants[ $value[ 'option_id' ] ])) {
                    $this->optionVariants[ $value[ 'option_id' ] ] += ( 2 ** $this->bitMap[ $value[ 'variant_id' ] ] );
                } else {
                    $this->optionVariants[ $value[ 'option_id' ] ] = ( 2 ** $this->bitMap[ $value[ 'variant_id' ] ] );
                }
            }
        }
        
        public function getData(): array
        {
            return $this->data;
        }
        
        public function setProductOptions($options)
        {
            if (empty($options)) {
                $this->newProductOptions = [];
            } else {
                $this->newProductOptions = $options;
            }
        }
        
        public function setVariantOptions($variantId, $options)
        {
            if (empty($options)) {
                $this->newVariantOptions[ $variantId ] = [];
            } else {
                $this->newVariantOptions[ $variantId ] = $options;
            }
        }
        
        public function save()
        {
            $this->insertData($this->prepareData());
            
            $this->updateVariantOptions();
        }
        
        public function getProductOptions(): array
        {
            $allVariantsMask = ( 2 ** count($this->bitMap) ) - 1;
            $result = [];
            foreach ($this->optionVariants as $option_id => $bitmask) {
                if ($bitmask === $allVariantsMask) {
                    $result[] = $option_id;
                }
            }
            
            return $result;
        }
        
        public function getVariantOptions(int $variantId): array
        {
            if (empty($this->bitMap) or !isset($this->bitMap[ $variantId ])) {
                return [];
            }
            $allVariantsMask = ( 2 ** count($this->bitMap) ) - 1;
            
            $variantBit = 2 ** ( $this->bitMap[ $variantId ] );
            
            $result = [];
            
            foreach ($this->optionVariants as $option_id => $bitMask) {
                if (( $bitMask !== $allVariantsMask ) && ( ( $bitMask & $variantBit ) !== 0 )) {
                    $result[] = $option_id;
                }
            }
            
            return $result;
        }
        
        protected function insertData(array $data): bool
        {
            $transaction = \Yii::$app->db->beginTransaction();
            try {
                \Yii::$app->db->createCommand()
                              ->delete(
                                  'option_to_variant',
                                  [
                                      'variant_id' => array_keys($this->newVariantOptions),
                                  ]
                              )
                              ->execute();
                
                \Yii::$app->db->createCommand()
                              ->batchInsert(
                                  'option_to_variant',
                                  [
                                      'variant_id',
                                      'option_id',
                                  ],
                                  $data
                              )
                              ->execute();
                $transaction->commit();
                
                return true;
            } catch (\Exception $e) {
                $transaction->rollBack();
                throw $e;
            } catch (\Throwable $e) {
                $transaction->rollBack();
                throw $e;
            }
        }
        
        protected function prepareData(): array
        {
            $variantToOptionInsert = [];
            
            foreach ($this->newVariantOptions as $variant_id => $options) {
                foreach ($options as $option) {
                    $variantToOptionInsert[] = [
                        $variant_id,
                        $option,
                    ];
                }
                
                foreach ($this->newProductOptions as $option) {
                    $variantToOptionInsert[] = [
                        $variant_id,
                        $option,
                    ];
                }
            }
            
            return $variantToOptionInsert;
        }
        
        protected function updateVariantOptions()
        {
            $query = ( new Query() )->select('*')
                                    ->from('option_to_variant')
                                    ->leftJoin('option o', 'option_to_variant.option_id = o.id')
                                    ->where(
                                        [
                                            'variant_id' => array_keys($this->newVariantOptions),
                                        ]
                                    )
                                    ->orderBy('variant_id');
            
            $rows = $query->all();
            $prev = null;
            $collection = [];
            foreach ($rows as $row) {
                $current = $row[ 'variant_id' ];
                if (( $prev !== null ) && ( $prev !== $current )) {
                    $this->flushCollection($collection, $prev);
                    $collection = [];
                }
                
                $collection[ $row[ 'group_id' ] ][] = $row[ 'id' ];
                
                $prev = $current;
            }
            $this->flushCollection($collection, $prev);
            
            $this->flushQuery();
        }
        
        protected function flushCollection(array $collection, $prev)
        {
            $arrays = [];
            $first = true;
            foreach ($collection as $group) {
                if ($first) {
                    foreach ($group as $option) {
                        $arrays[] = [ $option ];
                    }
                    $first = false;
                } else {
                    $newArrays = [];
                    
                    foreach ($arrays as $a) {
                        foreach ($group as $b) {
                            $newChunk = array_merge($a, [ $b ]);
                            $newArrays[] = $newChunk;
                        }
                    }
                    $arrays = $newArrays;
                }
                
            }
            
            foreach ($arrays as $array) {
                $this->toInsert[] = [
                    $prev,
                    'array[' . implode(',', $array) . ']',
                ];
            }
        }
        
        protected function flushQuery()
        {
            $values = [];
            foreach ($this->toInsert as $value) {
                $values[] = '(' . implode(',', $value) . ')';
            }
            $insertString = implode(',', $values);
            try {
                \Yii::$app->db->createCommand()
                              ->delete(
                                  'variant_options',
                                  [
                                      'variant_id' => array_keys($this->newVariantOptions),
                                  ]
                              )
                              ->execute();
                $command = \Yii::$app->db->createCommand(
                    'INSERT INTO "variant_options" ("variant_id", "options") VALUES ' . $insertString
                );
                $command->execute();
            } catch (Exception $exception) {
                \Yii::error('Variant options query failed');
            }
        }
    }