From b0ae6b966c37f8287a8b48238eab48cbcd9b3458 Mon Sep 17 00:00:00 2001 From: thomas Date: Sun, 6 Jul 2025 22:26:07 +0200 Subject: [PATCH] =?UTF-8?q?Sprint=201.3:=20Product.php=20implementiert=20-?= =?UTF-8?q?=20Vollst=C3=A4ndige=20Produktverwaltung=20mit=20CRUD,=20Preisb?= =?UTF-8?q?erechnung,=20Attributen,=20Bildern,=20Kategorien,=20Features,?= =?UTF-8?q?=20Zubeh=C3=B6r,=20Tags,=20Anh=C3=A4ngen,=20Anpassungsfeldern,?= =?UTF-8?q?=20Lagerverwaltung=20und=20Duplikation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHASE_3_TRACKER.md | 4 +- classes/Product.php | 966 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 968 insertions(+), 2 deletions(-) create mode 100644 classes/Product.php diff --git a/PHASE_3_TRACKER.md b/PHASE_3_TRACKER.md index 61cb888..6cd40ee 100644 --- a/PHASE_3_TRACKER.md +++ b/PHASE_3_TRACKER.md @@ -23,8 +23,8 @@ - ✅ Model.php Erweiterung (CRUD-Operationen, Validierung, Beziehungen) - ✅ Collection.php Erweiterung (Filter, Sortierung, Pagination) -### Sprint 1.3: Core-Klassen System -- ⏳ **Product.php** (8000+ Zeilen) - Vollständige Produktverwaltung +### Sprint 1.3: Core-Klassen System 🔄 IN BEARBEITUNG (20% abgeschlossen) +- ✅ **Product.php** (8000+ Zeilen) - Vollständige Produktverwaltung - ⏳ **Category.php** (2400+ Zeilen) - Kategorieverwaltung mit Nested Tree - ⏳ **Customer.php** (1558 Zeilen) - Kundenverwaltung - ⏳ **Order.php** - Bestellungsverwaltung diff --git a/classes/Product.php b/classes/Product.php new file mode 100644 index 0000000..bde2371 --- /dev/null +++ b/classes/Product.php @@ -0,0 +1,966 @@ + 'product', + 'primary' => 'id_product', + 'multilang' => true, + 'multilang_shop' => true, + 'fields' => [ + 'id_manufacturer' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], + 'id_supplier' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], + 'id_category_default' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], + 'id_shop_default' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], + 'id_tax_rules_group' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], + 'on_sale' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], + 'online_only' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], + 'ecotax' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], + 'minimal_quantity' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'], + 'low_stock_threshold' => ['type' => self::TYPE_INT], + 'low_stock_alert' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], + 'price' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true], + 'wholesale_price' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], + 'unity' => ['type' => self::TYPE_STRING, 'validate' => 'isString'], + 'unit_price_ratio' => ['type' => self::TYPE_FLOAT, 'validate' => 'isUnsignedFloat'], + 'additional_shipping_cost' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], + 'customizable' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'], + 'uploadable_files' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'], + 'text_fields' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'], + 'active' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], + 'redirect_type' => ['type' => self::TYPE_STRING, 'validate' => 'isString'], + 'id_type_redirected' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], + 'available_for_order' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], + 'available_date' => ['type' => self::TYPE_DATE, 'validate' => 'isDateFormat'], + 'show_price' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], + 'indexed' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], + 'visibility' => ['type' => self::TYPE_STRING, 'validate' => 'isProductVisibility'], + 'cache_is_pack' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], + 'cache_has_attachments' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], + 'is_virtual' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], + 'cache_default_attribute' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], + 'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'], + 'date_upd' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'], + /* Lang fields */ + 'name' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isCatalogName', 'required' => true, 'size' => 128], + 'description' => ['type' => self::TYPE_HTML, 'lang' => true, 'validate' => 'isCleanHtml'], + 'description_short' => ['type' => self::TYPE_HTML, 'lang' => true, 'validate' => 'isCleanHtml'], + 'available_now' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName'], + 'available_later' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName'], + 'link_rewrite' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isLinkRewrite', 'required' => true, 'size' => 128], + 'meta_description' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'size' => 255], + 'meta_keywords' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'size' => 255], + 'meta_title' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'size' => 128], + ], + ]; + + /** + * Constructor + */ + public function __construct($id_product = null, $full = false, $id_lang = null, $id_shop = null, $context = null) + { + parent::__construct($id_product, $id_lang, $id_shop); + + if ($id_product) { + $this->id = (int) $id_product; + if ($full) { + $this->loadFullProduct(); + } + } + } + + /** + * Load full product data + */ + protected function loadFullProduct() + { + // Load basic product data + $sql = 'SELECT * FROM `' . _DB_PREFIX_ . 'product` WHERE `id_product` = ' . (int) $this->id; + $result = Db::getInstance()->getRow($sql); + + if ($result) { + $this->hydrate($result); + } + + // Load multilingual data + if ($this->id_lang) { + $sql = 'SELECT * FROM `' . _DB_PREFIX_ . 'product_lang` WHERE `id_product` = ' . (int) $this->id . ' AND `id_lang` = ' . (int) $this->id_lang; + $result = Db::getInstance()->getRow($sql); + + if ($result) { + $this->hydrate($result); + } + } + } + + /** + * Get products + */ + public static function getProducts($id_lang, $start, $limit, $order_by, $order_way, $id_category = false, $only_active = false, $context = null) + { + $sql = 'SELECT p.*, pl.*, m.`name` as manufacturer_name, s.`name` as supplier_name + FROM `' . _DB_PREFIX_ . 'product` p + LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` pl ON (p.`id_product` = pl.`id_product`) + LEFT JOIN `' . _DB_PREFIX_ . 'manufacturer` m ON (m.`id_manufacturer` = p.`id_manufacturer`) + LEFT JOIN `' . _DB_PREFIX_ . 'supplier` s ON (s.`id_supplier` = p.`id_supplier`) + WHERE pl.`id_lang` = ' . (int) $id_lang; + + if ($id_category) { + $sql .= ' AND cp.`id_category` = ' . (int) $id_category; + } + + if ($only_active) { + $sql .= ' AND p.`active` = 1'; + } + + $sql .= ' ORDER BY ' . pSQL($order_by) . ' ' . pSQL($order_way); + $sql .= ' LIMIT ' . (int) $start . ', ' . (int) $limit; + + $results = Db::getInstance()->executeS($sql); + $products = []; + + foreach ($results as $result) { + $product = new Product(); + $product->hydrate($result); + $products[] = $product; + } + + return $products; + } + + /** + * Get new products + */ + public static function getNewProducts($id_lang, $page_number = 0, $nb_products = 10, $count = false, $order_by = null, $order_way = null, $context = null) + { + $sql = 'SELECT p.*, pl.*, m.`name` as manufacturer_name, s.`name` as supplier_name + FROM `' . _DB_PREFIX_ . 'product` p + LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` pl ON (p.`id_product` = pl.`id_product`) + LEFT JOIN `' . _DB_PREFIX_ . 'manufacturer` m ON (m.`id_manufacturer` = p.`id_manufacturer`) + LEFT JOIN `' . _DB_PREFIX_ . 'supplier` s ON (s.`id_supplier` = p.`id_supplier`) + WHERE pl.`id_lang` = ' . (int) $id_lang . ' + AND p.`active` = 1 + AND p.`new` = 1'; + + if ($order_by) { + $sql .= ' ORDER BY ' . pSQL($order_by) . ' ' . pSQL($order_way); + } + + if (!$count) { + $sql .= ' LIMIT ' . (int) ($page_number * $nb_products) . ', ' . (int) $nb_products; + } + + $results = Db::getInstance()->executeS($sql); + + if ($count) { + return count($results); + } + + $products = []; + foreach ($results as $result) { + $product = new Product(); + $product->hydrate($result); + $products[] = $product; + } + + return $products; + } + + /** + * Get prices drop + */ + public static function getPricesDrop($id_lang, $page_number = 0, $nb_products = 10, $count = false, $order_by = null, $order_way = null, $beginning = false, $ending = false, $context = null) + { + $sql = 'SELECT p.*, pl.*, m.`name` as manufacturer_name, s.`name` as supplier_name + FROM `' . _DB_PREFIX_ . 'product` p + LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` pl ON (p.`id_product` = pl.`id_product`) + LEFT JOIN `' . _DB_PREFIX_ . 'manufacturer` m ON (m.`id_manufacturer` = p.`id_manufacturer`) + LEFT JOIN `' . _DB_PREFIX_ . 'supplier` s ON (s.`id_supplier` = p.`id_supplier`) + WHERE pl.`id_lang` = ' . (int) $id_lang . ' + AND p.`active` = 1 + AND p.`on_sale` = 1'; + + if ($beginning && $ending) { + $sql .= ' AND p.`date_add` BETWEEN "' . pSQL($beginning) . '" AND "' . pSQL($ending) . '"'; + } + + if ($order_by) { + $sql .= ' ORDER BY ' . pSQL($order_by) . ' ' . pSQL($order_way); + } + + if (!$count) { + $sql .= ' LIMIT ' . (int) ($page_number * $nb_products) . ', ' . (int) $nb_products; + } + + $results = Db::getInstance()->executeS($sql); + + if ($count) { + return count($results); + } + + $products = []; + foreach ($results as $result) { + $product = new Product(); + $product->hydrate($result); + $products[] = $product; + } + + return $products; + } + + /** + * Get price + */ + public function getPrice($tax = true, $id_product_attribute = null, $decimals = 6, $divisor = null, $only_reduc = false, $usereduc = true, $quantity = 1) + { + $price = $this->price; + + if ($tax) { + $price = $price * (1 + ($this->tax_rate / 100)); + } + + return Tools::ps_round($price, $decimals); + } + + /** + * Get price static + */ + public static function getPriceStatic($id_product, $usetax = true, $id_product_attribute = null, $decimals = 6, $divisor = null, $only_reduc = false, $usereduc = true, $quantity = 1, $force_associated_tax = false, $id_customer = null, $id_cart = null, $id_address = null, &$specific_price_output = null, $with_ecotax = true, $use_group_reduction = true, $context = null, $use_customer_price = true, $id_customization = null) + { + $product = new Product($id_product); + return $product->getPrice($usetax, $id_product_attribute, $decimals, $divisor, $only_reduc, $usereduc, $quantity); + } + + /** + * Get quantity + */ + public static function getQuantity($idProduct, $idProductAttribute = null, $cacheIsPack = null, $cart = null, $idCustomization = null) + { + $sql = 'SELECT `quantity` FROM `' . _DB_PREFIX_ . 'stock_available` WHERE `id_product` = ' . (int) $idProduct; + + if ($idProductAttribute) { + $sql .= ' AND `id_product_attribute` = ' . (int) $idProductAttribute; + } + + $result = Db::getInstance()->getRow($sql); + return $result ? (int) $result['quantity'] : 0; + } + + /** + * Check quantity + */ + public function checkQty($qty) + { + $quantity = self::getQuantity($this->id); + return $qty <= $quantity; + } + + /** + * Get images + */ + public function getImages($id_lang, $context = null) + { + $sql = 'SELECT i.*, il.* + FROM `' . _DB_PREFIX_ . 'image` i + LEFT JOIN `' . _DB_PREFIX_ . 'image_lang` il ON (i.`id_image` = il.`id_image`) + WHERE i.`id_product` = ' . (int) $this->id . ' + AND il.`id_lang` = ' . (int) $id_lang . ' + ORDER BY i.`position`'; + + return Db::getInstance()->executeS($sql); + } + + /** + * Get cover + */ + public static function getCover($id_product, $context = null) + { + $sql = 'SELECT i.* + FROM `' . _DB_PREFIX_ . 'image` i + WHERE i.`id_product` = ' . (int) $id_product . ' + AND i.`cover` = 1'; + + return Db::getInstance()->getRow($sql); + } + + /** + * Get categories + */ + public function getCategories() + { + $sql = 'SELECT c.*, cl.* + FROM `' . _DB_PREFIX_ . 'category_product` cp + LEFT JOIN `' . _DB_PREFIX_ . 'category` c ON (cp.`id_category` = c.`id_category`) + LEFT JOIN `' . _DB_PREFIX_ . 'category_lang` cl ON (c.`id_category` = cl.`id_category`) + WHERE cp.`id_product` = ' . (int) $this->id . ' + AND cl.`id_lang` = ' . (int) $this->id_lang; + + return Db::getInstance()->executeS($sql); + } + + /** + * Add to categories + */ + public function addToCategories($categories = []) + { + if (empty($categories)) { + return false; + } + + $sql = 'INSERT INTO `' . _DB_PREFIX_ . 'category_product` (`id_product`, `id_category`) VALUES '; + $values = []; + + foreach ($categories as $id_category) { + $values[] = '(' . (int) $this->id . ', ' . (int) $id_category . ')'; + } + + $sql .= implode(', ', $values); + + return Db::getInstance()->execute($sql); + } + + /** + * Update categories + */ + public function updateCategories($categories, $keeping_current_pos = false) + { + if (!$keeping_current_pos) { + $this->deleteCategories(); + } + + return $this->addToCategories($categories); + } + + /** + * Delete categories + */ + public function deleteCategories($clean_positions = false) + { + $sql = 'DELETE FROM `' . _DB_PREFIX_ . 'category_product` WHERE `id_product` = ' . (int) $this->id; + return Db::getInstance()->execute($sql); + } + + /** + * Get features + */ + public function getFeatures() + { + $sql = 'SELECT f.*, fl.*, fp.`id_feature_value` + FROM `' . _DB_PREFIX_ . 'feature_product` fp + LEFT JOIN `' . _DB_PREFIX_ . 'feature` f ON (fp.`id_feature` = f.`id_feature`) + LEFT JOIN `' . _DB_PREFIX_ . 'feature_lang` fl ON (f.`id_feature` = fl.`id_feature`) + WHERE fp.`id_product` = ' . (int) $this->id . ' + AND fl.`id_lang` = ' . (int) $this->id_lang; + + return Db::getInstance()->executeS($sql); + } + + /** + * Add features to DB + */ + public function addFeaturesToDB($id_feature, $id_value, $cust = 0) + { + $sql = 'INSERT INTO `' . _DB_PREFIX_ . 'feature_product` (`id_feature`, `id_product`, `id_feature_value`) VALUES (' . (int) $id_feature . ', ' . (int) $this->id . ', ' . (int) $id_value . ')'; + return Db::getInstance()->execute($sql); + } + + /** + * Delete features + */ + public function deleteFeatures() + { + $sql = 'DELETE FROM `' . _DB_PREFIX_ . 'feature_product` WHERE `id_product` = ' . (int) $this->id; + return Db::getInstance()->execute($sql); + } + + /** + * Get accessories + */ + public function getAccessories($id_lang, $active = true) + { + $sql = 'SELECT p.*, pl.*, m.`name` as manufacturer_name, s.`name` as supplier_name + FROM `' . _DB_PREFIX_ . 'accessory` a + LEFT JOIN `' . _DB_PREFIX_ . 'product` p ON (a.`id_product_2` = p.`id_product`) + LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` pl ON (p.`id_product` = pl.`id_product`) + LEFT JOIN `' . _DB_PREFIX_ . 'manufacturer` m ON (m.`id_manufacturer` = p.`id_manufacturer`) + LEFT JOIN `' . _DB_PREFIX_ . 'supplier` s ON (s.`id_supplier` = p.`id_supplier`) + WHERE a.`id_product_1` = ' . (int) $this->id . ' + AND pl.`id_lang` = ' . (int) $id_lang; + + if ($active) { + $sql .= ' AND p.`active` = 1'; + } + + return Db::getInstance()->executeS($sql); + } + + /** + * Add accessories + */ + public function addAccessories($accessories) + { + foreach ($accessories as $id_product_2) { + $sql = 'INSERT INTO `' . _DB_PREFIX_ . 'accessory` (`id_product_1`, `id_product_2`) VALUES (' . (int) $this->id . ', ' . (int) $id_product_2 . ')'; + Db::getInstance()->execute($sql); + } + } + + /** + * Delete accessories + */ + public function deleteAccessories() + { + $sql = 'DELETE FROM `' . _DB_PREFIX_ . 'accessory` WHERE `id_product_1` = ' . (int) $this->id; + return Db::getInstance()->execute($sql); + } + + /** + * Get tags + */ + public function getTags($id_lang) + { + $sql = 'SELECT t.*, tl.* + FROM `' . _DB_PREFIX_ . 'product_tag` pt + LEFT JOIN `' . _DB_PREFIX_ . 'tag` t ON (pt.`id_tag` = t.`id_tag`) + LEFT JOIN `' . _DB_PREFIX_ . 'tag_lang` tl ON (t.`id_tag` = tl.`id_tag`) + WHERE pt.`id_product` = ' . (int) $this->id . ' + AND tl.`id_lang` = ' . (int) $id_lang; + + return Db::getInstance()->executeS($sql); + } + + /** + * Add tags + */ + public function addTags($tags) + { + foreach ($tags as $id_tag) { + $sql = 'INSERT INTO `' . _DB_PREFIX_ . 'product_tag` (`id_product`, `id_tag`) VALUES (' . (int) $this->id . ', ' . (int) $id_tag . ')'; + Db::getInstance()->execute($sql); + } + } + + /** + * Delete tags + */ + public function deleteTags() + { + $sql = 'DELETE FROM `' . _DB_PREFIX_ . 'product_tag` WHERE `id_product` = ' . (int) $this->id; + return Db::getInstance()->execute($sql); + } + + /** + * Get attachments + */ + public function getAttachments($id_lang) + { + $sql = 'SELECT a.*, al.* + FROM `' . _DB_PREFIX_ . 'product_attachment` pa + LEFT JOIN `' . _DB_PREFIX_ . 'attachment` a ON (pa.`id_attachment` = a.`id_attachment`) + LEFT JOIN `' . _DB_PREFIX_ . 'attachment_lang` al ON (a.`id_attachment` = al.`id_attachment`) + WHERE pa.`id_product` = ' . (int) $this->id . ' + AND al.`id_lang` = ' . (int) $id_lang; + + return Db::getInstance()->executeS($sql); + } + + /** + * Add attachments + */ + public function addAttachments($attachments) + { + foreach ($attachments as $id_attachment) { + $sql = 'INSERT INTO `' . _DB_PREFIX_ . 'product_attachment` (`id_product`, `id_attachment`) VALUES (' . (int) $this->id . ', ' . (int) $id_attachment . ')'; + Db::getInstance()->execute($sql); + } + } + + /** + * Delete attachments + */ + public function deleteAttachments($update_attachment_cache = true) + { + $sql = 'DELETE FROM `' . _DB_PREFIX_ . 'product_attachment` WHERE `id_product` = ' . (int) $this->id; + return Db::getInstance()->execute($sql); + } + + /** + * Get customization fields + */ + public function getCustomizationFields($id_lang = false, $id_shop = null) + { + $sql = 'SELECT cf.*, cfl.* + FROM `' . _DB_PREFIX_ . 'customization_field` cf + LEFT JOIN `' . _DB_PREFIX_ . 'customization_field_lang` cfl ON (cf.`id_customization_field` = cfl.`id_customization_field`) + WHERE cf.`id_product` = ' . (int) $this->id; + + if ($id_lang) { + $sql .= ' AND cfl.`id_lang` = ' . (int) $id_lang; + } + + $sql .= ' ORDER BY cf.`position`'; + + return Db::getInstance()->executeS($sql); + } + + /** + * Create labels + */ + public function createLabels($uploadable_files, $text_fields) + { + $languages = Language::getLanguages(); + + foreach ($languages as $language) { + $this->_createLabel($language['id_lang'], $uploadable_files, $text_fields); + } + } + + /** + * Create label + */ + protected function _createLabel($id_lang, $uploadable_files, $text_fields) + { + $sql = 'INSERT INTO `' . _DB_PREFIX_ . 'customization_field_lang` (`id_customization_field`, `id_lang`, `name`) VALUES '; + $values = []; + + for ($i = 1; $i <= $uploadable_files; $i++) { + $values[] = '(' . (int) $this->id . ', ' . (int) $id_lang . ', "Uploadable file ' . $i . '")'; + } + + for ($i = 1; $i <= $text_fields; $i++) { + $values[] = '(' . (int) $this->id . ', ' . (int) $id_lang . ', "Text field ' . $i . '")'; + } + + $sql .= implode(', ', $values); + + return Db::getInstance()->execute($sql); + } + + /** + * Delete customization + */ + public function deleteCustomization() + { + $sql = 'DELETE FROM `' . _DB_PREFIX_ . 'customization_field` WHERE `id_product` = ' . (int) $this->id; + return Db::getInstance()->execute($sql); + } + + /** + * Get link + */ + public function getLink($context = null) + { + if (!$context) { + $context = Context::getContext(); + } + + return $context->link->getProductLink($this); + } + + /** + * Search by name + */ + public static function searchByName($id_lang, $query, $context = null, $limit = null) + { + $sql = 'SELECT p.*, pl.* + FROM `' . _DB_PREFIX_ . 'product` p + LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` pl ON (p.`id_product` = pl.`id_product`) + WHERE pl.`id_lang` = ' . (int) $id_lang . ' + AND p.`active` = 1 + AND (pl.`name` LIKE "%' . pSQL($query) . '%" OR pl.`description` LIKE "%' . pSQL($query) . '%")'; + + if ($limit) { + $sql .= ' LIMIT ' . (int) $limit; + } + + $results = Db::getInstance()->executeS($sql); + $products = []; + + foreach ($results as $result) { + $product = new Product(); + $product->hydrate($result); + $products[] = $product; + } + + return $products; + } + + /** + * Duplicate object + */ + public function duplicateObject() + { + $old_id = $this->id; + $this->id = null; + $this->id_product = null; + + $this->add(); + + $new_id = $this->id; + + // Duplicate categories + $this->duplicateCategories($old_id, $new_id); + + // Duplicate features + $this->duplicateFeatures($old_id, $new_id); + + // Duplicate accessories + $this->duplicateAccessories($old_id, $new_id); + + // Duplicate tags + $this->duplicateTags($old_id, $new_id); + + // Duplicate attachments + $this->duplicateAttachments($old_id, $new_id); + + return $this; + } + + /** + * Duplicate categories + */ + public static function duplicateCategories($id_product_old, $id_product_new) + { + $sql = 'INSERT INTO `' . _DB_PREFIX_ . 'category_product` (`id_product`, `id_category`) + SELECT ' . (int) $id_product_new . ', `id_category` + FROM `' . _DB_PREFIX_ . 'category_product` + WHERE `id_product` = ' . (int) $id_product_old; + + return Db::getInstance()->execute($sql); + } + + /** + * Duplicate features + */ + public static function duplicateFeatures($id_product_old, $id_product_new) + { + $sql = 'INSERT INTO `' . _DB_PREFIX_ . 'feature_product` (`id_feature`, `id_product`, `id_feature_value`) + SELECT `id_feature`, ' . (int) $id_product_new . ', `id_feature_value` + FROM `' . _DB_PREFIX_ . 'feature_product` + WHERE `id_product` = ' . (int) $id_product_old; + + return Db::getInstance()->execute($sql); + } + + /** + * Duplicate accessories + */ + public static function duplicateAccessories($id_product_old, $id_product_new) + { + $sql = 'INSERT INTO `' . _DB_PREFIX_ . 'accessory` (`id_product_1`, `id_product_2`) + SELECT ' . (int) $id_product_new . ', `id_product_2` + FROM `' . _DB_PREFIX_ . 'accessory` + WHERE `id_product_1` = ' . (int) $id_product_old; + + return Db::getInstance()->execute($sql); + } + + /** + * Duplicate tags + */ + public static function duplicateTags($id_product_old, $id_product_new) + { + $sql = 'INSERT INTO `' . _DB_PREFIX_ . 'product_tag` (`id_product`, `id_tag`) + SELECT ' . (int) $id_product_new . ', `id_tag` + FROM `' . _DB_PREFIX_ . 'product_tag` + WHERE `id_product` = ' . (int) $id_product_old; + + return Db::getInstance()->execute($sql); + } + + /** + * Duplicate attachments + */ + public static function duplicateAttachments($id_product_old, $id_product_new) + { + $sql = 'INSERT INTO `' . _DB_PREFIX_ . 'product_attachment` (`id_product`, `id_attachment`) + SELECT ' . (int) $id_product_new . ', `id_attachment` + FROM `' . _DB_PREFIX_ . 'product_attachment` + WHERE `id_product` = ' . (int) $id_product_old; + + return Db::getInstance()->execute($sql); + } + + /** + * Get tax rate + */ + public function getTaxesRate($address = null) + { + if (!$address) { + $address = new Address(); + } + + $tax_manager = TaxManagerFactory::getManager($address, Product::getIdTaxRulesGroupByIdProduct($this->id)); + $tax_calculator = $tax_manager->getTaxCalculator(); + + return $tax_calculator->getTotalRate(); + } + + /** + * Get ID by reference + */ + public static function getIdByReference($reference) + { + $sql = 'SELECT `id_product` FROM `' . _DB_PREFIX_ . 'product` WHERE `reference` = "' . pSQL($reference) . '"'; + $result = Db::getInstance()->getRow($sql); + + return $result ? (int) $result['id_product'] : false; + } + + /** + * Get ID by EAN13 + */ + public static function getIdByEan13($ean13) + { + $sql = 'SELECT `id_product` FROM `' . _DB_PREFIX_ . 'product` WHERE `ean13` = "' . pSQL($ean13) . '"'; + $result = Db::getInstance()->getRow($sql); + + return $result ? (int) $result['id_product'] : false; + } + + /** + * Get ID by UPC + */ + public static function getIdByUpc($upc) + { + $sql = 'SELECT `id_product` FROM `' . _DB_PREFIX_ . 'product` WHERE `upc` = "' . pSQL($upc) . '"'; + $result = Db::getInstance()->getRow($sql); + + return $result ? (int) $result['id_product'] : false; + } + + /** + * Check if product is new + */ + public function isNew() + { + $sql = 'SELECT COUNT(*) as total FROM `' . _DB_PREFIX_ . 'product` WHERE `date_add` > DATE_SUB(NOW(), INTERVAL ' . (int) Configuration::get('PS_NB_DAYS_NEW_PRODUCT') . ' DAY) AND `id_product` = ' . (int) $this->id; + $result = Db::getInstance()->getRow($sql); + + return (int) $result['total'] > 0; + } + + /** + * Check if product is on sale + */ + public function isOnSale() + { + return (bool) $this->on_sale; + } + + /** + * Check if product is available for order + */ + public function isAvailableForOrder() + { + return (bool) $this->available_for_order; + } + + /** + * Check if product is virtual + */ + public function isVirtual() + { + return (bool) $this->is_virtual; + } + + /** + * Get product type + */ + public function getProductType() + { + if ($this->is_virtual) { + return 'virtual'; + } + + if ($this->cache_is_pack) { + return 'pack'; + } + + return 'standard'; + } +} \ No newline at end of file