/** * Finds and returns list of attributes associated with selected product by it's ID. * * @param $productId int Product ID. * * @return array List of attributes attached to selected product. */ public function getAttributes($productId) { $wpdb = $this->wp->getWPDB(); $query = $wpdb->prepare("\n\t\tSELECT a.id, a.is_local, a.slug, a.label, a.type, pa.value,\n\t\t\tao.id AS option_id, ao.value AS option_value, ao.label as option_label,\n\t\t\tpam.id AS meta_id, pam.meta_key, pam.meta_value\n\t\tFROM {$wpdb->prefix}jigoshop_attribute a\n\t\t\tLEFT JOIN {$wpdb->prefix}jigoshop_attribute_option ao ON a.id = ao.attribute_id\n\t\t\tLEFT JOIN {$wpdb->prefix}jigoshop_product_attribute pa ON pa.attribute_id = a.id\n\t\t\tLEFT JOIN {$wpdb->prefix}jigoshop_product_attribute_meta pam ON pa.attribute_id = pam.attribute_id AND pa.product_id = pam.product_id\n\t\t\tWHERE pa.product_id = %d\n\t\t", array($productId)); $results = $wpdb->get_results($query, ARRAY_A); $attributes = array(); for ($i = 0, $endI = count($results); $i < $endI;) { $attribute = $this->createAttribute($results[$i]['type'], Attribute::PRODUCT_ATTRIBUTE_EXISTS); $attribute->setId((int) $results[$i]['id']); $attribute->setSlug($results[$i]['slug']); $attribute->setLabel($results[$i]['label']); $attribute->setLocal((bool) $results[$i]['is_local']); $attribute->setValue($results[$i]['value']); $fields = array(); while ($i < $endI && $results[$i]['id'] == $attribute->getId()) { $option = new Attribute\Option(); if ($results[$i]['option_id'] !== null) { $option->setId($results[$i]['option_id']); $option->setLabel($results[$i]['option_label']); $option->setValue($results[$i]['option_value']); $attribute->addOption($option); } while ($i < $endI && $results[$i]['id'] == $attribute->getId() && $results[$i]['option_id'] == $option->getId()) { if ($results[$i]['meta_id'] !== null && !isset($fields[$results[$i]['meta_key']])) { $field = new Attribute\Field(); $field->setId($results[$i]['meta_id']); $field->setKey($results[$i]['meta_key']); $field->setValue($results[$i]['meta_value']); $field->setAttribute($attribute); $fields[$results[$i]['meta_key']] = $field; } $i++; } } $attribute->restoreFields($fields); $attributes[$attribute->getId()] = $attribute; } return $attributes; }
/** * Migrates data from old format to new one. * @param array $products * @return bool migration product status: success or not */ public function migrate($products) { $wpdb = $this->wp->getWPDB(); // Open transaction for save migration products $var_autocommit_sql = $wpdb->get_var("SELECT @@AUTOCOMMIT"); try { $this->checkSql(); $wpdb->query("SET AUTOCOMMIT=0"); $this->checkSql(); $wpdb->query("START TRANSACTION"); $this->checkSql(); // Register product type taxonomy to fetch old product types $this->wp->registerTaxonomy('product_type', array('product'), array('hierarchical' => false, 'show_ui' => false, 'query_var' => true, 'show_in_nav_menus' => false)); $this->checkSql(); if ($this->wp->getOption('jigoshop_migration_product_first', false) == false) { // Update product_cat into product_category $wpdb->query($wpdb->prepare("UPDATE {$wpdb->term_taxonomy} SET taxonomy = %s WHERE taxonomy = %s", array('product_category', 'product_cat'))); $this->checkSql(); $wpdb->query($wpdb->prepare("UPDATE {$wpdb->postmeta} SET meta_value = %s WHERE meta_key = %s AND meta_value = %s", array('product_category', '_menu_item_object', 'product_cat'))); $this->checkSql(); foreach ($wpdb->get_results("SELECT * FROM {$wpdb->prefix}jigoshop_termmeta", ARRAY_A) as $termMeta) { $wpdb->insert($wpdb->prefix . 'jigoshop_term_meta', $termMeta); } $this->wp->updateOption('jigoshop_migration_product_first', true); } $productIds = array(); $attributes = array(); $productAttributes = array(); $globalAttributes = array(); foreach ($wpdb->get_results("SELECT * FROM {$wpdb->prefix}jigoshop_attribute_taxonomies") as $attribute) { $this->checkSql(); $globalAttributes[$this->wp->getHelpers()->sanitizeTitle($attribute->attribute_name)] = $attribute; } foreach ($wpdb->get_results("SELECT id AS attribute_id, slug AS attribute_name, label AS attribute_label, type AS attribute_type FROM {$wpdb->prefix}jigoshop_attribute") as $attribute) { $this->checkSql(); $globalAttributes[$attribute->attribute_name] = $attribute; } for ($i = 0, $endI = count($products); $i < $endI;) { $product = $products[$i]; $productIds[] = $product->ID; $productAttributes[$product->ID] = array('attributes' => array(), 'variations' => array()); // Add product types $types = $this->wp->getTheTerms($product->ID, 'product_type'); $this->checkSql(); $productType = Product\Simple::TYPE; if (is_array($types)) { if (!in_array($types[0]->slug, array(Product\Simple::TYPE, Product\Virtual::TYPE, Product\Downloadable::TYPE, Product\External::TYPE, Product\Variable::TYPE))) { Migration::saveLog(sprintf(__('We detected a product <a href="%s" target="_blank">(#%d) %s </a> of type "subscription" - this type is not supported by Jigoshop without an additional plugin. We changed its type to "simple" and set it as private.', 'jigoshop'), get_permalink($product->ID), $product->ID, get_the_title($product->ID), $types[0]->slug)); $wpdb->query($wpdb->prepare("UPDATE {$wpdb->posts} SET post_status = 'private' WHERE ID = %d", $product->ID)); $types[0]->slug = 'simple'; } $productType = $types[0]->slug; $wpdb->query($wpdb->prepare("INSERT INTO {$wpdb->postmeta} VALUES (NULL, %d, %s, %s)", array($product->ID, 'type', $types[0]->slug))); $this->checkSql(); } $regularPrice = 0.0; $taxClasses = array(); // Update columns do { // Sales support if ($products[$i]->meta_key == 'sale_price' && !empty($products[$i]->meta_value)) { $wpdb->query($wpdb->prepare("INSERT INTO {$wpdb->postmeta} (post_id, meta_key, meta_value) VALUES (%d, %s, %s)", array($product->ID, 'sales_enabled', true))); $this->checkSql(); } // Product attributes support if ($products[$i]->meta_key == 'product_attributes') { $attributeData = unserialize($products[$i]->meta_value); if (is_array($attributeData)) { foreach ($attributeData as $slug => $source) { if (empty($source['value'])) { continue; } $changeLocalToGlobal = isset($source['variation']) && $source['variation'] == true && $source['is_taxonomy'] != true && $productType == Product\Variable::TYPE; $productAttributes[$product->ID]['attributes'][$slug] = array('is_visible' => $source['visible'], 'is_variable' => isset($source['variation']) && $source['variation'] == true, 'values' => $changeLocalToGlobal ? str_replace(',', '|', $source['value']) : $source['value']); if (!isset($attributes[$slug])) { $type = isset($globalAttributes[$slug]) ? $this->_getAttributeType($globalAttributes[$slug]) : Text::TYPE; if ($changeLocalToGlobal) { $type = Multiselect::TYPE; } $label = isset($globalAttributes[$slug]) ? !empty($globalAttributes[$slug]->attribute_label) ? $globalAttributes[$slug]->attribute_label : $globalAttributes[$slug]->attribute_name : $source['name']; $attribute = $this->productService->createAttribute($type); $attribute->setSlug($slug); $attribute->setLabel($label); $attribute->setLocal($source['is_taxonomy'] != true && $changeLocalToGlobal != true); if ($changeLocalToGlobal) { foreach (explode('|', $productAttributes[$product->ID]['attributes'][$slug]['values']) as $attributeOption) { $option = new Option(); $option->setLabel($attributeOption); $option->setValue(sanitize_title($attributeOption)); $attribute->addOption($option); } $productAttributes[$product->ID]['attributes'][$slug]['values'] = array_map(function ($item) { return sanitize_title($item); }, explode('|', $productAttributes[$product->ID]['attributes'][$slug]['values'])); } $attributes[$slug] = $attribute; } } } } // Product variation data if ($products[$i]->meta_key == 'variation_data') { $variations = unserialize($products[$i]->meta_value); foreach ($variations as $variation => $value) { $productAttributes[$product->ID]['variations'][str_replace('tax_', '', $variation)] = sanitize_title($value); } } $key = $this->_transformKey($products[$i]->meta_key); if ($key !== null) { $value = $this->_transform($products[$i]->meta_key, $products[$i]->meta_value); if ($key == 'regular_price') { $regularPrice = $value; } if ($key == 'tax_classes') { $taxClasses = $value; } $wpdb->query($wpdb->prepare("UPDATE {$wpdb->postmeta} SET meta_value = %s, meta_key = %s WHERE meta_id = %d", array($value, $key, $products[$i]->meta_id))); $this->checkSql(); } $i++; } while ($i < $endI && $products[$i]->ID == $product->ID); // Update regular price if it includes tax if (!empty($this->taxes)) { $taxClasses = maybe_unserialize($taxClasses); foreach ($taxClasses as $taxClass) { if (isset($this->taxes['__compound__' . $taxClass])) { $regularPrice = $regularPrice / (100 + $this->taxes['__compound__' . $taxClass]['rate']) * 100; } } foreach ($taxClasses as $taxClass) { if (isset($this->taxes[$taxClass])) { $regularPrice = $regularPrice / (100 + $this->taxes[$taxClass]['rate']) * 100; } } $wpdb->query($wpdb->prepare("UPDATE {$wpdb->postmeta} SET meta_value = %s WHERE meta_key = %s AND post_id = %d", array($regularPrice, 'regular_price', $product->ID))); $this->checkSql(); } } foreach ($globalAttributes as $slug => $attributeData) { if (isset($attributes[$slug])) { continue; } $type = $this->_getAttributeType($attributeData); $label = !empty($attributeData->attribute_label) ? $attributeData->attribute_label : $attributeData->attribute_name; $attribute = $this->productService->createAttribute($type); $attribute->setSlug($slug); $attribute->setLabel($label); $attribute->setLocal(false); $attributes[$slug] = $attribute; } foreach ($attributes as $slug => $attribute) { /** @var $attribute Product\Attribute */ $antiDuplicateAttributes = unserialize($this->wp->getOption('jigoshop_attributes_anti_duplicate', serialize(array()))); if (!isset($antiDuplicateAttributes[$attribute->getSlug()]) || $attribute->isLocal()) { if (!$attribute->isLocal()) { // Fetch options if attribute is a taxonomy $options = $wpdb->get_results("\n\t\t\t\t\t\tSELECT t.name, t.slug FROM {$wpdb->terms} t\n\t\t\t\t\t\t\tLEFT JOIN {$wpdb->term_taxonomy} tt ON tt.term_id = t.term_id\n\t\t\t\t\t\t WHERE tt.taxonomy = 'pa_{$slug}'\n\t\t\t\t \t "); $this->checkSql(); $createdOptions = array(); foreach ($options as $source) { $option = new Option(); $option->setLabel($source->name); $option->setValue($source->slug); $attribute->addOption($option); $createdOptions[] = $source->slug; } } $this->productService->saveAttribute($attribute); $this->checkSql(); if (!$attribute->isLocal()) { $antiDuplicateAttributes[$attribute->getSlug()] = $attribute->getId(); $this->wp->updateOption('jigoshop_attributes_anti_duplicate', serialize($antiDuplicateAttributes)); $this->checkSql(); } } else { //merge attributes $attribute = $this->productService->getAttribute($antiDuplicateAttributes[$attribute->getSlug()]); if ($attribute instanceof Product\Attribute) { $savedOptions = array_map(function ($item) { return $item->getValue(); }, $attribute->getOptions()); foreach ($attributes[$slug]->getOptions() as $option) { if (!in_array($option->getValue(), $savedOptions)) { $attribute->addOption($option); } } $attributes[$slug] = $attribute; $this->productService->saveAttribute($attribute); } } // Add attribute to the products if ($attribute instanceof Product\Attribute) { foreach ($productIds as $id) { if (isset($productAttributes[$id]['attributes'][$attribute->getSlug()])) { $data = $productAttributes[$id]['attributes'][$attribute->getSlug()]; $value = array(); if (is_array($data['values'])) { foreach ($attribute->getOptions() as $option) { /** @var $option Option */ if (in_array($option->getValue(), $data['values'])) { $value[] = $option->getId(); } } } if (empty($value)) { $value = $data['values']; } $wpdb->insert($wpdb->prefix . 'jigoshop_product_attribute', array('product_id' => $id, 'attribute_id' => $attribute->getId(), 'value' => is_array($value) ? join('|', $value) : $value)); $this->checkSql(); $query = array('product_id' => $id, 'attribute_id' => $attribute->getId(), 'meta_key' => 'is_visible', 'meta_value' => $data['is_visible']); $wpdb->insert($wpdb->prefix . 'jigoshop_product_attribute_meta', $query); $this->checkSql(); if ($data['is_variable']) { $query = array('product_id' => $id, 'attribute_id' => $attribute->getId(), 'meta_key' => 'is_variable', 'meta_value' => true); $wpdb->insert($wpdb->prefix . 'jigoshop_product_attribute_meta', $query); $this->checkSql(); } } } } } foreach ($productIds as $id) { foreach ($productAttributes[$id]['variations'] as $taxonomy => $value) { if (!isset($attributes[$taxonomy])) { continue; } $attribute = $attributes[$taxonomy]; $option = $this->_findOption($attribute->getOptions(), $value); $query = array('variation_id' => $id, 'attribute_id' => $attribute->getId(), 'value' => $option); $wpdb->insert($wpdb->prefix . 'jigoshop_product_variation_attribute', $query); $this->checkSql(); } } // Add found tax classes $currentTaxClasses = $this->options->get('tax.classes'); $currentTaxClassesKeys = array_map(function ($item) { return $item['class']; }, $currentTaxClasses); $this->taxClasses = array_filter(array_unique($this->taxClasses), function ($item) use($currentTaxClassesKeys) { return !in_array($item, $currentTaxClassesKeys); }); foreach ($this->taxClasses as $class) { $currentTaxClasses[] = array('label' => ucfirst($class), 'class' => $class); } $this->options->update('tax.classes', $currentTaxClasses); // commit sql transation and restore value of autocommit $wpdb->query("COMMIT"); $wpdb->query("SET AUTOCOMMIT=" . $var_autocommit_sql); return true; } catch (Exception $e) { // rollback sql transation and restore value of autocommit if (WP_DEBUG) { \Monolog\Registry::getInstance(JIGOSHOP_LOGGER)->addDebug($e); } $wpdb->query("ROLLBACK"); $wpdb->query("SET AUTOCOMMIT=" . $var_autocommit_sql); Migration::saveLog(__('Migration products end with error: ', 'jigoshop') . $e); return false; } }
/** * Finds and returns list of available attributes. * * @return array List of available product attributes */ public function findAllAttributes() { $wpdb = $this->wp->getWPDB(); $query = "\n\t\tSELECT a.id, a.is_local, a.slug, a.label, a.type,\n\t\t\tao.id AS option_id, ao.value AS option_value, ao.label as option_label\n\t\tFROM {$wpdb->prefix}jigoshop_attribute a\n\t\t\tLEFT JOIN {$wpdb->prefix}jigoshop_attribute_option ao ON a.id = ao.attribute_id\n\t\t\tWHERE a.is_local = 0\n\t\t"; $results = $wpdb->get_results($query, ARRAY_A); $attributes = array(); for ($i = 0, $endI = count($results); $i < $endI;) { $attribute = $this->factory->createAttribute($results[$i]['type']); $attribute->setId((int) $results[$i]['id']); $attribute->setSlug($results[$i]['slug']); $attribute->setLabel($results[$i]['label']); $attribute->setLocal((bool) $results[$i]['is_local']); while ($i < $endI && $results[$i]['id'] == $attribute->getId()) { if ($results[$i]['option_id'] !== null) { $option = new Attribute\Option(); $option->setId($results[$i]['option_id']); $option->setLabel($results[$i]['option_label']); $option->setValue($results[$i]['option_value']); $attribute->addOption($option); } $i++; } $attributes[$attribute->getId()] = $attribute; } return $attributes; }
public function ajaxSaveAttributeOption() { $errors = array(); if (!isset($_POST['attribute_id']) || !is_numeric($_POST['attribute_id'])) { $errors[] = __('Respective attribute is not set.', 'jigoshop'); } if (!isset($_POST['label']) || empty($_POST['label'])) { $errors[] = __('Option label is not set.', 'jigoshop'); } if (!empty($errors)) { echo json_encode(array('success' => false, 'error' => join('<br/>', $errors))); exit; } $attribute = $this->productService->getAttribute((int) $_POST['attribute_id']); if (isset($_POST['id'])) { $option = $attribute->removeOption($_POST['id']); } else { $option = new Attribute\Option(); } $option->setLabel(trim(htmlspecialchars(strip_tags($_POST['label'])))); if (isset($_POST['slug']) && !empty($_POST['slug'])) { $option->setValue(trim(htmlspecialchars(strip_tags($_POST['value'])))); } else { $option->setValue($this->wp->getHelpers()->sanitizeTitle($option->getLabel())); } $attribute->addOption($option); $this->productService->saveAttribute($attribute); echo json_encode(array('success' => true, 'html' => Render::get('admin/product_attributes/option', array('id' => $attribute->getId(), 'option_id' => $option->getId(), 'option' => $option)))); exit; }
/** * Adds option to the attribute. * * @param Option $option Option to add. */ public function addOption(Attribute\Option $option) { $option->setAttribute($this); // Ability to add multiple new options $id = $option->getId(); if (empty($id)) { $id = hash('md5', $option->getLabel()); } $this->options[$id] = $option; }