/**
  * @see shopProductStorageInterface::setData()
  * @param shopProduct $product current product object
  * @param array [string] mixed $data new product feature values
  */
 public function setData(shopProduct $product, $data)
 {
     $product_id = $product->getId();
     $feature_model = new shopFeatureModel();
     $codes = array_keys($data);
     $features = $feature_model->getByCode($codes);
     /**
      * composite fields workaround
      */
     $composite_codes = array();
     foreach ($data as $code => $value) {
         if (!preg_match('/\\.[0-3]$/', $code) && isset($features[$code]) && preg_match('/^([23])d\\./', $features[$code]['type'], $matches)) {
             $n = $matches[1];
             $pattern = '/^' . implode('\\s*[×xX\\*]?\\s*', array_fill(0, $n, '([^\\s]+)')) . '(\\s+.+)?$/u';
             if (preg_match($pattern, trim($value), $matches)) {
                 $unit = ifset($matches[$n + 1]);
                 for ($i = 0; $i < $n; $i++) {
                     $c_code = $code . '.' . $i;
                     $data[$c_code] = $matches[$i + 1] . $unit;
                     $composite_codes[] = $c_code;
                 }
                 unset($features[$code]);
             } else {
                 /**
                  * invalid complex feature format
                  */
             }
             unset($data[$code]);
         }
     }
     if ($composite_codes) {
         $features += $feature_model->getByCode($composite_codes);
     }
     $features_map = array();
     foreach ($features as $code => $f) {
         $features_map[$f['id']] =& $features[$code];
     }
     $current = array();
     $rows = $this->getByField(array('product_id' => $product_id, 'sku_id' => null), true);
     foreach ($rows as $row) {
         $id = $row['feature_id'];
         if (isset($features_map[$id])) {
             $f = $features_map[$id];
             $code = $f['code'];
             if (empty($f['multiple'])) {
                 $current[$code] = intval($row['feature_value_id']);
             } else {
                 if (!isset($current[$code])) {
                     $current[$code] = array();
                 }
                 $current[$code][] = intval($row['feature_value_id']);
             }
         } else {
             //obsolete data
         }
     }
     $add = $delete = array();
     foreach ($data as $code => $value) {
         if (isset($features[$code])) {
             $f =& $features[$code];
             if (is_array($value)) {
                 $empty = isset($value['value']) && $value['value'] === '';
                 if (!$empty && isset($value['value']) && is_array($value['value'])) {
                     foreach ($value['value'] as $key => $v) {
                         if ($v === '') {
                             unset($value['value'][$key]);
                         }
                     }
                     $empty = count($value['value']) == 0;
                 }
                 if (!$empty && !isset($value['value'])) {
                     foreach ($value as $key => $v) {
                         if ($v === '') {
                             unset($value[$key]);
                         }
                     }
                     $empty = count($value) == 0;
                 }
             } else {
                 $empty = $value === '';
             }
             if ($empty) {
                 //delete it
                 if (isset($current[$code])) {
                     $delete[$f['id']] = $current[$code];
                 }
             } else {
                 if (is_array($value) && preg_match('/^(.+\\.)[12]$/', $code, $matches) && isset($data[$matches[1] . '0'])) {
                     $value = array_merge($data[$matches[1] . '0'], $value);
                 }
                 $id = $feature_model->getValueId($f, $value, true);
                 if (isset($current[$code])) {
                     if (empty($f['multiple'])) {
                         if ($current[$code] != $id) {
                             $delete[$f['id']] = $current[$code];
                             $add[$f['id']] = $id;
                         }
                     } else {
                         $delete[$f['id']] = array_diff($current[$code], (array) $id);
                         if (empty($delete[$f['id']])) {
                             unset($delete[$f['id']]);
                         }
                         $add[$f['id']] = array_diff((array) $id, $current[$code]);
                         if (empty($add[$f['id']])) {
                             unset($add[$f['id']]);
                         }
                     }
                 } else {
                     $add[$f['id']] = $id;
                 }
             }
         } elseif (!empty($value) && is_array($value)) {
             //it's a new feature
             if (!empty($value) && (ifset($value['type']) == shopFeatureModel::TYPE_BOOLEAN || !empty($value['value']))) {
                 $f = array('name' => $value['name'], 'type' => $value['type'], 'types' => $value['types']);
                 $f['id'] = $feature_model->save($f);
                 $type_features_model = new shopTypeFeaturesModel();
                 $type_features_model->updateByFeature($f['id'], $f['types']);
                 if ($value['value'] !== '') {
                     $add[$f['id']] = $feature_model->getValueId($f, $value['value'], true);
                 }
             }
         }
     }
     foreach ($features as $code => $f) {
         if (empty($data[$code]) && !empty($current[$code])) {
             $delete[$f['id']] = $current[$code];
         }
     }
     foreach ($delete as $feature_id => $value_id) {
         $this->deleteByField(array('product_id' => $product_id, 'sku_id' => null, 'feature_id' => $feature_id, 'feature_value_id' => $value_id));
     }
     foreach ($add as $feature_id => $value_id) {
         $this->multipleInsert(array('product_id' => $product_id, 'feature_id' => $feature_id, 'feature_value_id' => $value_id));
     }
 }
 /**
  * Verify input data and get selected base features
  * @param array &$data
  * @return int[int][int] value_id[value_id][feature_id]
  */
 private function getSelectedData(&$data)
 {
     $selected = array();
     $features = array();
     if ($feature_codes = array_keys($data)) {
         $feature_model = new shopFeatureModel();
         $features = $feature_model->getByCode($feature_codes);
     }
     foreach ($data as $code => &$feature) {
         if (!isset($features[$code])) {
             unset($data[$code]);
         } else {
             $feature_id = intval($features[$code]['id']);
             $selected[$feature_id] = array();
             if (!isset($feature['values'])) {
                 $feature = array('values' => $feature);
             }
             $feature['feature_id'] = $feature_id;
             $values = $feature['values'];
             $feature['values'] = array();
             foreach ($values as $value) {
                 if (is_array($value)) {
                     if (isset($value['value']) && empty($value['id'])) {
                         $value['id'] = $feature_model->getValueId($features[$code], $value['value'], true);
                     }
                 } else {
                     $value = array('id' => $value);
                 }
                 $id = $value['id'];
                 if (!isset($value['value'])) {
                     $value['value'] = (string) $feature_model->getValuesModel($features[$code]['type'])->getFeatureValue($id);
                 }
                 $feature['values'][$id] = $value;
                 $selected[$feature_id][$id] = $id;
                 unset($value);
             }
         }
         unset($values);
     }
     ksort($selected, SORT_NUMERIC);
     return $selected;
 }