diff --git a/api/apiUpdateProducts.php b/api/apiUpdateProducts.php index 327b88e..e5c7b8e 100644 --- a/api/apiUpdateProducts.php +++ b/api/apiUpdateProducts.php @@ -6,5 +6,6 @@ require __DIR__ . '/IsotopeDatabaseHandler.php'; $apiExact = new ApiExact(); $databaseHandler = new IsotopeDatabaseHandler(); $products = $apiExact->getProducts(); -$databaseHandler->processProducts($products); +var_dump($products); +//$databaseHandler->processProducts($products); diff --git a/api/listener/Product.php b/api/listener/Product.php new file mode 100644 index 0000000..3ddb5de --- /dev/null +++ b/api/listener/Product.php @@ -0,0 +1,271 @@ + + * @date 11.07.22 + */ + + +class Product +{ + const VAT = 0.19; + + private $id; + private $standardSalesPrice; + private $salesVatCode; + private $salesVatCodeDescription; + private $description; + private $extraDescription; + private $isWebShopItem; + private $itemGroup; + private $itemGroupCode; + private $stock; + private $netWeight; + private $grossWeight; + private $code; + + public static function getFields() + { + return [ + 'ID', + 'StandardSalesPrice', + 'SalesVatCode', + 'SalesVatCodeDescription', + 'Description', + 'ExtraDescription', + 'IsWebshopItem', + 'ItemGroup', + 'ItemGroupCode', + 'Stock', + 'NetWeight', + 'GrossWeight', + 'Code', + ]; + } + + public static function createProductFromApiItem(array $item) + { + $res = new self(); + $res->setId($item['ID']); + $res->setStandardSalesPrice($item['StandardSalesPrice']); + $res->setSalesVatCode($item['SalesVatCode']); + $res->setSalesVatCodeDescription($item['SalesVatCodeDescription']); + $res->setDescription($item['Description']); + $res->setExtraDescription($item['ExtraDescription']); + $res->setIsWebShopItem($item['IsWebshopItem']); + $res->setItemGroup($item['ItemGroup']); + $res->setItemGroupCode($item['ItemGroupCode']); + $res->setStock($item['Stock']); + $res->setNetWeight($item['NetWeight']); + $res->setGrossWeight($item['GrossWeight']); + $res->setCode($item['Code']); + return $res; + } + + /** + * @return mixed + */ + public function getId() + { + return $this->id; + } + + /** + * @param mixed $id + */ + public function setId($id) + { + $this->id = $id; + } + + /** + * @return mixed + */ + public function getStandardSalesPrice() + { + return $this->standardSalesPrice; + } + + /** + * @param mixed $standardSalesPrice + */ + public function setStandardSalesPrice($standardSalesPrice) + { + $this->standardSalesPrice = $standardSalesPrice; + } + + /** + * @return mixed + */ + public function getSalesVatCode() + { + return $this->salesVatCode; + } + + /** + * @param mixed $salesVatCode + */ + public function setSalesVatCode($salesVatCode) + { + $this->salesVatCode = $salesVatCode; + } + + /** + * @return mixed + */ + public function getSalesVatCodeDescription() + { + return $this->salesVatCodeDescription; + } + + /** + * @param mixed $salesVatCodeDescription + */ + public function setSalesVatCodeDescription($salesVatCodeDescription) + { + $this->salesVatCodeDescription = $salesVatCodeDescription; + } + + /** + * @return mixed + */ + public function getDescription() + { + return $this->description; + } + + /** + * @param mixed $description + */ + public function setDescription($description) + { + $this->description = $description; + } + + /** + * @return mixed + */ + public function getExtraDescription() + { + return $this->extraDescription; + } + + /** + * @param mixed $extraDescription + */ + public function setExtraDescription($extraDescription) + { + $this->extraDescription = $extraDescription; + } + + /** + * @return mixed + */ + public function getIsWebShopItem() + { + return $this->isWebShopItem; + } + + /** + * @param mixed $isWebShopItem + */ + public function setIsWebShopItem($isWebShopItem) + { + $this->isWebShopItem = $isWebShopItem; + } + + /** + * @return mixed + */ + public function getItemGroup() + { + return $this->itemGroup; + } + + /** + * @param mixed $itemGroup + */ + public function setItemGroup($itemGroup) + { + $this->itemGroup = $itemGroup; + } + + /** + * @return mixed + */ + public function getItemGroupCode() + { + return $this->itemGroupCode; + } + + /** + * @param mixed $itemGroupCode + */ + public function setItemGroupCode($itemGroupCode) + { + $this->itemGroupCode = $itemGroupCode; + } + + /** + * @return mixed + */ + public function getStock() + { + return $this->stock; + } + + /** + * @param mixed $stock + */ + public function setStock($stock) + { + $this->stock = $stock; + } + + /** + * @return mixed + */ + public function getNetWeight() + { + return $this->netWeight; + } + + /** + * @param mixed $netWeight + */ + public function setNetWeight($netWeight) + { + $this->netWeight = $netWeight; + } + + /** + * @return mixed + */ + public function getGrossWeight() + { + return $this->grossWeight; + } + + /** + * @param mixed $grossWeight + */ + public function setGrossWeight($grossWeight) + { + $this->grossWeight = $grossWeight; + } + + /** + * @return mixed + */ + public function getCode() + { + return $this->code; + } + + /** + * @param mixed $code + */ + public function setCode($code) + { + $this->code = $code; + } +} \ No newline at end of file diff --git a/api/tokenData b/api/tokenData index c674512..9e9815a 100644 --- a/api/tokenData +++ b/api/tokenData @@ -1 +1 @@ -{"access_token":"stampDE001.gAAAAATOqLu_FvySDkT1YxBYQOSbyTqVC-jQWfCBmfomFxKEuiSUp2H5XYFwHOjuZ_cbURYObp_CuoijjuXPHYAvgR5GjxZVLpnI33116cIBqeKkdnQ2617l-DfjsiPlSO9ilA5CtrV75A1ZhxtWJqJ5uZynH0eT0ZB8LytI81Fg2p4tVAIAAIAAAAAqO6Xcq6ab64TEJDE2QyPHpfueoxrCkLlFVkxbiXgoANGrrSTu9qQtOTYyszkuc3sGZSdg1zD8eJLx8sL0zlTy7BYRYgTmiUhLZhECjG_7PfpObMuaz_Q4NOlIeyGOX23B6p-lbSZ_HHcVx1XzDgSry7MSIcFZGIqUWqr4jWW6EDqU0Q9A1ypbkKAPxh77V68LrHAzIm8yjoQell4GBnNSVOwNWKcLgmqkznUIsywr-Kq6dvfs_Ziy0nu5DR6PbpYLIVY4g1eCE4ncfgtPYsndKKuSIOisf8pYn_7NaLfSxvYWVwFxuTHw3roRufszbE-toAsUzfBVv2mQjKdyop9xRri3Adh3vfsEAKEyksZdsduCjFEPu6KoSv_pgQfExSURkHKj0gOX8_GjPtcbT4OWZ3JuZ7n9-4U5uSV-HHOvd7T2ojgQ0lFcVukuwHq086KTMNGzcBPfM-_vcBYAx1nRRpupQL2Np0fm85iycZwycSKAFydpcMeB1VaP5PYkVLiLOsXBPy4SMM0_-c51NykUm338m8SwAVeVQXN3VYcT0kJwUsyMdOtYoDvXUL-5p_NafKwwPwdrgpIwnjjO3s37O43RsGWaY8kp42y2I4lQbMwcqTde_bOcPCRQWium4OB__fkFaiCJ5eE5EGcbPUVzjEfW_Lhr8yrkRacFfut9YqCTVuGxZepi0eyX3FHk_-qAtXTBav11EZ40G5slokVlyPu63pQfhpSrsNIGZv-j5YIsN5MqjHn7HgtbS0UMRUAQERHvDkz6ddfbkqwjOgYE","token_type":"bearer","expires_in":"600","refresh_token":"stampDE001.vhK0!IAAAAIMZSdkYxeTLXpkXHeNZCQAgV_DZ3JM_sqN9Y81kYggu8QEAAAE1Ecbn6_tJNnyZQn4ktg02ypEuBII4NQc3bgVgWB1lM0IZaxRyKB5C9AVOCqh9ewKECN3eNMP0C0P2QVzNyoBsOHrK_QHqqypFPBfs2fPG2psKUngqf4nTWfGWpjnaeevGqll88v4_Cka58o-Kmlrv2Ho_uc3Tjl4gE6GeLF870gyWnW1Qx3AJUTx4730yswUr1YwEODVTl0WBSJ2LEqELKvx4MQ603b642QkevFCrH6ttZBef-ufEKcRpT7Fic9JHcSdUEJCGrhsGe__Ah0dS2WtxNfhD3m7YgGpxBqSjCJXWvXzBpBd9UsZ6vCuF0CP2EVvSZ_HS3iTgD5UOBDoQkTn8kt65TixrpQWZE5j2-vfgoQy9CbPv8aO75XT4gZGPtjij7GAdXNc17ZH-P_3N5CFmLnycGyfPNdnS58PP4nkYcl6LzVF5pBOBj2mIZa9Ic7W1fcSWDePaGC_WjVQvuV18tjvsMxp738XNTPdZfta5V769cSq1tuRblIyLYIJhhYhmprqg1KADYkcNC7RHYSVOHbds83VORW9gvgjFLCWt15TLhJaGbydgaMMUGHlZ4gb8YFMCzbRiq_20_K8iP_J5BdP2omIeHCNnH6360wmJybbgcFlYv0FZNDXUwkRxaTsREpfSvDUzZHCsk0Od","expiry_time":1657818416} \ No newline at end of file +{"access_token":"stampDE001.gAAAALz6N4YQffUT_PsUt5T0HjUWldTzhSYiO3CUjBSEayLfCTPdWlFQZ7houvHkiJ5jDlRFLGflu6K_7VnRNtYmkBNRTz3P30sOtLmqGX2B7J3EJ3uSoApS-AEw4kzKk-FitabCwiTgdP9UbXaAnbfqqZEaXWmLmXQLlLBEOv4AJEvsVAIAAIAAAADSS-GjXrYGVJnWsv-jussxX5iQvsELp6Pj0JfWjxMd7AZaTFhADLGSvQndUodg2EeGmQ65dcgxE-gu6ZVdRwUg3u8yFpwDyoGTxqRojOA0pUDz1NGYifXvU_uw49jFYIjT5NhDwUeW-7LtZAnkU-GXqTJXeUy20cPXX7pw5uI6h-DAimSj9rrQTOZVOVAIfLhT-ojj0HFmUQsON0FJ_4ZulHoUggehSj4ECTdNM1sE9HP9cUMwuT46ccuqfyTu01HxkBxuwbPwUFf6VbyenqMjx8kuPrDNhhDD7h3XBnEYnUB5S9gobBGzkBxowi3PE83rhgpHQ1cyq6q76yXK0UOKzYVqyTGlQX6fbH95Ftv8CO7XhI5hQVOHDvadJxKZLtkVTrj9c0VlyduBi6s_VtmMhNw8dWDJqBrDSJOnzmEsXzvcvXMVXj0rLVHOGK54V_RPgFL9ieLLFvHs80X15xl90FvmILpm8BUp1J8hC5ruCGK_LZNsjIzJkPDjTmWCu3ys7WkvMMG79a28S4bgQDXmrgBhLH4PwLYz_r-XLvp7FeFXmL4NFsyQUQULCZjLkHZ45IcrlbLNKSO69QSDCS2orVj5_Ta7koRQ2zTDL34dLmXfk9RP1zVinc7fFN6RTPc_HYFqEjAHiu14Wsq1fMcNPmA9mWIVFXIVJBK6E9t-OxUkHXnvGy_wBIGZvK4qMakFisAeqmbQ61RlFYvzcE-8gUbCx0KU1ZG0TbutmJPr8mFEMWdVBk83Xlm4oaItp6BYA9pKgMciBucPq156qQ0G","token_type":"bearer","expires_in":"600","refresh_token":"stampDE001.vhK0!IAAAACdtr4d9DYfTnyyeJaJvOI0bouLOlyz4z5SU6-U1dG7D8QEAAAEjZ3c-4haAJg1MGAzHhNBKsuCqEJS71izk6qHzC8cEgxzDi5yTB1-UwsRWD8gUZEqepx55CP426XFUBQQzs8C7C_he3pRWXMZ3-qNAKAFcGtL4xJUEjnhHT-8oTswEZUo95U1WBXeAlY2jvQlL2TWlRfXwWWyE1qbcPLI6EHDQfjFnyMk-qFzmwUMjRtxv42FQODq3kkyqJWQm9yzqCJcYF1VpPKOn4NuEvoOgR0PEia3MccvSsRP9EParcpcWIEe45bEZ3jizx6G1vwSmHVNOvKfI45gQksw4EYWZl9TsS2d1AtGAuqGxkJ8JWD6fQHU5aOx0IuSAbcghZbMj25hMenJOCEAx8mehcg-QwDfAFMV0zAoSIhoZ0FZAV25y_fAEiGEz7nZUMkkZkO3wsxTjLkQslA5YrGNHz1VZyHNbQBgsNm2iP-piYpmYcSV2Z91f6oj_N-jLYB5T4vyhQSsw9Fm0Zy97kl0A59NCjkM3EHw3Z6twDF_rQkpqdes40y5PLp2TZrmkmW8moIIYU5L0SMQ0T0t7MnsrHp3GhaKSvFKNcQNUNU8eZfQDr04diEauKmQnGvGy-rXYbhap3kGtLmRZ3jZ8EhC5YXnn1cpejmIacBloOFZ-DdbUG3m86N78LJRVUv06Ca5b-MZjmbtB","expiry_time":1657899700} \ No newline at end of file diff --git a/api2/Product.php b/api2/Product.php deleted file mode 100644 index f507f25..0000000 --- a/api2/Product.php +++ /dev/null @@ -1,809 +0,0 @@ -'" . ($time + 60) . "')" - ); - } - - return static::findBy($arrColumns, $arrValues, $arrOptions); - } - - /** - * Find a single product by primary key - * - * @param int $intId - * @param array $arrOptions - * - * @return static|null - */ - public static function findPublishedByPk($intId, array $arrOptions = array()) - { - $arrOptions = array_merge( - array( - 'return' => 'Model' - ), - $arrOptions - ); - - return static::findPublishedBy(static::$strPk, (int) $intId, $arrOptions); - } - - /** - * Find a single product by its ID or alias - * - * @param mixed $varId The ID or alias - * @param array $arrOptions An optional options array - * - * @return static|null The model or null if the result is empty - */ - public static function findPublishedByIdOrAlias($varId, array $arrOptions = array()) - { - $t = static::$strTable; - - $arrColumns = array("($t.id=? OR $t.alias=?)"); - $arrValues = array(is_numeric($varId) ? $varId : 0, $varId); - - $arrOptions = array_merge( - array( - 'limit' => 1, - 'return' => 'Model' - ), - $arrOptions - ); - - return static::findPublishedBy($arrColumns, $arrValues, $arrOptions); - } - - /** - * Find products by IDs - * - * @param array $arrIds - * @param array $arrOptions - * - * @return Product[]|Collection - */ - public static function findPublishedByIds(array $arrIds, array $arrOptions = array()) - { - if (0 === \count($arrIds)) { - return null; - } - - return static::findPublishedBy( - array(static::$strTable . '.id IN (' . implode(',', array_map('intval', $arrIds)) . ')'), - null, - $arrOptions - ); - } - - /** - * Return collection of published product variants by product PID - * - * @param int $intPid - * @param array $arrOptions - * - * @return Collection|Product[]|null - */ - public static function findPublishedByPid($intPid, array $arrOptions = array()) - { - return static::findPublishedBy('pid', (int) $intPid, $arrOptions); - } - - /** - * Return collection of published products by categories - * - * @param array $arrCategories - * @param array $arrOptions - * - * @return Collection|Product[]|null - */ - public static function findPublishedByCategories(array $arrCategories, array $arrOptions = array()) - { - return static::findPublishedBy( - array('c.page_id IN (' . implode(',', array_map('intval', $arrCategories)) . ')'), - null, - $arrOptions - ); - } - - /** - * Find a single frontend-available product by primary key - * - * @param int $intId - * @param array $arrOptions - * - * @return static|null - */ - public static function findAvailableByPk($intId, array $arrOptions = array()) - { - $objProduct = static::findPublishedByPk($intId, $arrOptions); - - if (null === $objProduct || !$objProduct->isAvailableInFrontend()) { - return null; - } - - return $objProduct; - } - - /** - * Find a single frontend-available product by its ID or alias - * - * @param mixed $varId The ID or alias - * @param array $arrOptions An optional options array - * - * @return Product|null The model or null if the result is empty - */ - public static function findAvailableByIdOrAlias($varId, array $arrOptions = array()) - { - $objProduct = static::findPublishedByIdOrAlias($varId, $arrOptions); - - if (null === $objProduct || !$objProduct->isAvailableInFrontend()) { - return null; - } - - return $objProduct; - } - - /** - * Find frontend-available products by IDs - * - * @param array $arrIds - * @param array $arrOptions - * - * @return Collection|Product[]|null - */ - public static function findAvailableByIds(array $arrIds, array $arrOptions = array()) - { - $objProducts = static::findPublishedByIds($arrIds, $arrOptions); - - if (null === $objProducts) { - return null; - } - - $arrProducts = []; - foreach ($objProducts as $objProduct) { - if ($objProduct->isAvailableInFrontend()) { - $arrProducts[] = $objProduct; - } - } - - if (0 === \count($arrProducts)) { - return null; - } - - return new Collection($arrProducts, static::$strTable); - } - - /** - * Find frontend-available products by condition - * - * @param mixed $arrColumns - * @param mixed $arrValues - * @param array $arrOptions - * - * @return Collection - */ - public static function findAvailableBy($arrColumns, $arrValues, array $arrOptions = array()) - { - $objProducts = static::findPublishedBy($arrColumns, $arrValues, $arrOptions); - - if (null === $objProducts) { - return null; - } - - $arrProducts = []; - foreach ($objProducts as $objProduct) { - if ($objProduct->isAvailableInFrontend()) { - $arrProducts[] = $objProduct; - } - } - - if (0 === \count($arrProducts)) { - return null; - } - - return new Collection($arrProducts, static::$strTable); - } - - /** - * Find variant of a product - * - * @param IsotopeProduct $objProduct - * @param array $arrVariant - * @param array $arrOptions - * - * @return Model|null - */ - public static function findVariantOfProduct( - IsotopeProduct $objProduct, - array $arrVariant, - array $arrOptions = array() - ) { - $t = static::$strTable; - - $arrColumns = array( - "$t.id IN (" . implode(',', $objProduct->getVariantIds()) . ')', - "$t." . implode("=? AND $t.", array_keys($arrVariant)) . '=?' - ); - - $arrOptions = array_merge( - array( - 'limit' => 1, - 'column' => $arrColumns, - 'value' => $arrVariant, - 'return' => 'Model' - ), - $arrOptions - ); - - return static::find($arrOptions); - } - - /** - * Finds the default variant of a product. - * - * @param IsotopeProduct $objProduct - * @param array $arrOptions - * - * @return static|null - */ - public static function findDefaultVariantOfProduct(IsotopeProduct $objProduct, array $arrOptions = array()) - { - static $cache; - - if (null === $cache) { - $cache = []; - $data = Database::getInstance()->execute( - "SELECT id, pid FROM tl_iso_product WHERE pid>0 AND language='' AND fallback='1'" - ); - - while ($data->next()) { - $cache[$data->pid] = $data->id; - } - } - - $defaultId = $cache[$objProduct->getProductId()]; - - if ($defaultId < 1 || !\in_array($defaultId, $objProduct->getVariantIds())) { - return null; - } - - return static::findByPk($defaultId, $arrOptions); - } - - /** - * Returns the number of published products. - * - * @param array $arrOptions - * - * @return int - */ - public static function countPublished(array $arrOptions = array()) - { - return static::countPublishedBy(array(), array(), $arrOptions); - } - - /** - * Return the number of products matching certain criteria - * - * @param mixed $arrColumns - * @param mixed $arrValues - * @param array $arrOptions - * - * @return int - */ - public static function countPublishedBy($arrColumns, $arrValues, array $arrOptions = array()) - { - $t = static::$strTable; - - $arrValues = (array) $arrValues; - - if (!\is_array($arrColumns)) { - $arrColumns = array(static::$strTable . '.' . $arrColumns . '=?'); - } - - // Add publish check to $arrColumns as the first item to enable SQL keys - if (BE_USER_LOGGED_IN !== true) { - $time = Date::floorToMinute(); - array_unshift( - $arrColumns, - " - $t.published='1' - AND ($t.start='' OR $t.start<'$time') - AND ($t.stop='' OR $t.stop>'" . ($time + 60) . "') - " - ); - } - - return static::countBy($arrColumns, $arrValues, $arrOptions); - } - - /** - * Gets the number of translation records in the product table. - * Mostly useful to see if there are any translations at all to optimize queries. - * - * @return int - */ - public static function countTranslatedProducts() - { - static $result; - - if (null === $result) { - $result = Database::getInstance()->query( - "SELECT COUNT(*) AS total FROM tl_iso_product WHERE language!=''" - )->total; - } - - return $result; - } - - /** - * Return a model or collection based on the database result type - * - * @param array $arrOptions - * - * @return Product|Product[]|Collection|null - */ - protected static function find(array $arrOptions) - { - $arrOptions['group'] = static::getTable() . '.id' . (null === ($arrOptions['group'] ?? null) ? '' : ', '.$arrOptions['group']); - - $objProducts = parent::find($arrOptions); - - if (null === $objProducts) { - return null; - } - - /** @var Filter[] $arrFilters */ - $arrFilters = $arrOptions['filters'] ?? null; - $arrSorting = $arrOptions['sorting'] ?? null; - - $hasFilters = \is_array($arrFilters) && 0 !== \count($arrFilters); - $hasSorting = \is_array($arrSorting) && 0 !== \count($arrSorting); - - if ($hasFilters || $hasSorting) { - - /** @var static[] $arrProducts */ - $arrProducts = $objProducts->getModels(); - - if ($hasFilters) { - $arrProducts = array_filter($arrProducts, function ($objProduct) use ($arrFilters) { - $arrGroups = []; - - foreach ($arrFilters as $objFilter) { - $blnMatch = $objFilter->matches($objProduct); - - if ($objFilter->hasGroup()) { - $arrGroups[$objFilter->getGroup()] = $arrGroups[$objFilter->getGroup()] ? : $blnMatch; - } elseif (!$blnMatch) { - return false; - } - } - - return !\in_array(false, $arrGroups, true); - }); - } - - // $arrProducts can be empty if the filter removed all records - if ($hasSorting && 0 !== \count($arrProducts)) { - $arrParam = array(); - $arrData = array(); - - foreach ($arrSorting as $strField => $arrConfig) { - foreach ($arrProducts as $objProduct) { - - // Both SORT_STRING and SORT_REGULAR are case sensitive, strings starting with a capital letter - // will come before strings starting with a lowercase letter. To perform a case insensitive - // search, force the sorting order to be determined by a lowercase copy of the original value. - - // Temporary fix for price attribute (see #945) - if ('price' === $strField) { - if (null !== $objProduct->getPrice()) { - $arrData[$strField][$objProduct->id] = $objProduct->getPrice()->getAmount(); - } else { - $arrData[$strField][$objProduct->id] = 0; - } - } else { - $arrData[$strField][$objProduct->id] = strtolower( - str_replace('"', '', $objProduct->$strField) - ); - } - } - - $arrParam[] = &$arrData[$strField]; - $arrParam[] = $arrConfig[0]; - $arrParam[] = $arrConfig[1]; - } - - // Add product array as the last item. - // This will sort the products array based on the sorting of the passed in arguments. - $arrParam[] = &$arrProducts; - \call_user_func_array('array_multisort', $arrParam); - } - - $objProducts = new Collection($arrProducts, static::$strTable); - } - - return $objProducts; - } - - /** - * Return select statement to load product data including multilingual fields - * - * @param array $arrOptions an array of columns - * - * @return string - */ - protected static function buildFindQuery(array $arrOptions) - { - $objBase = DcaExtractor::getInstance($arrOptions['table']); - $hasTranslations = (static::countTranslatedProducts() > 0); - $hasVariants = (ProductType::countByVariants() > 0); - - $arrJoins = array(); - $arrFields = array( - $arrOptions['table'] . '.*', - "'" . str_replace('-', '_', $GLOBALS['TL_LANGUAGE']) . "' AS language", - ); - - if ($hasVariants) { - $arrFields[] = sprintf( - 'IF(%s.pid>0, parent.type, %s.type) AS type', - $arrOptions['table'], - $arrOptions['table'] - ); - } - - if ($hasTranslations) { - foreach (Attribute::getMultilingualFields() as $attribute) { - $arrFields[] = "IFNULL(translation.$attribute, " . $arrOptions['table'] . ".$attribute) AS $attribute"; - } - } - - foreach (Attribute::getFetchFallbackFields() as $attribute) { - $arrFields[] = "{$arrOptions['table']}.$attribute AS {$attribute}_fallback"; - } - - if ($hasTranslations) { - $arrJoins[] = sprintf( - " LEFT OUTER JOIN %s translation ON %s.id=translation.pid AND translation.language='%s'", - $arrOptions['table'], - $arrOptions['table'], - str_replace('-', '_', $GLOBALS['TL_LANGUAGE']) - ); - - $arrOptions['group'] = (null === $arrOptions['group'] ? '' : $arrOptions['group'].', ') . 'translation.id'; - } - - if ($hasVariants) { - $arrJoins[] = sprintf( - ' LEFT OUTER JOIN %s parent ON %s.pid=parent.id', - $arrOptions['table'], - $arrOptions['table'] - ); - } - - $arrFields[] = 'GROUP_CONCAT(c.page_id) AS product_categories'; - $arrJoins[] = sprintf( - ' LEFT OUTER JOIN %s c ON %s=c.pid', - ProductCategory::getTable(), - ($hasVariants ? "IFNULL(parent.id, {$arrOptions['table']}.id)" : "{$arrOptions['table']}.id") - ); - - if ('c.sorting' === ($arrOptions['order'] ?? '')) { - $arrFields[] = 'c.sorting'; - - $arrOptions['group'] = (null === $arrOptions['group'] ? '' : $arrOptions['group'].', ') . 'c.id'; - } - - if ($objBase->hasRelations()) { - $intCount = 0; - - foreach ($objBase->getRelations() as $strKey => $arrConfig) { - // Automatically join the single-relation records - if (('eager' === $arrConfig['load'] || ($arrOptions['eager'] ?? false)) - && ('hasOne' === $arrConfig['type'] || 'belongsTo' === $arrConfig['type']) - ) { - if (\is_array($arrOptions['joinAliases'] ?? null) - && ($key = array_search($arrConfig['table'], $arrOptions['joinAliases'], true)) !== false - ) { - $strJoinAlias = $key; - unset($arrOptions['joinAliases'][$key]); - } else { - ++$intCount; - $strJoinAlias = 'j' . $intCount; - } - - $objRelated = DcaExtractor::getInstance($arrConfig['table']); - - foreach ($objRelated->getFields() as $strField => $config) { - $arrFields[] = $strJoinAlias . '.' . $strField . ' AS ' . $strKey . '__' . $strField; - } - - $arrJoins[] = sprintf( - ' LEFT JOIN %s %s ON %s.%s=%s.id', - $arrConfig['table'], - $strJoinAlias, - $arrOptions['table'], - $strKey, - $strJoinAlias - ); - } - } - } - - // Generate the query - $strQuery = 'SELECT ' . implode(', ', $arrFields) . ' FROM ' . $arrOptions['table'] . implode('', $arrJoins); - - // Where condition - if (!\is_array($arrOptions['column'] ?? null)) { - $arrOptions['column'] = array($arrOptions['table'] . '.' . $arrOptions['column'] . '=?'); - } - - // The model must never find a language record - $strQuery .= " WHERE {$arrOptions['table']}.language='' AND " . implode(' AND ', $arrOptions['column']); - - // Group by - if (($arrOptions['group'] ?? null) !== null) { - $strQuery .= ' GROUP BY ' . $arrOptions['group']; - } - - // Order by - if (($arrOptions['order'] ?? null) !== null) { - $strQuery .= ' ORDER BY ' . $arrOptions['order']; - } - - return $strQuery; - } - - /** - * Build a query based on the given options to count the number of products. - * - * @param array $arrOptions The options array - * - * @return string - */ - protected static function buildCountQuery(array $arrOptions) - { - $hasTranslations = (static::countTranslatedProducts() > 0); - $hasVariants = (ProductType::countByVariants() > 0); - - $arrJoins = array(); - $arrFields = array( - $arrOptions['table'] . '.id', - "'" . str_replace('-', '_', $GLOBALS['TL_LANGUAGE']) . "' AS language", - ); - - if ($hasVariants) { - $arrFields[] = sprintf( - 'IF(%s.pid>0, parent.type, %s.type) AS type', - $arrOptions['table'], - $arrOptions['table'] - ); - } - - if ($hasTranslations) { - foreach (Attribute::getMultilingualFields() as $attribute) { - $arrFields[] = "IFNULL(translation.$attribute, " . $arrOptions['table'] . ".$attribute) AS $attribute"; - } - } - - if ($hasTranslations) { - $arrJoins[] = sprintf( - " LEFT OUTER JOIN %s translation ON %s.id=translation.pid AND translation.language='%s'", - $arrOptions['table'], - $arrOptions['table'], - str_replace('-', '_', $GLOBALS['TL_LANGUAGE']) - ); - - $arrOptions['group'] = (null === $arrOptions['group'] ? '' : $arrOptions['group'].', ') . 'translation.id, tl_iso_product.id'; - } - - if ($hasVariants) { - $arrJoins[] = sprintf( - ' LEFT OUTER JOIN %s parent ON %s.pid=parent.id', - $arrOptions['table'], - $arrOptions['table'] - ); - } - - $arrJoins[] = sprintf( - ' LEFT OUTER JOIN %s c ON %s=c.pid', - ProductCategory::getTable(), - ($hasVariants ? "IFNULL(parent.id, {$arrOptions['table']}.id)" : "{$arrOptions['table']}.id") - ); - - // Generate the query - $strWhere = ''; - $strQuery = ' - SELECT - ' . implode(', ', $arrFields) . ' - FROM ' . $arrOptions['table'] . implode('', $arrJoins); - - // Where condition - if (!empty($arrOptions['column'])) { - if (!\is_array($arrOptions['column'])) { - $arrOptions['column'] = array($arrOptions['table'] . '.' . $arrOptions['column'] . '=?'); - } - - $strWhere = ' AND ' . implode(' AND ', $arrOptions['column']); - } - - // The model must never find a language record - $strQuery .= " WHERE {$arrOptions['table']}.language=''" . $strWhere; - - // Group by - if ($arrOptions['group'] !== null) { - $strQuery .= ' GROUP BY ' . $arrOptions['group']; - } - - return 'SELECT COUNT(*) AS count FROM ('.$strQuery.') c1'; - } - - /** - * Return select statement to load product data including multilingual fields - * - * @param array $arrOptions an array of columns - * @param array $arrJoinAliases an array of table join aliases - * - * @return string - * - * @deprecated use buildFindQuery introduced in Contao 3.3 - */ - protected static function buildQueryString($arrOptions, $arrJoinAliases = array('t' => 'tl_iso_producttype')) - { - $arrOptions['joinAliases'] = $arrJoinAliases; - - return static::buildFindQuery((array) $arrOptions); - } -} diff --git a/api2/ProductCollection.php b/api2/ProductCollection.php deleted file mode 100644 index 0e8b41d..0000000 --- a/api2/ProductCollection.php +++ /dev/null @@ -1,2097 +0,0 @@ -arrData['uniqid'] = $this->generateUniqueId(); - - // Do not use __destruct, because Database object might be destructed first - // see http://github.com/contao/core/issues/2236 - if ('FE' === TL_MODE) { - register_shutdown_function(array($this, 'updateDatabase'), false); - } - } - - /** - * Prevent cloning because we can't copy items etc. - * - * @throws \LogicException because ProductCollection cannot be cloned - */ - /** @noinspection MagicMethodsValidityInspection */ - public function __clone() - { - throw new \LogicException( - 'Product collections can\'t be cloned, you should probably use ProductCollection::createFromCollection' - ); - } - - /** - * Shutdown function to update prices of items and collection - * - * @param boolean $blnCreate If true create Model even if not in registry or not saved at all - */ - public function updateDatabase($blnCreate = true) - { - if (!$this->blnPreventSaving - && !$this->isLocked() - && (Registry::getInstance()->isRegistered($this) || $blnCreate) - ) { - foreach ($this->getItems() as $objItem) { - if (!$objItem->hasProduct()) { - continue; - } - - $objItem->price = $objItem->getPrice(); - $objItem->tax_free_price = $objItem->getTaxFreePrice(); - $objItem->save(); - } - - // First call to __set for tstamp will truncate the cache - $this->tstamp = time(); - $this->subtotal = $this->getSubtotal(); - $this->tax_free_subtotal = $this->getTaxFreeSubtotal(); - $this->total = $this->getTotal(); - $this->tax_free_total = $this->getTaxFreeTotal(); - $this->currency = (string) $this->getConfig()->currency; - - $this->save(); - } - } - - /** - * Mark a field as modified - * - * @param string $strKey The field key - */ - public function markModified($strKey) - { - if ('locked' === $strKey) { - throw new \InvalidArgumentException('Cannot change lock status of collection'); - } - - if ('document_number' === $strKey) { - throw new \InvalidArgumentException( - 'Cannot change document number of a collection, must be generated using generateDocumentNumber()' - ); - } - - $this->clearCache(); - - parent::markModified($strKey); - } - - /** - * @inheritdoc - */ - public function getId() - { - return (int) $this->id; - } - - /** - * @inheritdoc - */ - public function getUniqueId() - { - return $this->uniqid; - } - - /** - * @inheritdoc - */ - public function getMember() - { - if (0 === (int) $this->member) { - return null; - } - - return MemberModel::findByPk($this->member); - } - - /** - * @inheritdoc - */ - public function getStoreId() - { - return (int) $this->store_id; - } - - /** - * @inheritdoc - */ - public function getConfig() - { - try { - return $this->getRelated('config_id'); - } catch (\Exception $e) { - return null; - } - } - - /** - * @inheritdoc - */ - public function isLocked() - { - return null !== $this->locked; - } - - /** - * @inheritdoc - */ - public function getLockTime() - { - return $this->locked; - } - - - /** - * @inheritdoc - */ - public function isEmpty() - { - return 0 === \count($this->getItems()); - } - - /** - * Return payment method for this collection - * - * @return IsotopePayment|null - */ - public function getPaymentMethod() - { - if (false === $this->objPayment) { - try { - $this->objPayment = $this->getRelated('payment_id'); - } catch (\Exception $e) { - $this->objPayment = null; - } - } - - return $this->objPayment; - } - - /** - * Set payment method for this collection - * - * @param IsotopePayment $objPayment - */ - public function setPaymentMethod(IsotopePayment $objPayment = null) - { - $this->payment_id = (null === $objPayment ? 0 : $objPayment->getId()); - $this->objPayment = $objPayment; - } - - /** - * Return surcharge for current payment method - * - * @return ProductCollectionSurcharge|null - */ - public function getPaymentSurcharge() - { - return $this->hasPayment() ? $this->getPaymentMethod()->getSurcharge($this) : null; - } - - /** - * Return boolean whether collection has payment - * - * @return bool - */ - public function hasPayment() - { - return null !== $this->getPaymentMethod(); - } - - /** - * Return boolean whether collection requires payment - * - * @return bool - */ - public function requiresPayment() - { - return $this->getTotal() > 0; - } - - /** - * Return shipping method for this collection - * - * @return IsotopeShipping|null - */ - public function getShippingMethod() - { - if (false === $this->objShipping) { - try { - $this->objShipping = $this->getRelated('shipping_id'); - } catch (\Exception $e) { - $this->objShipping = null; - } - } - - return $this->objShipping; - } - - /** - * Set shipping method for this collection - * - * @param IsotopeShipping $objShipping - */ - public function setShippingMethod(IsotopeShipping $objShipping = null) - { - $this->shipping_id = (null === $objShipping ? 0 : $objShipping->getId()); - $this->objShipping = $objShipping; - } - - /** - * Return surcharge for current shipping method - * - * @return ProductCollectionSurcharge|null - */ - public function getShippingSurcharge() - { - return $this->hasShipping() ? $this->getShippingMethod()->getSurcharge($this) : null; - } - - /** - * Return boolean whether collection has shipping - * - * @return bool - */ - public function hasShipping() - { - return null !== $this->getShippingMethod(); - } - - /** - * Return boolean whether collection requires shipping - * - * @return bool - */ - public function requiresShipping() - { - if (!isset($this->arrCache['requiresShipping'])) { - $this->arrCache['requiresShipping'] = false; - $arrItems = $this->getItems(); - - foreach ($arrItems as $objItem) { - if ($objItem->hasProduct() && !$objItem->getProduct()->isExemptFromShipping()) { - $this->arrCache['requiresShipping'] = true; - break; - } - } - } - - return $this->arrCache['requiresShipping']; - } - - /** - * Get billing address for collection - * - * @return \Isotope\Model\Address|null - */ - public function getBillingAddress() - { - if (!$this->billing_address_id) { - return null; - } - - return $this->getRelated('billing_address_id'); - } - - /** - * Set billing address for collection - * - * @param Address $objAddress - */ - public function setBillingAddress(Address $objAddress = null) - { - if (null === $objAddress || $objAddress->id < 1) { - $this->billing_address_id = 0; - } else { - $this->billing_address_id = $objAddress->id; - } - } - - /** - * Return boolean whether collection requires a shipping address - * - * @return bool - */ - public function requiresShippingAddress() - { - if (!$this->requiresShipping()) { - return false; - } - - if (!isset($this->arrCache['requiresShippingAddress'])) { - $this->arrCache['requiresShippingAddress'] = true; - $arrItems = $this->getItems(); - - foreach ($arrItems as $objItem) { - $product = $objItem->getProduct(); - if ($product instanceof IsotopeProduct && \method_exists($product, 'isPickupOnly') && $product->isPickupOnly()) { - $this->arrCache['requiresShippingAddress'] = false; - break; - } - } - } - - return $this->arrCache['requiresShippingAddress']; - } - - /** - * Get shipping address for collection - * - * @return Address|null - */ - public function getShippingAddress() - { - if (!$this->shipping_address_id || !$this->requiresShippingAddress()) { - return null; - } - - return $this->getRelated('shipping_address_id'); - } - - /** - * Set shipping address for collection - * - * @param Address $objAddress - */ - public function setShippingAddress(Address $objAddress = null) - { - if (null === $objAddress || $objAddress->id < 1) { - $this->shipping_address_id = 0; - } else { - $this->shipping_address_id = $objAddress->id; - } - } - - /** - * Returns the generated document number or empty string if not available. - * - * @return string - */ - public function getDocumentNumber() - { - return (string) $this->arrData['document_number']; - } - - /** - * Return customer email address for the collection - * - * @return string - */ - public function getEmailRecipient() - { - $strName = ''; - $strEmail = ''; - $objBillingAddress = $this->getBillingAddress(); - $objShippingAddress = $this->getShippingAddress(); - - if ($objBillingAddress->email != '') { - $strName = $objBillingAddress->firstname . ' ' . $objBillingAddress->lastname; - $strEmail = $objBillingAddress->email; - } elseif ($objShippingAddress->email != '') { - $strName = $objShippingAddress->firstname . ' ' . $objShippingAddress->lastname; - $strEmail = $objShippingAddress->email; - } elseif ($this->member > 0 - && ($objMember = MemberModel::findByPk($this->member)) !== null - && $objMember->email != '' - ) { - $strName = $objMember->firstname . ' ' . $objMember->lastname; - $strEmail = $objMember->email; - } - - if (trim($strName) != '') { - // Romanize friendly name to prevent email issues - $strName = html_entity_decode($strName, ENT_QUOTES, $GLOBALS['TL_CONFIG']['characterSet']); - $strName = StringUtil::stripInsertTags($strName); - $strName = utf8_romanize($strName); - $strName = preg_replace('/[^A-Za-z0-9.!#$%&\'*+-\/=?^_ `{|}~]+/i', '_', $strName); - - $strEmail = sprintf('"%s" <%s>', $strName, $strEmail); - } - - // !HOOK: determine email recipient for collection - if (isset($GLOBALS['ISO_HOOKS']['emailRecipientForCollection']) - && \is_array($GLOBALS['ISO_HOOKS']['emailRecipientForCollection']) - ) { - foreach ($GLOBALS['ISO_HOOKS']['emailRecipientForCollection'] as $callback) { - $strEmail = System::importStatic($callback[0])->{$callback[1]}($strEmail, $this); - } - } - - return $strEmail; - } - - /** - * Return number of items in the collection - * - * @return int - */ - public function countItems() - { - if (!isset($this->arrCache['countItems'])) { - $this->arrCache['countItems'] = ProductCollectionItem::countBy('pid', (int) $this->id); - } - - return $this->arrCache['countItems']; - } - - /** - * Return summary of item quantity in collection - * - * @return int - */ - public function sumItemsQuantity() - { - if (!isset($this->arrCache['sumItemsQuantity'])) { - $this->arrCache['sumItemsQuantity'] = ProductCollectionItem::sumBy('quantity', 'pid', (int) $this->id); - } - - return $this->arrCache['sumItemsQuantity']; - } - - /** - * Load settings from database field - * - * @param array $arrData - * - * @return $this - */ - public function setRow(array $arrData) - { - parent::setRow($arrData); - - // Merge settings into arrData, save() will move the values back - $this->arrData = array_merge(StringUtil::deserialize($arrData['settings'] ?? [], true), $this->arrData); - - return $this; - } - - /** - * Save all non-database fields in the settings array - * - * @return $this - */ - public function save() - { - // The instance cannot be saved - if ($this->blnPreventSaving) { - throw new \LogicException('The model instance has been detached and cannot be saved'); - } - - // !HOOK: additional functionality when saving a collection - if (isset($GLOBALS['ISO_HOOKS']['saveCollection']) && \is_array($GLOBALS['ISO_HOOKS']['saveCollection'])) { - foreach ($GLOBALS['ISO_HOOKS']['saveCollection'] as $callback) { - System::importStatic($callback[0])->{$callback[1]}($this); - } - } - - $arrDbFields = Database::getInstance()->getFieldNames(static::$strTable); - $arrModified = array_diff_key($this->arrModified, array_flip($arrDbFields)); - - if (!empty($arrModified)) { - $arrSettings = StringUtil::deserialize($this->settings, true); - $arrSettings = array_merge($arrSettings, array_intersect_key($this->arrData, $arrModified)); - - $this->settings = serialize($arrSettings); - } - - return parent::save(); - } - - /** - * Also delete child table records when dropping this collection - * - * @param bool $blnForce Force to delete the collection even if it's locked - * - * @return int Number of rows affected - * - * @throws \BadMethodCallException if the product collection is locked. - */ - public function delete($blnForce = false) - { - if (!$blnForce) { - $this->ensureNotLocked(); - - // !HOOK: additional functionality when deleting a collection - if (isset($GLOBALS['ISO_HOOKS']['deleteCollection']) - && \is_array($GLOBALS['ISO_HOOKS']['deleteCollection']) - ) { - foreach ($GLOBALS['ISO_HOOKS']['deleteCollection'] as $callback) { - $blnRemove = System::importStatic($callback[0])->{$callback[1]}($this); - - if ($blnRemove === false) { - return 0; - } - } - } - } - - $intPid = $this->id; - $intAffectedRows = parent::delete(); - - if ($intAffectedRows > 0 && $intPid > 0) { - Database::getInstance()->query(" - DELETE FROM tl_iso_product_collection_download - WHERE pid IN (SELECT id FROM tl_iso_product_collection_item WHERE pid=$intPid) - "); - Database::getInstance()->query( - "DELETE FROM tl_iso_product_collection_item WHERE pid=$intPid" - ); - Database::getInstance()->query( - "DELETE FROM tl_iso_product_collection_surcharge WHERE pid=$intPid" - ); - Database::getInstance()->query( - "DELETE FROM tl_iso_address WHERE ptable='" . static::$strTable . "' AND pid=$intPid" - ); - } - - $this->arrCache = array(); - $this->arrItems = null; - $this->arrSurcharges = null; - - // !HOOK: additional functionality when deleting a collection - if (isset($GLOBALS['ISO_HOOKS']['postDeleteCollection']) - && \is_array($GLOBALS['ISO_HOOKS']['postDeleteCollection']) - ) { - foreach ($GLOBALS['ISO_HOOKS']['postDeleteCollection'] as $callback) { - System::importStatic($callback[0])->{$callback[1]}($this, $intPid); - } - } - - return $intAffectedRows; - } - - /** - * Delete all products in the collection - * - * @throws \BadMethodCallException if the product collection is locked. - */ - public function purge() - { - $this->ensureNotLocked(); - - foreach ($this->getItems() as $objItem) { - $this->deleteItem($objItem); - } - - foreach ($this->getSurcharges() as $objSurcharge) { - if ($objSurcharge->id) { - $objSurcharge->delete(); - } - } - - $this->clearCache(); - } - - /** - * Lock collection from begin modified - * - * @throws \BadMethodCallException if the product collection is locked. - */ - public function lock() - { - $this->ensureNotLocked(); - - global $objPage; - $time = time(); - - $this->pageId = (int) $objPage->id; - $this->language = (string) $GLOBALS['TL_LANGUAGE']; - - $this->createPrivateAddresses(); - $this->updateDatabase(); - - // Add surcharges to the collection - $sorting = 128; - foreach ($this->getSurcharges() as $objSurcharge) { - $objSurcharge->pid = $this->id; - $objSurcharge->tstamp = $time; - $objSurcharge->sorting = $sorting; - $objSurcharge->save(); - - $sorting += 128; - } - - // Add downloads from products to the collection - foreach (ProductCollectionDownload::createForProductsInCollection($this) as $objDownload) { - $objDownload->save(); - } - - // Can't use model, it would not save as soon as it's locked - Database::getInstance()->query( - "UPDATE tl_iso_product_collection SET locked=$time WHERE id=" . $this->id - ); - $this->arrData['locked'] = $time; - - // !HOOK: pre-process checkout - if (isset($GLOBALS['ISO_HOOKS']['collectionLocked']) && \is_array($GLOBALS['ISO_HOOKS']['collectionLocked'])) { - foreach ($GLOBALS['ISO_HOOKS']['collectionLocked'] as $callback) { - System::importStatic($callback[0])->{$callback[1]}($this); - } - } - - $this->clearCache(); - } - - /** - * Sum total price of all items in the collection - * - * @return float - */ - public function getSubtotal() - { - if ($this->isLocked()) { - return $this->subtotal; - } - - if (!isset($this->arrCache['subtotal'])) { - $fltAmount = 0; - $arrItems = $this->getItems(); - - foreach ($arrItems as $objItem) { - $varPrice = $objItem->getTotalPrice(); - - if ($varPrice !== null) { - $fltAmount += $varPrice; - } - } - - $this->arrCache['subtotal'] = $fltAmount; - } - - return $this->arrCache['subtotal']; - } - - /** - * Sum total tax free price of all items in the collection - * - * @return float - */ - public function getTaxFreeSubtotal() - { - if ($this->isLocked()) { - return $this->tax_free_subtotal; - } - - if (!isset($this->arrCache['taxFreeSubtotal'])) { - $fltAmount = 0; - $arrItems = $this->getItems(); - - foreach ($arrItems as $objItem) { - $varPrice = $objItem->getTaxFreeTotalPrice(); - - if ($varPrice !== null) { - $fltAmount += $varPrice; - } - } - - $this->arrCache['taxFreeSubtotal'] = $fltAmount; - } - - return $this->arrCache['taxFreeSubtotal']; - } - - /** - * Sum total price of items and surcharges - * - * @return float - */ - public function getTotal() - { - if ($this->isLocked()) { - return $this->total; - } - - if (!isset($this->arrCache['total'])) { - $fltAmount = $this->getSubtotal(); - $arrSurcharges = $this->getSurcharges(); - - foreach ($arrSurcharges as $objSurcharge) { - if ($objSurcharge->addToTotal) { - $fltAmount += $objSurcharge->total_price; - } - } - - $this->arrCache['total'] = $fltAmount > 0 ? $fltAmount : 0; - } - - return $this->arrCache['total']; - } - - /** - * Sum tax free total of items and surcharges - * - * @return float - */ - public function getTaxFreeTotal() - { - if ($this->isLocked()) { - return $this->tax_free_total; - } - - if (!isset($this->arrCache['taxFreeTotal'])) { - $arrSurcharges = $this->getSurcharges(); - - if (Config::PRICE_DISPLAY_GROSS === $this->getConfig()->priceDisplay) { - $fltAmount = $this->getTotal(); - - foreach ($arrSurcharges as $objSurcharge) { - if ($objSurcharge instanceof Tax) { - $fltAmount -= $objSurcharge->total_price; - } - } - } else { - $fltAmount = $this->getTaxFreeSubtotal(); - - foreach ($arrSurcharges as $objSurcharge) { - if ($objSurcharge->addToTotal) { - $fltAmount += $objSurcharge->tax_free_total_price; - } - } - } - - $this->arrCache['taxFreeTotal'] = $fltAmount > 0 ? Isotope::roundPrice($fltAmount) : 0; - } - - return $this->arrCache['taxFreeTotal']; - } - - /** - * @inheritdoc - */ - public function getCurrency() - { - return $this->currency; - } - - /** - * Return the item with the latest timestamp (e.g. the latest added item) - * - * @return ProductCollectionItem|null - */ - public function getLatestItem() - { - if (!isset($this->arrCache['latestItem'])) { - $latest = 0; - $arrItems = $this->getItems(); - - foreach ($arrItems as $objItem) { - if ($objItem->tstamp > $latest) { - $this->arrCache['latestItem'] = $objItem; - $latest = $objItem->tstamp; - } - } - } - - return $this->arrCache['latestItem']; - } - - /** - * Return timestamp when this collection was created - * This is relevant for price calculation - * - * @return int - */ - public function getLastModification() - { - if ($this->isLocked()) { - return $this->locked; - } - - return $this->tstamp ? : time(); - } - - /** - * Return all items in the collection - * - * @param callable $varCallable - * @param bool $blnNoCache - * - * @return ProductCollectionItem[] - */ - public function getItems($varCallable = null, $blnNoCache = false) - { - if (null === $this->arrItems || $blnNoCache) { - $this->arrItems = array(); - - if (($objItems = ProductCollectionItem::findBy('pid', $this->id)) !== null) { - /** @var ProductCollectionItem $objItem */ - foreach ($objItems as $objItem) { - if ($this->isLocked()) { - $objItem->lock(); - } - - // Add error message for items no longer available - if (!$objItem->isAvailable() && !$objItem->hasErrors()) { - $objItem->addError($GLOBALS['TL_LANG']['ERR']['collectionItemNotAvailable']); - } - - $this->arrItems[$objItem->id] = $objItem; - } - } - } - - if ($varCallable === null) { - return $this->arrItems; - } - - // not allowed to chance items - $arrItems = $this->arrItems; - - return \call_user_func($varCallable, $arrItems); - } - - /** - * Search item for a specific product - * - * @param IsotopeProduct $objProduct - * - * @return ProductCollectionItem|null - */ - public function getItemForProduct(IsotopeProduct $objProduct) - { - $strClass = array_search(\get_class($objProduct), Product::getModelTypes(), true); - - $objItem = ProductCollectionItem::findOneBy( - array('pid=?', 'type=?', 'product_id=?', 'configuration=?'), - array($this->id, $strClass, $objProduct->getId(), serialize($objProduct->getOptions())) - ); - - return $objItem; - } - - /** - * Gets the product collection with given ID if it belongs to this collection. - * - * @param int $id - * - * @return ProductCollectionItem|null - */ - public function getItemById($id) - { - $items = $this->getItems(); - - if (!isset($items[$id])) { - return null; - } - - return $items[$id]; - } - - /** - * Check if a given product is already in the collection - * - * @param IsotopeProduct $objProduct - * @param bool $blnIdentical - * - * @return bool - */ - public function hasProduct(IsotopeProduct $objProduct, $blnIdentical = true) - { - if (true === $blnIdentical) { - return null !== $this->getItemForProduct($objProduct); - } - - $intId = $objProduct->getProductId(); - - foreach ($this->getItems() as $objItem) { - if ($objItem->hasProduct() - && ($objItem->getProduct()->getId() == $intId || $objItem->getProduct()->getProductId() == $intId) - ) { - return true; - } - } - - return false; - } - - /** - * Add a product to the collection - * - * @param IsotopeProduct $objProduct - * @param int $intQuantity - * @param array $arrConfig - * - * @return ProductCollectionItem|false - */ - public function addProduct(IsotopeProduct $objProduct, $intQuantity, array $arrConfig = array()) - { - // !HOOK: additional functionality when adding product to collection - if (isset($GLOBALS['ISO_HOOKS']['addProductToCollection']) - && \is_array($GLOBALS['ISO_HOOKS']['addProductToCollection']) - ) { - foreach ($GLOBALS['ISO_HOOKS']['addProductToCollection'] as $callback) { - $intQuantity = System::importStatic($callback[0])->{$callback[1]}($objProduct, $intQuantity, $this, $arrConfig); - } - } - - if ($intQuantity == 0) { - return false; - } - - $time = time(); - $this->tstamp = $time; - - // Make sure collection is in DB before adding product - if (!Registry::getInstance()->isRegistered($this)) { - $this->save(); - } - - // Remove uploaded files from session so they are not added to the next product (see #646) - unset($_SESSION['FILES']); - - $objItem = $this->getItemForProduct($objProduct); - $intMinimumQuantity = $objProduct->getMinimumQuantity(); - - if (null !== $objItem) { - if (($objItem->quantity + $intQuantity) < $intMinimumQuantity) { - Message::addInfo(sprintf( - $GLOBALS['TL_LANG']['ERR']['productMinimumQuantity'], - $objProduct->getName(), - $intMinimumQuantity - )); - $intQuantity = $intMinimumQuantity - $objItem->quantity; - } - - $objItem->increaseQuantityBy($intQuantity); - } else { - if ($intQuantity < $intMinimumQuantity) { - Message::addInfo(sprintf( - $GLOBALS['TL_LANG']['ERR']['productMinimumQuantity'], - $objProduct->getName(), - $intMinimumQuantity - )); - $intQuantity = $intMinimumQuantity; - } - - $objItem = new ProductCollectionItem(); - $objItem->pid = $this->id; - $objItem->jumpTo = isset($arrConfig['jumpTo']) ? (int) $arrConfig['jumpTo']->id : 0; - - $this->setProductForItem($objProduct, $objItem, $intQuantity); - $objItem->save(); - - // Add the new item to our cache - $this->arrItems[$objItem->id] = $objItem; - } - - // !HOOK: additional functionality when adding product to collection - if (isset($GLOBALS['ISO_HOOKS']['postAddProductToCollection']) - && \is_array($GLOBALS['ISO_HOOKS']['postAddProductToCollection']) - ) { - foreach ($GLOBALS['ISO_HOOKS']['postAddProductToCollection'] as $callback) { - System::importStatic($callback[0])->{$callback[1]}($objItem, $intQuantity, $this, $arrConfig); - } - } - - return $objItem; - } - - /** - * Update product details for a collection item. - * - * @param IsotopeProduct $objProduct - * @param ProductCollectionItem $objItem - * - * @return bool - */ - public function updateProduct(IsotopeProduct $objProduct, ProductCollectionItem $objItem) - { - if ($objItem->pid != $this->id) { - throw new \InvalidArgumentException('Item does not belong to this collection'); - } - - // !HOOK: additional functionality when updating product in collection - if (isset($GLOBALS['ISO_HOOKS']['updateProductInCollection']) - && \is_array($GLOBALS['ISO_HOOKS']['updateProductInCollection']) - ) { - foreach ($GLOBALS['ISO_HOOKS']['updateProductInCollection'] as $callback) { - if (false === System::importStatic($callback[0])->{$callback[1]}($objProduct, $objItem, $this)) { - return false; - } - } - } - - $this->setProductForItem($objProduct, $objItem, $objItem->quantity); - $objItem->save(); - - // !HOOK: additional functionality when adding product to collection - if (isset($GLOBALS['ISO_HOOKS']['postUpdateProductInCollection']) - && \is_array($GLOBALS['ISO_HOOKS']['postUpdateProductInCollection']) - ) { - foreach ($GLOBALS['ISO_HOOKS']['postUpdateProductInCollection'] as $callback) { - System::importStatic($callback[0])->{$callback[1]}($objProduct, $objItem, $this); - } - } - - return true; - } - - /** - * Update a product collection item - * - * @param ProductCollectionItem $objItem The product object - * @param array $arrSet The property(ies) to adjust - * - * @return bool - */ - public function updateItem(ProductCollectionItem $objItem, $arrSet) - { - return $this->updateItemById($objItem->id, $arrSet); - } - - /** - * Update product collection item with given ID - * - * @param int $intId - * @param array $arrSet - * - * @return bool - */ - public function updateItemById($intId, $arrSet) - { - $this->ensureNotLocked(); - - $arrItems = $this->getItems(); - - if (!isset($arrItems[$intId])) { - return false; - } - - /** @var ProductCollectionItem $objItem */ - $objItem = $arrItems[$intId]; - - // !HOOK: additional functionality when updating a product in the collection - if (isset($GLOBALS['ISO_HOOKS']['updateItemInCollection']) - && \is_array($GLOBALS['ISO_HOOKS']['updateItemInCollection']) - ) { - foreach ($GLOBALS['ISO_HOOKS']['updateItemInCollection'] as $callback) { - $arrSet = System::importStatic($callback[0])->{$callback[1]}($objItem, $arrSet, $this); - - if (!\is_array($arrSet) || 0 === \count($arrSet)) { - return false; - } - } - } - - // Quantity set to 0, delete item - if (isset($arrSet['quantity']) && $arrSet['quantity'] == 0) { - return $this->deleteItemById($intId); - } - - if (isset($arrSet['quantity']) && $objItem->hasProduct()) { - - // Set product quantity so we can determine the correct minimum price - $objProduct = $objItem->getProduct(); - $intMinimumQuantity = $objProduct->getMinimumQuantity(); - - if ($arrSet['quantity'] < $intMinimumQuantity) { - Message::addInfo(sprintf( - $GLOBALS['TL_LANG']['ERR']['productMinimumQuantity'], - $objProduct->getName(), - $intMinimumQuantity - )); - $arrSet['quantity'] = $intMinimumQuantity; - } - } - - $arrSet['tstamp'] = time(); - - foreach ($arrSet as $k => $v) { - $objItem->$k = $v; - } - - $objItem->save(); - $this->tstamp = time(); - - // !HOOK: additional functionality when adding product to collection - if (isset($GLOBALS['ISO_HOOKS']['postUpdateItemInCollection']) - && \is_array($GLOBALS['ISO_HOOKS']['postUpdateItemInCollection']) - ) { - foreach ($GLOBALS['ISO_HOOKS']['postUpdateItemInCollection'] as $callback) { - System::importStatic($callback[0])->{$callback[1]}($objItem, $arrSet['quantity'], $this); - } - } - - return true; - } - - /** - * Remove item from collection - * - * @param ProductCollectionItem $objItem - * - * @return bool - */ - public function deleteItem(ProductCollectionItem $objItem) - { - return $this->deleteItemById($objItem->id); - } - - /** - * Remove item with given ID from collection - * - * @param int $intId - * - * @return bool - * - * @throws \BadMethodCallException if the product collection is locked. - */ - public function deleteItemById($intId) - { - $this->ensureNotLocked(); - - $arrItems = $this->getItems(); - - if (!isset($arrItems[$intId])) { - return false; - } - - $objItem = $arrItems[$intId]; - - // !HOOK: additional functionality when a product is removed from the collection - if (isset($GLOBALS['ISO_HOOKS']['deleteItemFromCollection']) - && \is_array($GLOBALS['ISO_HOOKS']['deleteItemFromCollection']) - ) { - foreach ($GLOBALS['ISO_HOOKS']['deleteItemFromCollection'] as $callback) { - $blnRemove = System::importStatic($callback[0])->{$callback[1]}($objItem, $this); - - if ($blnRemove === false) { - return false; - } - } - } - - $objItem->delete(); - - unset($this->arrItems[$intId]); - - $this->tstamp = time(); - - // !HOOK: additional functionality when adding product to collection - if (isset($GLOBALS['ISO_HOOKS']['postDeleteItemFromCollection']) - && \is_array($GLOBALS['ISO_HOOKS']['postDeleteItemFromCollection']) - ) { - foreach ($GLOBALS['ISO_HOOKS']['postDeleteItemFromCollection'] as $callback) { - System::importStatic($callback[0])->{$callback[1]}($objItem, $this); - } - } - - return true; - } - - /** - * Find surcharges for the current collection - * - * @return ProductCollectionSurcharge[] - */ - public function getSurcharges() - { - if (null === $this->arrSurcharges) { - if ($this->isLocked()) { - $this->arrSurcharges = []; - - if (($objSurcharges = ProductCollectionSurcharge::findBy('pid', $this->id)) !== null) { - $this->arrSurcharges = $objSurcharges->getModels(); - } - } else { - $this->arrSurcharges = ProductCollectionSurcharge::findForCollection($this); - } - } - - return $this->arrSurcharges; - } - - /** - * Copy product collection items from another collection to this one (e.g. Cart to Order) - * - * @param IsotopeProductCollection $objSource - * - * @return int[] - * - * @throws \BadMethodCallException if the product collection is locked. - */ - public function copyItemsFrom(IsotopeProductCollection $objSource) - { - $this->ensureNotLocked(); - - $this->updateDatabase(); - - // Make sure database table has the latest prices - $objSource->updateDatabase(); - - $time = time(); - $arrIds = []; - $arrOldItems = $objSource->getItems(); - - foreach ($arrOldItems as $objOldItem) { - - // !HOOK: additional functionality when copying product to collection - if (isset($GLOBALS['ISO_HOOKS']['copyCollectionItem']) - && \is_array($GLOBALS['ISO_HOOKS']['copyCollectionItem']) - ) { - foreach ($GLOBALS['ISO_HOOKS']['copyCollectionItem'] as $callback) { - if (System::importStatic($callback[0])->{$callback[1]}($objOldItem, $objSource, $this) === false) { - continue; - } - } - } - - if ($objOldItem->hasProduct() && $this->hasProduct($objOldItem->getProduct())) { - - $objNewItem = $this->getItemForProduct($objOldItem->getProduct()); - $objNewItem->increaseQuantityBy($objOldItem->quantity); - - } else { - - $objNewItem = clone $objOldItem; - $objNewItem->pid = $this->id; - $objNewItem->tstamp = $time; - $objNewItem->save(); - } - - $arrIds[$objOldItem->id] = $objNewItem->id; - } - - if (\count($arrIds) > 0) { - $this->tstamp = $time; - } - - // !HOOK: additional functionality when adding product to collection - if (isset($GLOBALS['ISO_HOOKS']['copiedCollectionItems']) - && \is_array($GLOBALS['ISO_HOOKS']['copiedCollectionItems']) - ) { - foreach ($GLOBALS['ISO_HOOKS']['copiedCollectionItems'] as $callback) { - System::importStatic($callback[0])->{$callback[1]}($objSource, $this, $arrIds); - } - } - - $this->clearCache(); - - return $arrIds; - } - - /** - * Copy product collection surcharges from another collection to this one (e.g. Cart to Order) - * - * @param IsotopeProductCollection $objSource - * @param array $arrItemMap - * - * @return int[] - * - * @deprecated Deprecated since version 2.2, to be removed in 3.0. - * Surcharges are calculated on the fly, so it does not make sense to copy them from another one. - * - * @throws \BadMethodCallException if the product collection is locked. - */ - public function copySurchargesFrom(IsotopeProductCollection $objSource, array $arrItemMap = array()) - { - $this->ensureNotLocked(); - - $arrIds = array(); - $time = time(); - $sorting = 128; - - foreach ($objSource->getSurcharges() as $objSourceSurcharge) { - $objSurcharge = clone $objSourceSurcharge; - $objSurcharge->pid = $this->id; - $objSurcharge->tstamp = $time; - $objSurcharge->sorting = $sorting; - - // Convert surcharge amount for individual product IDs - $objSurcharge->convertCollectionItemIds($arrItemMap); - - $objSurcharge->save(); - - $arrIds[$sorting] = $objSurcharge->id; - - $sorting += 128; - } - - // Empty cache - $this->arrSurcharges = null; - $this->arrCache = null; - - return $arrIds; - } - - /** - * @inheritdoc - */ - public function addToScale(Scale $objScale = null) - { - if (null === $objScale) { - $objScale = new Scale(); - } - - foreach ($this->getItems() as $objItem) { - if (!$objItem->hasProduct()) { - continue; - } - - $objProduct = $objItem->getProduct(); - - if ($objProduct instanceof WeightAggregate) { - $objWeight = $objProduct->getWeight(); - - if (null !== $objWeight) { - for ($i = 0; $i < $objItem->quantity; $i++) { - $objScale->add($objWeight); - } - } - - } elseif ($objProduct instanceof Weighable) { - for ($i = 0; $i < $objItem->quantity; $i++) { - $objScale->add($objProduct); - } - } - } - - return $objScale; - } - - /** - * @inheritdoc - */ - public function addToTemplate(Template $objTemplate, array $arrConfig = []) - { - $arrGalleries = array(); - $objConfig = $this->getRelated('config_id') ?: Isotope::getConfig(); - $arrItems = $this->addItemsToTemplate($objTemplate, $arrConfig['sorting']); - - $objTemplate->id = $this->id; - $objTemplate->collection = $this; - $objTemplate->config = $objConfig; - $objTemplate->surcharges = Frontend::formatSurcharges($this->getSurcharges(), $objConfig->currency); - $objTemplate->subtotal = Isotope::formatPriceWithCurrency($this->getSubtotal(), true, $objConfig->currency); - $objTemplate->total = Isotope::formatPriceWithCurrency($this->getTotal(), true, $objConfig->currency); - $objTemplate->tax_free_subtotal = Isotope::formatPriceWithCurrency($this->getTaxFreeSubtotal(), true, $objConfig->currency); - $objTemplate->tax_free_total = Isotope::formatPriceWithCurrency($this->getTaxFreeTotal(), true, $objConfig->currency); - - $objTemplate->hasAttribute = function ($strAttribute, ProductCollectionItem $objItem) { - if (!$objItem->hasProduct()) { - return false; - } - - $objProduct = $objItem->getProduct(); - - return \in_array($strAttribute, $objProduct->getAttributes(), true) - || \in_array($strAttribute, $objProduct->getVariantAttributes(), true); - }; - - $objTemplate->generateAttribute = function ( - $strAttribute, - ProductCollectionItem $objItem, - array $arrOptions = array() - ) { - if (!$objItem->hasProduct()) { - return ''; - } - - $objAttribute = $GLOBALS['TL_DCA']['tl_iso_product']['attributes'][$strAttribute]; - - if (!($objAttribute instanceof IsotopeAttribute)) { - throw new \InvalidArgumentException($strAttribute . ' is not a valid attribute'); - } - - return $objAttribute->generate($objItem->getProduct(), $arrOptions); - }; - - $objTemplate->getGallery = function ( - $strAttribute, - ProductCollectionItem $objItem - ) use ( - $arrConfig, - &$arrGalleries - ) { - if (!$objItem->hasProduct()) { - return new StandardGallery(); - } - - $strCacheKey = 'product' . $objItem->product_id . '_' . $strAttribute; - $arrConfig['jumpTo'] = $objItem->getRelated('jumpTo'); - - if (!isset($arrGalleries[$strCacheKey])) { - $arrGalleries[$strCacheKey] = Gallery::createForProductAttribute( - $objItem->getProduct(), - $strAttribute, - $arrConfig - ); - } - - return $arrGalleries[$strCacheKey]; - }; - - $objTemplate->attributeLabel = function ($name, array $options = []) { - /** @var Attribute $attribute */ - $attribute = $GLOBALS['TL_DCA']['tl_iso_product']['attributes'][$name] ?? null; - - if (!$attribute instanceof IsotopeAttribute) { - return Format::dcaLabel('tl_iso_product', $name); - } - - return $attribute->getLabel($options); - }; - - $objTemplate->attributeValue = function ($name, $value, array $options = []) { - /** @var Attribute $attribute */ - $attribute = $GLOBALS['TL_DCA']['tl_iso_product']['attributes'][$name] ?? null; - - if (!$attribute instanceof IsotopeAttribute) { - return Format::dcaValue('tl_iso_product', $name, $value); - } - - return $attribute->generateValue($value, $options); - }; - - // !HOOK: allow overriding of the template - if (isset($GLOBALS['ISO_HOOKS']['addCollectionToTemplate']) - && \is_array($GLOBALS['ISO_HOOKS']['addCollectionToTemplate']) - ) { - foreach ($GLOBALS['ISO_HOOKS']['addCollectionToTemplate'] as $callback) { - System::importStatic($callback[0])->{$callback[1]}($objTemplate, $arrItems, $this, $arrConfig); - } - } - } - - /** - * @inheritdoc - */ - public function addError($message) - { - $this->arrErrors[] = $message; - } - - /** - * @inheritdoc - */ - public function hasErrors() - { - if (\count($this->arrErrors) > 0) { - return true; - } - - foreach ($this->getItems() as $objItem) { - if ($objItem->hasErrors()) { - return true; - } - } - - return false; - } - - /** - * @inheritdoc - */ - public function getErrors() - { - $arrErrors = $this->arrErrors; - - foreach ($this->getItems() as $objItem) { - if ($objItem->hasErrors()) { - array_unshift($arrErrors, $this->getMessageIfErrorsInItems()); - break; - } - } - - return $arrErrors; - } - - /** - * Loop over items and add them to template - * - * @param Callable $varCallable - * - * @return array - */ - protected function addItemsToTemplate(Template $objTemplate, $varCallable = null) - { - $taxIds = array(); - $arrItems = array(); - - foreach ($this->getItems($varCallable) as $objItem) { - $item = $this->generateItem($objItem); - - $taxIds[] = $item['tax_id']; - $arrItems[] = $item; - } - - RowClass::withKey('rowClass')->addCount('row_')->addFirstLast('row_')->addEvenOdd('row_')->applyTo($arrItems); - - $objTemplate->items = $arrItems; - $objTemplate->total_tax_ids = \count(array_unique($taxIds)); - - return $arrItems; - } - - /** - * Generate item array for template - * - * @param ProductCollectionItem $objItem - * - * @return array - */ - protected function generateItem(ProductCollectionItem $objItem) - { - $blnHasProduct = $objItem->hasProduct(); - $objProduct = $objItem->getProduct(); - $objConfig = $this->getRelated('config_id') ?: Isotope::getConfig(); - $arrCSS = ($blnHasProduct ? StringUtil::deserialize($objProduct->cssID, true) : array()); - - // Set the active product for insert tags replacement - if ($blnHasProduct) { - Product::setActive($objProduct); - } - - $arrItem = array( - 'id' => $objItem->id, - 'sku' => $objItem->getSku(), - 'name' => $objItem->getName(), - 'options' => Isotope::formatOptions($objItem->getOptions()), - 'configuration' => $objItem->getConfiguration(), - 'attributes' => $objItem->getAttributes(), - 'quantity' => $objItem->quantity, - 'price' => Isotope::formatPriceWithCurrency($objItem->getPrice(), true, $objConfig->currency), - 'tax_free_price' => Isotope::formatPriceWithCurrency($objItem->getTaxFreePrice(), true, $objConfig->currency), - 'original_price' => Isotope::formatPriceWithCurrency($objItem->getOriginalPrice(), true, $objConfig->currency), - 'total' => Isotope::formatPriceWithCurrency($objItem->getTotalPrice(), true, $objConfig->currency), - 'tax_free_total' => Isotope::formatPriceWithCurrency($objItem->getTaxFreeTotalPrice(), true, $objConfig->currency), - 'original_total' => Isotope::formatPriceWithCurrency($objItem->getTotalOriginalPrice(), true, $objConfig->currency), - 'tax_id' => $objItem->tax_id, - 'href' => false, - 'hasProduct' => $blnHasProduct, - 'product' => $objProduct, - 'item' => $objItem, - 'raw' => $objItem->row(), - 'rowClass' => trim('product ' . (($blnHasProduct && $objProduct->isNew()) ? 'new ' : '') . ($arrCSS[1] ?? '')) - ); - - if ($blnHasProduct && null !== $objItem->getRelated('jumpTo') && $objProduct->isAvailableInFrontend()) { - $arrItem['href'] = $objProduct->generateUrl($objItem->getRelated('jumpTo')); - } - - Product::unsetActive(); - - return $arrItem; - } - - /** - * Get a collection-specific error message for items with errors - * - * @return string - */ - protected function getMessageIfErrorsInItems() - { - return $GLOBALS['TL_LANG']['ERR']['collectionErrorInItems']; - } - - /** - * Generate the next higher Document Number based on existing records - * - * @param string $strPrefix - * @param int $intDigits - * - * @return string - * @throws \Exception - */ - protected function generateDocumentNumber($strPrefix, $intDigits) - { - if ($this->arrData['document_number'] != '') { - return $this->arrData['document_number']; - } - - // !HOOK: generate a custom order ID - if (isset($GLOBALS['ISO_HOOKS']['generateDocumentNumber']) - && \is_array($GLOBALS['ISO_HOOKS']['generateDocumentNumber']) - ) { - foreach ($GLOBALS['ISO_HOOKS']['generateDocumentNumber'] as $callback) { - $strOrderId = System::importStatic($callback[0])->{$callback[1]}($this, $strPrefix, $intDigits); - - if ($strOrderId !== false) { - $this->arrData['document_number'] = $strOrderId; - break; - } - } - } - - try { - if ($this->arrData['document_number'] == '') { - $strPrefix = Controller::replaceInsertTags($strPrefix, false); - $intPrefix = utf8_strlen($strPrefix); - - // Lock tables so no other order can get the same ID - Database::getInstance()->lockTables(array(static::$strTable => 'WRITE')); - - $prefixCondition = ($strPrefix != '' ? " AND document_number LIKE '$strPrefix%'" : ''); - - // Retrieve the highest available order ID - $objMax = Database::getInstance() - ->prepare(" - SELECT document_number - FROM tl_iso_product_collection - WHERE - type=? - $prefixCondition - AND store_id=? - ORDER BY CAST(" . ($strPrefix != '' ? 'SUBSTRING(document_number, ' . ($intPrefix + 1) . ')' : 'document_number') . ' AS UNSIGNED) DESC - ') - ->limit(1) - ->execute( - array_search(\get_called_class(), static::getModelTypes(), true), - $this->store_id - ) - ; - - $intMax = (int) substr($objMax->document_number, $intPrefix); - - $this->arrData['document_number'] = $strPrefix . str_pad($intMax + 1, $intDigits, '0', STR_PAD_LEFT); - } - - Database::getInstance() - ->prepare('UPDATE tl_iso_product_collection SET document_number=? WHERE id=?') - ->execute($this->arrData['document_number'], $this->id) - ; - - Database::getInstance()->unlockTables(); - - } catch (\Exception $e) { - // Make sure tables are always unlocked - Database::getInstance()->unlockTables(); - - throw $e; - } - - return $this->arrData['document_number']; - } - - /** - * Generate a unique ID for this collection - * - * @return string - */ - protected function generateUniqueId() - { - if (!empty($this->arrData['uniqid'])) { - return $this->arrData['uniqid']; - } - - return uniqid('', true); - } - - /** - * Prevent modifying a locked collection - * - * @throws \BadMethodCallException if the collection is locked. - */ - protected function ensureNotLocked() - { - if ($this->isLocked()) { - throw new \BadMethodCallException('Product collection is locked'); - } - } - - /** - * Make sure the addresses belong to this collection only, so they will never be modified - * - * @throws \UnderflowException if collection is not saved (not in DB) - * @throws \BadMethodCallException if the product collection is locked. - */ - protected function createPrivateAddresses() - { - $this->ensureNotLocked(); - - if (!$this->id) { - throw new \UnderflowException('Product collection must be saved before creating unique addresses.'); - } - - $canSkip = StringUtil::deserialize($this->iso_checkout_skippable, true); - $objBillingAddress = $this->getBillingAddress(); - $objShippingAddress = $this->getShippingAddress(); - - // Store address in address book - if ($this->iso_addToAddressbook && $this->member > 0) { - if (null !== $objBillingAddress - && $objBillingAddress->ptable != MemberModel::getTable() - && !\in_array('billing_address', $canSkip, true) - ) { - $objAddress = clone $objBillingAddress; - $objAddress->pid = $this->member; - $objAddress->tstamp = time(); - $objAddress->ptable = MemberModel::getTable(); - $objAddress->store_id = $this->store_id; - $objAddress->save(); - - $this->updateDefaultAddress($objAddress); - } - - if (null !== $objBillingAddress - && null !== $objShippingAddress - && $objBillingAddress->id != $objShippingAddress->id - && $objShippingAddress->ptable != MemberModel::getTable() - && !\in_array('shipping_address', $canSkip, true) - ) { - $objAddress = clone $objShippingAddress; - $objAddress->pid = $this->member; - $objAddress->tstamp = time(); - $objAddress->ptable = MemberModel::getTable(); - $objAddress->store_id = $this->store_id; - $objAddress->save(); - - $this->updateDefaultAddress($objAddress); - } - } - - /** @var Config $config */ - $config = $this->getRelated('config_id'); - $billingFields = (null === $config) ? array() : $config->getBillingFields(); - $shippingFields = (null === $config) ? array() : $config->getShippingFields(); - - if (null !== $objBillingAddress - && ($objBillingAddress->ptable != static::$strTable || $objBillingAddress->pid != $this->id) - ) { - $arrData = array_intersect_key( - $objBillingAddress->row(), - array_merge(array_flip($billingFields), ['country' => '']) - ); - - $objNew = new Address(); - $objNew->setRow($arrData); - - $objNew->pid = $this->id; - $objNew->tstamp = time(); - $objNew->ptable = static::$strTable; - $objNew->store_id = $this->store_id; - $objNew->save(); - - $this->setBillingAddress($objNew); - - if (null !== $objShippingAddress && $objBillingAddress->id == $objShippingAddress->id) { - $this->setShippingAddress($objNew); - - // Stop here, we do not need to check shipping address - return; - } - } - - if (null !== $objShippingAddress - && ($objShippingAddress->ptable != static::$strTable || $objShippingAddress->pid != $this->id) - ) { - $arrData = array_intersect_key( - $objShippingAddress->row(), - array_merge(array_flip($shippingFields), ['country' => '']) - ); - - $objNew = new Address(); - $objNew->setRow($arrData); - - $objNew->pid = $this->id; - $objNew->tstamp = time(); - $objNew->ptable = static::$strTable; - $objNew->store_id = $this->store_id; - $objNew->save(); - - $this->setShippingAddress($objNew); - } elseif (null === $objShippingAddress) { - // Make sure to set the shipping address to null if collection has no shipping - // see isotope/core#2014 - $this->setShippingAddress(null); - } - } - - /** - * Mark existing addresses as not default if the new address is default - * - * @param Address $objAddress - */ - protected function updateDefaultAddress(Address $objAddress) - { - $arrSet = array(); - - if ($objAddress->isDefaultBilling) { - $arrSet['isDefaultBilling'] = ''; - } - - if ($objAddress->isDefaultShipping) { - $arrSet['isDefaultShipping'] = ''; - } - - if (\count($arrSet) > 0) { - Database::getInstance() - ->prepare('UPDATE tl_iso_address %s WHERE pid=? AND ptable=? AND store_id=? AND id!=?') - ->set($arrSet) - ->execute($this->member, MemberModel::getTable(), $this->store_id, $objAddress->id) - ; - } - } - - /** - * Clear all cache properties - */ - protected function clearCache() - { - $this->arrItems = null; - $this->arrSurcharges = null; - $this->arrCache = null; - $this->arrErrors = array(); - $this->objPayment = false; - $this->objShipping = false; - } - - /** - * Initialize a new collection and duplicate everything from the source - * - * @param IsotopeProductCollection $objSource - * - * @return static - */ - public static function createFromCollection(IsotopeProductCollection $objSource) - { - $objCollection = new static(); - $objConfig = $objSource->getConfig(); - - if (null === $objConfig) { - $objConfig = Isotope::getConfig(); - } - - $member = $objSource->getMember(); - - $objCollection->source_collection_id = $objSource->getId(); - $objCollection->config_id = (int) $objConfig->id; - $objCollection->store_id = (int) $objSource->getStoreId(); - $objCollection->member = (null === $member ? 0 : $member->id); - - if ($objCollection instanceof IsotopeOrderableCollection - && $objSource instanceof IsotopeOrderableCollection) - { - $objCollection->setShippingMethod($objSource->getShippingMethod()); - $objCollection->setPaymentMethod($objSource->getPaymentMethod()); - - $objCollection->setShippingAddress($objSource->getShippingAddress()); - $objCollection->setBillingAddress($objSource->getBillingAddress()); - } - - $arrItemIds = $objCollection->copyItemsFrom($objSource); - - $objCollection->updateDatabase(); - - // HOOK: order status has been updated - if (isset($GLOBALS['ISO_HOOKS']['createFromProductCollection']) - && \is_array($GLOBALS['ISO_HOOKS']['createFromProductCollection']) - ) { - foreach ($GLOBALS['ISO_HOOKS']['createFromProductCollection'] as $callback) { - System::importStatic($callback[0])->{$callback[1]}($objCollection, $objSource, $arrItemIds); - } - } - - return $objCollection; - } - - /** - * Method that returns a closure to sort product collection items - * - * @param string $strOrderBy - * - * @return \Closure|null - */ - public static function getItemsSortingCallable($strOrderBy = 'asc_id') - { - [$direction, $attribute] = explode('_', $strOrderBy, 2) + [null, null]; - - if ('asc' === $direction) { - return function ($arrItems) use ($attribute) { - uasort($arrItems, function ($objItem1, $objItem2) use ($attribute) { - if ($objItem1->$attribute == $objItem2->$attribute) { - return 0; - } - - return $objItem1->$attribute < $objItem2->$attribute ? -1 : 1; - }); - - return $arrItems; - }; - - } - - if ('desc' === $direction) { - return function ($arrItems) use ($attribute) { - uasort($arrItems, function ($objItem1, $objItem2) use ($attribute) { - if ($objItem1->$attribute == $objItem2->$attribute) { - return 0; - } - - return $objItem1->$attribute > $objItem2->$attribute ? -1 : 1; - }); - - return $arrItems; - }; - } - - return null; - } - - /** - * @param IsotopeProduct $product - * @param ProductCollectionItem $item - * @param int $quantity - */ - private function setProductForItem(IsotopeProduct $product, ProductCollectionItem $item, $quantity) - { - $item->tstamp = time(); - $item->type = array_search(\get_class($product), Product::getModelTypes(), true); - $item->product_id = (int) $product->getId(); - $item->sku = (string) $product->getSku(); - $item->name = (string) $product->getName(); - $item->configuration = $product->getOptions(); - $item->quantity = (int) $quantity; - $item->price = (float) ($product->getPrice($this) ? $product->getPrice($this)->getAmount((int) $quantity) : 0); - $item->tax_free_price = (float) ($product->getPrice($this) ? $product->getPrice($this)->getNetAmount((int) $quantity) : 0); - } - - /** - * Check if product collection has tax - * - * @return bool - */ - public function hasTax() - { - foreach ($this->getSurcharges() as $surcharge) { - if ($surcharge instanceof Tax) { - return true; - } - } - - return false; - } -} diff --git a/api2/ProductCollectionItem.php b/api2/ProductCollectionItem.php deleted file mode 100644 index 261a52a..0000000 --- a/api2/ProductCollectionItem.php +++ /dev/null @@ -1,489 +0,0 @@ -isLocked()) { - return true; - } - - if (isset($GLOBALS['ISO_HOOKS']['itemIsAvailable']) && \is_array($GLOBALS['ISO_HOOKS']['itemIsAvailable'])) { - foreach ($GLOBALS['ISO_HOOKS']['itemIsAvailable'] as $callback) { - $available = System::importStatic($callback[0])->{$callback[1]}($this); - - // If return value is boolean then we accept it as result - if (true === $available || false === $available) { - return $available; - } - } - } - - if (!$this->hasProduct() || !$this->getProduct()->isAvailableForCollection($this->getRelated('pid'))) { - return false; - } - - $arrConfig = $this->getOptions(); - foreach ($this->getProduct()->getOptions() as $k => $v) { - if ($arrConfig[$k] !== $v) { - return false; - } - } - - return true; - } - - /** - * Return true if product collection item is locked - */ - public function isLocked() - { - return $this->blnLocked; - } - - /** - * Lock item, necessary if product collection is locked - */ - public function lock() - { - $this->blnLocked = true; - } - - /** - * Delete downloads when deleting product collection item - * - * @return int - */ - public function delete() - { - $intId = $this->id; - $intAffected = parent::delete(); - - if ($intAffected) { - Database::getInstance()->query("DELETE FROM tl_iso_product_collection_download WHERE pid=$intId"); - } - - return $intAffected; - } - - /** - * Get the product related to this item - * - * @param bool $blnNoCache - * - * @return IsotopeProduct|null - */ - public function getProduct($blnNoCache = false) - { - if (false === $this->objProduct || true === $blnNoCache) { - - $this->objProduct = null; - - /** @var string|\Isotope\Model\Product $strClass */ - $strClass = Product::getClassForModelType($this->type); - - if ($strClass == '' || !class_exists($strClass)) { - System::log('Error creating product object of type "' . $this->type . '"', __METHOD__, TL_ERROR); - - return null; - } - - try { - $this->objProduct = $strClass::findByPk($this->product_id); - } catch (\Exception $e) { - $this->objProduct = null; - $this->addError($e->getMessage()); - } - - if (null !== $this->objProduct && $this->objProduct instanceof IsotopeProductWithOptions) { - try { - if ($this->objProduct instanceof Model) { - $this->objProduct = clone $this->objProduct; - $this->objProduct->preventSaving(false); - $this->objProduct->id = $this->product_id; - } - - $this->objProduct->setOptions($this->getOptions()); - } catch (\RuntimeException $e) { - $this->addError($GLOBALS['TL_LANG']['ERR']['collectionItemNotAvailable']); - } - } - } - - return $this->objProduct; - } - - /** - * Return boolean flag if product could be loaded - * - * @return bool - */ - public function hasProduct() - { - return (null !== $this->getProduct()); - } - - /** - * Get product SKU. Automatically falls back to the collection item table if product is not found. - * - * @return string - */ - public function getSku() - { - return (string) ($this->isLocked() || !$this->hasProduct()) ? $this->sku : $this->getProduct()->getSku(); - } - - /** - * Get product name. Automatically falls back to the collection item table if product is not found. - * - * @return string - */ - public function getName() - { - return (string) ($this->isLocked() || !$this->hasProduct()) ? $this->name : $this->getProduct()->getName(); - } - - /** - * Returns key-value array for variant-enabled and customer editable attributes. - * - * @return array - * - * @deprecated Use getOptions() - */ - public function getAttributes() - { - return $this->getOptions(); - } - - /** - * Returns key-value array for variant-enabled and customer editable attributes. - * - * @return array - */ - public function getOptions() - { - $arrConfig = StringUtil::deserialize($this->configuration); - - return \is_array($arrConfig) ? $arrConfig : []; - } - - /** - * Get product configuration - * - * @return array - * - * @deprecated Deprecated since Isotope 2.4, to be removed in Isotope 3.0. Use getOptions() instead. - */ - public function getConfiguration() - { - $arrConfig = StringUtil::deserialize($this->configuration); - - if (empty($arrConfig) || !\is_array($arrConfig)) { - return array(); - } - - if ($this->hasProduct()) { - return Isotope::formatProductConfiguration($arrConfig, $this->getProduct()); - - } else { - foreach ($arrConfig as $k => $v) { - $arrConfig[$k] = new Plain($v, $k); - } - - return $arrConfig; - } - } - - /** - * Get product price. Automatically falls back to the collection item table if product is not found. - * - * @return string - */ - public function getPrice() - { - if ($this->isLocked() || !$this->hasProduct()) { - return $this->price; - } - - $objPrice = $this->getProduct()->getPrice($this->getRelated('pid')); - - if (null === $objPrice) { - return ''; - } - - return $objPrice->getAmount((int) $this->quantity, $this->getOptions()); - } - - /** - * Get tax free product price. Automatically falls back to the collection item table if product is not found. - * - * @return string - */ - public function getTaxFreePrice() - { - if ($this->isLocked() || !$this->hasProduct()) { - return $this->tax_free_price; - } - - $objPrice = $this->getProduct()->getPrice($this->getRelated('pid')); - - if (null === $objPrice) { - return ''; - } - - return $objPrice->getNetAmount((int) $this->quantity, $this->getOptions()); - } - - /** - * Get original product price. Automatically falls back to the collection item table if product is not found. - * - * @return string - */ - public function getOriginalPrice() - { - if ($this->isLocked() || !$this->hasProduct()) { - return $this->price; - } - - $objPrice = $this->getProduct()->getPrice($this->getRelated('pid')); - - if (null === $objPrice) { - return ''; - } - - return $objPrice->getOriginalAmount((int) $this->quantity, $this->getOptions()); - } - - /** - * Get product price multiplied by the requested product quantity - * - * @return string - */ - public function getTotalPrice() - { - return (string) ($this->getPrice() * (int) $this->quantity); - } - - /** - * Get original product price multiplied by the requested product quantity - * - * @return string - */ - public function getTotalOriginalPrice() - { - return (string) ($this->getOriginalPrice() * (int) $this->quantity); - } - - /** - * Get tax free product price multiplied by the requested product quantity - * - * @return string - */ - public function getTaxFreeTotalPrice() - { - return (string) ($this->getTaxFreePrice() * (int) $this->quantity); - } - - /** - * Return downloads associated with this product collection item - * - * @return ProductCollectionDownload[] - */ - public function getDownloads() - { - if (null === $this->arrDownloads) { - $this->arrDownloads = array(); - - $objDownloads = ProductCollectionDownload::findBy('pid', $this->id); - - if (null !== $objDownloads) { - while ($objDownloads->next()) { - $this->arrDownloads[] = $objDownloads->current(); - } - } - } - - return $this->arrDownloads; - } - - /** - * Increase quantity of product collection item - * - * @param int $intQuantity - * - * @return self - */ - public function increaseQuantityBy($intQuantity) - { - $time = time(); - - Database::getInstance()->query(" - UPDATE tl_iso_product_collection_item - SET tstamp=$time, quantity=(quantity+" . (int) $intQuantity . ') - WHERE id=' . $this->id - ); - - $this->tstamp = $time; - $this->quantity = Database::getInstance() - ->query("SELECT quantity FROM tl_iso_product_collection_item WHERE id=" . $this->id) - ->quantity - ; - - return $this; - } - - /** - * Decrease quantity of product collection item - * - * @param int $intQuantity - * - * @return self - */ - public function decreaseQuantityBy($intQuantity) - { - if (($this->quantity - $intQuantity) < 1) { - throw new \UnderflowException('Quantity of product collection item cannot be less than 1.'); - } - - $time = time(); - - Database::getInstance()->query(" - UPDATE tl_iso_product_collection_item - SET tstamp=$time, quantity=(quantity-" . (int) $intQuantity . ') - WHERE id=' . $this->id - ); - - $this->tstamp = $time; - $this->quantity = Database::getInstance() - ->query('SELECT quantity FROM tl_iso_product_collection_item WHERE id=' . $this->id) - ->quantity - ; - - return $this; - } - - /** - * Calculate the sum of a database column - * - * @param string $strField - * @param mixed $strColumn - * @param mixed $varValue - * - * @return int - */ - public static function sumBy($strField, $strColumn = null, $varValue = null) - { - $strQuery = "SELECT SUM($strField) AS sum FROM tl_iso_product_collection_item"; - - if ($strColumn !== null) { - $strQuery .= ' WHERE ' . (\is_array($strColumn) ? implode(' AND ', $strColumn) : static::$strTable . '.' . $strColumn . "=?"); - } - - return (int) Database::getInstance()->prepare($strQuery)->execute($varValue)->sum; - } - - /** - * Add an error message - * - * @param string $strError - */ - public function addError($strError) - { - $this->arrErrors[] = $strError; - } - - /** - * Return true if the collection item has errors - * - * @return bool - */ - public function hasErrors() - { - return 0 !== \count($this->arrErrors); - } - - /** - * Return the errors array - * - * @return array - */ - public function getErrors() - { - return $this->arrErrors; - } -} diff --git a/src/Command/CmdUpdateProducts.php b/src/Command/CmdUpdateProducts.php new file mode 100644 index 0000000..b99aa7e --- /dev/null +++ b/src/Command/CmdUpdateProducts.php @@ -0,0 +1,36 @@ +connection = $connection; + parent::__construct(); + } + + protected function configure(): void + { + $this->setDescription('Updates products from Exact-Online to Contao-Isotope tables.'); + } + + protected function execute(InputInterface $input, OutputInterface $output): ?int + { + $apiConnector = new ApiExact(); + $products = $apiConnector->getProducts(); + $handler = new IsotopeDatabaseHandler($this->connection); + $handler->processProducts($products); + return $this->statusCode; + } +} \ No newline at end of file diff --git a/src/EventListener/PostCheckoutListener.php b/src/EventListener/PostCheckoutListener.php new file mode 100644 index 0000000..542a165 --- /dev/null +++ b/src/EventListener/PostCheckoutListener.php @@ -0,0 +1,122 @@ +getItems() as $orderItem) { + $salesOrderItems[] = $this->createOrderItem($orderItem); +// /** @var Product $orderItem */ +// $product = $orderItem->getProduct(); +// var_dump($orderItem->id); +// var_dump($orderItem->sku); +// var_dump($orderItem->quantity); +// var_dump($orderItem->tax_free_price); +// +// var_dump($product->id); +// var_dump($product->name); +// var_dump($product->is_set); + + } + //var_dump($apiExact->test()); +// file_put_contents('dan.txt', ob_get_contents()); +// ob_end_clean(); + + + + $shippingCustomer = null; +// if ($this->compareAddresses() === false) { +// $shippingCustomer = $apiExact->createCustomer( +// $this->createCustomerApiData($order->getShippingAddress()) +// ); +// } +// + $billingCustomer = $apiExact->createCustomer( + $this->createCustomerApiData($order->getBillingAddress()) + ); + $apiExact->createSalesOrder($this->createSalesOrderData($order, $billingCustomer, $shippingCustomer)); + + } + + private function compareAddresses(Order $order) + { + return + $order->getBillingAddress()->firstname === $order->getShippingAddress()->firstname && + $order->getBillingAddress()->lastname === $order->getShippingAddress()->lastname && + $order->getBillingAddress()->street_1 === $order->getShippingAddress()->street_1 && + $order->getBillingAddress()->postal === $order->getShippingAddress()->postal && + $order->getBillingAddress()->city === $order->getShippingAddress()->city && + $order->getBillingAddress()->company === $order->getShippingAddress()->company && + $order->getBillingAddress()->phone === $order->getShippingAddress()->phone && + $order->getBillingAddress()->email === $order->getShippingAddress()->email; + } + + private function createCustomerApiData(Address $address) + { + return [ + 'Name' => $address->firstname . ' ' . $address->lastname, + 'AddressLine1' => $address->street_1, + 'AddressLine2' => $address->company, + 'City' => $address->city, + 'Postcode' => $address->postal, + 'Email' => $address->email, + 'Country' => 'DE', + 'Phone' => $address->phone, + 'Status' => 'C', + ]; + } + + private function createOrderItem(ProductCollectionItem $orderItem) + { + /** @var Product $orderItem */ + $product = $orderItem->getProduct(); + return [ + 'Description' => $product->name, + 'Item' => $product->exact_id, + 'UnitPrice' => (float) $orderItem->tax_free_price / (int) $orderItem->quantity, + 'Quantity' => $orderItem->quantity + ]; + } + + private function createSalesOrderData(Order $order, $billingCustomer, $salesOrderItems, $shippingCustomer = null) + { + $date = new \DateTime('now'); + $res = [ + 'Description' => $billingCustomer['Name'], + 'OrderDate' => $date->format('m/d/Y H:i:s'), + 'OrderedBy' => $billingCustomer['ID'], + 'WarehouseID' => self::WAREHOUSE_ID, + 'SalesOrderLines' => [ + $salesOrderItems + ] + ]; + if ($shippingCustomer !== null) { + $res['DeliverTo'] = $shippingCustomer['ID']; + } + + return $res; + } +} \ No newline at end of file diff --git a/src/ExactApi/ApiExact.php b/src/ExactApi/ApiExact.php new file mode 100644 index 0000000..2bb8c5f --- /dev/null +++ b/src/ExactApi/ApiExact.php @@ -0,0 +1,170 @@ +expiry_time < time()) { + $postData = array( + 'grant_type' => 'refresh_token', + 'refresh_token' => $tokenData->refresh_token, + 'client_id' => self::CLIENT_ID, + 'client_secret' => self::CLIENT_SECRET + ); + + $ch = curl_init(); + curl_setopt_array($ch, array( + CURLOPT_URL => self::API_URL . '/oauth2/token', + CURLOPT_RETURNTRANSFER => TRUE, + CURLOPT_HTTPHEADER => array('Content-Type: application/x-www-form-urlencoded'), + CURLOPT_POSTFIELDS => http_build_query($postData) + )); + + $response = curl_exec($ch); + curl_close($ch); + $jsonResult = json_decode($response); + + if (property_exists($jsonResult, 'error')) { + $msg = $jsonResult->error; + throw new \Exception($msg); + } + $jsonResult->expiry_time = time() + self::ACCESS_TOKEN_VALID_DURATION; + file_put_contents(__DIR__ . '/tokenData', json_encode($jsonResult)); + + $tokenData = json_decode(file_get_contents(__DIR__ . '/tokenData', true)); + } + return $tokenData->access_token; + } + + public function getProducts() + { + $baseUrl = self::API_URL_WNR . "/bulk/Logistics/Items?"; + $fields = [ + 'ID', + 'StandardSalesPrice', + 'SalesVatCode', + 'SalesVatCodeDescription', + 'Description', + 'ExtraDescription', + 'IsWebshopItem', + 'ItemGroup', + 'ItemGroupCode', + 'Stock', + 'NetWeight', + 'GrossWeight', + 'Code' + ]; + $filter = "\$filter=ItemGroup eq guid'".self::ITEM_GROUP_UUID."'"; + $select = "&\$select=".$this->getFieldString($fields); + $requestUrl = $baseUrl . $filter . $select; + $response = $this->getApiData($requestUrl); + $responseArr = json_decode($response, 1)['d']['results']; + + $res = []; + foreach ($responseArr as $productItem) { + if ( + array_key_exists('IsWebshopItem', $productItem) && + $productItem['IsWebshopItem'] === 1 && + array_key_exists('Code', $productItem) && + stripos(strtoupper($productItem['Code']), self::PRODUCT_CODE_PREFIX) === 0 + ) { + $res[] = Product::createProductFromApiItem($productItem); + } + } + return $res; + } + + public function createCustomer($customerData) + { + $url = self::API_URL_WNR . "/crm/Accounts"; + return json_decode($this->postApiData($url, $customerData)); + } + + public function createSalesOrder($salesOrderData) + { + $url = self::API_URL_WNR . "/salesorder/SalesOrders"; +// $parameters = [ +// 'Description' => 'Daniel Knudsen', +// 'OrderDate' => '07/13/2022 17:00:00', +// 'OrderedBy' => '9ba706a3-d6f5-40c8-8d4f-e55a37692cef', +// 'WarehouseID' => 'd8a6a9b8-d0ac-4d36-8d00-bd420d0d81f5', +// 'SalesOrderLines' => [ +// [ +// 'Description' => 'TEST: Reinigungstuch grün', +// 'Item' => '9dcd8b50-5de6-4f15-a51a-c51282292383', +// 'UnitPrice' => '2.45', +// 'Quantity' => '3' +// ], +// [ +// 'Description' => 'TEST: McQuade´s E-Bike Kettenrückführ - Tool', +// 'Item' => '0e04113f-1299-4d3c-b65e-01e83ca5b3b2', +// 'UnitPrice' => '476.00', +// 'Quantity' => '1' +// ], +// ] +// ]; + return json_decode($this->postApiData($url, $salesOrderData)); + } + + private function getApiData($url) + { + $ch = curl_init(); + curl_setopt_array($ch, array( + CURLOPT_URL => $url, + CURLOPT_HTTPHEADER => array('Accept: application/json' , "Authorization: Bearer " . $this->getAccessToken() ), + CURLOPT_RETURNTRANSFER => TRUE, + )); + $res = curl_exec($ch); + curl_close($ch); + return $res; + } + + private function postApiData($url, $data) + { + $ch = curl_init(); + curl_setopt_array($ch, array( + CURLOPT_URL => $url, + CURLOPT_HTTPHEADER => array( + 'Content-Type: application/json', + 'Accept: application/json', + "Authorization: Bearer " . $this->getAccessToken() ), + CURLOPT_RETURNTRANSFER => TRUE, + CURLOPT_POST => TRUE, + CURLOPT_POSTFIELDS => json_encode($data) + )); + $res = curl_exec($ch); + curl_close($ch); + return $res; + } + + private function getFieldString($fields) + { + $res = ''; + $cnt = count($fields); + $i = 1; + foreach ($fields as $field) { + $res .= $cnt !== $i ? $field . ',' : $field; + $i++; + } + return $res; + } +} \ No newline at end of file diff --git a/src/ExactApi/IsotopeDatabaseHandler.php b/src/ExactApi/IsotopeDatabaseHandler.php new file mode 100644 index 0000000..74cb9c0 --- /dev/null +++ b/src/ExactApi/IsotopeDatabaseHandler.php @@ -0,0 +1,193 @@ +connection = $connection; + } + + public function processProducts(array $products) + { + $this->connection->beginTransaction(); + + $sql = "SELECT * FROM `tl_iso_product`"; + $stmt = $this->connection->query($sql); + $dbProducts = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + $sql = "SELECT * FROM `tl_iso_product_price`"; + $stmt = $this->connection->query($sql); + $dbProductPrices = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + $dbProductsBySku = []; + foreach ($dbProducts as $prod) { + $dbProductsBySku[$prod['sku']] = $prod; + } + + $dbProductsPricesByPid = []; + foreach ($dbProductPrices as $price) { + $dbProductsPricesByPid[$price['pid']] = $price; + } + + /** @var Product $product */ + foreach ($products as $product) { + + if (array_key_exists($product->getCode(), $dbProductsBySku)) { + $dbProduct = $dbProductsBySku[$product->getCode()]; + $dbPrice = $dbProductsPricesByPid[$dbProduct['id']]; + $this->updateProduct($product, $dbProduct['id']); + $this->updatePriceTier($product, $dbPrice['id']); + + } else { + $productId = $this->createProduct($product); + $productPriceId = $this->createPrice($product, $productId); + $this->createPriceTier($product, $productPriceId); + } + } + $this->connection->commit(); + } + + private function updateProduct(Product $product, $productDbId) + { + $shippingWeight = $this->convertShippingWeight($product); + $sql = + "UPDATE `tl_iso_product` SET + tstamp = ".time().", + shipping_weight = '$shippingWeight' + WHERE `id` = ".$productDbId; + $this->connection->query($sql); + } + + private function updatePriceTier(Product $product, $priceDbId) + { + $price = $product->getStandardSalesPrice() * (1 + Product::VAT); + $sql = + "UPDATE `tl_iso_product_pricetier` SET + tstamp = ".time().", + price = $price + WHERE `pid` = ".$priceDbId; + $this->connection->query($sql); + } + + private function createProduct(Product $product) + { + $alias = 'exact-product-'.$product->getCode(); + $shippingWeight = $this->convertShippingWeight($product); + $sql = + "INSERT into `tl_iso_product` ( + pid, + gid, + tstamp, + language, + dateAdded, + type, + fallback, + alias, + gtin, + sku, + name, + description, + meta_title, + baseprice, + shipping_weight, + shipping_exempt, + shipping_pickup, + shipping_price, + protected, + guests, + cssID, + published, + start, + stop, + exact_id + ) VALUES ( + 0, + 0, + ".time().", + '', + ".time().", + 2, + '', + '$alias', + '', + '".$product->getCode()."', + '".$product->getDescription()."', + '".$product->getExtraDescription()."', + '', + '', + '$shippingWeight', + '', + '', + 0.00, + '', + '', + '', + '0', + '', + '', + '".$product->getId()."' + )"; + $this->connection->query($sql); + return $this->connection->lastInsertId(); + } + + private function createPrice(Product $product, $productDbId) + { + $sql = + "INSERT INTO `tl_iso_product_price` ( + pid, + tstamp, + tax_class, + config_id, + member_group, + start, + stop + ) VALUES ( + $productDbId, + ".time().", + 1, + 0, + 0, + '', + '' + )"; + $this->connection->query($sql); + return $this->connection->lastInsertId(); + } + + private function createPriceTier(Product $product, $productPriceDbId) + { + $price = $product->getStandardSalesPrice() * (1 + Product::VAT); + $sql = + "INSERT INTO `tl_iso_product_pricetier` ( + pid, + tstamp, + min, + price + ) VALUES ( + $productPriceDbId, + ".time().", + 1, + $price + )"; + $this->connection->query($sql); + return $this->connection->lastInsertId(); + } + + private function convertShippingWeight(Product $product) + { + $shippingWeight = $product->getGrossWeight() !== '' ? + $product->getGrossWeight() : + $product->getNetWeight(); + return ((int) $shippingWeight === 0 || $shippingWeight === '') ? + '' : + 'a:2:{s:4:"unit";s:2:"kg";s:5:"value";s:3:"' . $shippingWeight . '";}'; + } +} \ No newline at end of file diff --git a/src/ExactApi/Product.php b/src/ExactApi/Product.php new file mode 100644 index 0000000..f115e4f --- /dev/null +++ b/src/ExactApi/Product.php @@ -0,0 +1,267 @@ +setId($item['ID']); + $res->setStandardSalesPrice($item['StandardSalesPrice']); + $res->setSalesVatCode($item['SalesVatCode']); + $res->setSalesVatCodeDescription($item['SalesVatCodeDescription']); + $res->setDescription($item['Description']); + $res->setExtraDescription($item['ExtraDescription']); + $res->setIsWebShopItem($item['IsWebshopItem']); + $res->setItemGroup($item['ItemGroup']); + $res->setItemGroupCode($item['ItemGroupCode']); + $res->setStock($item['Stock']); + $res->setNetWeight($item['NetWeight']); + $res->setGrossWeight($item['GrossWeight']); + $res->setCode($item['Code']); + return $res; + } + + /** + * @return mixed + */ + public function getId() + { + return $this->id; + } + + /** + * @param mixed $id + */ + public function setId($id) + { + $this->id = $id; + } + + /** + * @return mixed + */ + public function getStandardSalesPrice() + { + return $this->standardSalesPrice; + } + + /** + * @param mixed $standardSalesPrice + */ + public function setStandardSalesPrice($standardSalesPrice) + { + $this->standardSalesPrice = $standardSalesPrice; + } + + /** + * @return mixed + */ + public function getSalesVatCode() + { + return $this->salesVatCode; + } + + /** + * @param mixed $salesVatCode + */ + public function setSalesVatCode($salesVatCode) + { + $this->salesVatCode = $salesVatCode; + } + + /** + * @return mixed + */ + public function getSalesVatCodeDescription() + { + return $this->salesVatCodeDescription; + } + + /** + * @param mixed $salesVatCodeDescription + */ + public function setSalesVatCodeDescription($salesVatCodeDescription) + { + $this->salesVatCodeDescription = $salesVatCodeDescription; + } + + /** + * @return mixed + */ + public function getDescription() + { + return $this->description; + } + + /** + * @param mixed $description + */ + public function setDescription($description) + { + $this->description = $description; + } + + /** + * @return mixed + */ + public function getExtraDescription() + { + return $this->extraDescription; + } + + /** + * @param mixed $extraDescription + */ + public function setExtraDescription($extraDescription) + { + $this->extraDescription = $extraDescription; + } + + /** + * @return mixed + */ + public function getIsWebShopItem() + { + return $this->isWebShopItem; + } + + /** + * @param mixed $isWebShopItem + */ + public function setIsWebShopItem($isWebShopItem) + { + $this->isWebShopItem = $isWebShopItem; + } + + /** + * @return mixed + */ + public function getItemGroup() + { + return $this->itemGroup; + } + + /** + * @param mixed $itemGroup + */ + public function setItemGroup($itemGroup) + { + $this->itemGroup = $itemGroup; + } + + /** + * @return mixed + */ + public function getItemGroupCode() + { + return $this->itemGroupCode; + } + + /** + * @param mixed $itemGroupCode + */ + public function setItemGroupCode($itemGroupCode) + { + $this->itemGroupCode = $itemGroupCode; + } + + /** + * @return mixed + */ + public function getStock() + { + return $this->stock; + } + + /** + * @param mixed $stock + */ + public function setStock($stock) + { + $this->stock = $stock; + } + + /** + * @return mixed + */ + public function getNetWeight() + { + return $this->netWeight; + } + + /** + * @param mixed $netWeight + */ + public function setNetWeight($netWeight) + { + $this->netWeight = $netWeight; + } + + /** + * @return mixed + */ + public function getGrossWeight() + { + return $this->grossWeight; + } + + /** + * @param mixed $grossWeight + */ + public function setGrossWeight($grossWeight) + { + $this->grossWeight = $grossWeight; + } + + /** + * @return mixed + */ + public function getCode() + { + return $this->code; + } + + /** + * @param mixed $code + */ + public function setCode($code) + { + $this->code = $code; + } +} \ No newline at end of file diff --git a/src/ExactApi/apiUpdateProducts.php b/src/ExactApi/apiUpdateProducts.php new file mode 100644 index 0000000..624ed0d --- /dev/null +++ b/src/ExactApi/apiUpdateProducts.php @@ -0,0 +1,23 @@ +getAccessToken() . "\n"; +$products = $apiConnector->getProducts(); +$databaseHandler->processProducts($products); diff --git a/src/ExactApi/tokenData b/src/ExactApi/tokenData new file mode 100644 index 0000000..8a2a02d --- /dev/null +++ b/src/ExactApi/tokenData @@ -0,0 +1 @@ +{"access_token":"stampDE001.gAAAAKLeomGUJnO_59CX7KmwE4tkt1bPxLR7EfU5E2eHjj6LeU2dAPfkRstKdlJJoudf3y6ehyKwF5wQQ1SljbA5sRrGwQsw4M876o0RRtxVHwyR6_QSoM7kP3qj7HfJLEDWjY-KrlJf_rlKtZZ2sRGJkX9jbd-4XDA_uqQECkf2NnIfVAIAAIAAAAC2ADvWhSjIjtyvs4_rhH9HzqaVKUbQthUoKf6StMSX4O5GzilHhM2cQCQJly_7hbpKHJMe4o167cPYIQLTeOilQdlQh21nhGykWfOKtXG31h210WtT2bg8Kc0MtrHQGi_f161wE_xINPXr-H7GTcXLe4klIGUkv0kmmmUacNoDUDfU1fPRTCCFvmkRnE7iBzd116aY9XMVw6mDucFExI9U-dLOD8IFWCPa9U6DSd50T7WoglDHC8Enh-J92C1c8kAhCCiGyA5GHyb9S1eJAnHFDYzOl9y0pwtIHekBJq-LxpzAbVieBUZrXEsTJnD7eiHvKoy9g_Bl-jOVgPTJT29n0dFDUClE_iQ6MGydAiEVKoyhQO20W7EJg64G8l6ZHZrd8r1J36TZ4jmdZ8E8_wqw3MzYGZm4vEXK4G0Lf2-j5uD0eh3aKPQCCdn_jEiCaEABQ-bsvd9sQazNNHbDwzl82GZdlhY8sqln14NpNloahOxhY9-jKu5nT9IBN2EftT-VoLNWp_E4ydPltM_7SS0Xro9j8nf7xNjpBDa__GeR2QSg_He5VR6u2n4lUbU1Qzks2W38RiCA3jXrQ-T6FCtiiaf5rhMzudqeq2c94Ggbn1g7BD_3S9xUFeA5kmSCkn1NZ3RzSqmkMq4IVPcbsJFYeoCiuZ1gYcvSOoBMHN8mjZ8j3xpPEvzJSX39arD5IeNqrLZDXuEU5aORiRnqjN6kPC4w1GQbbMsEL94r8tYGy8VkW7bstxkar7vb2J8vOPhLamN0mcxJdTZCCJ8tj18e","token_type":"bearer","expires_in":"600","refresh_token":"stampDE001.vhK0!IAAAANKu8TC2-MBN5wFyro7uwanpxXBsRFy7YkYSPaM9jMCm8QEAAAEVEJUYfzoc3Q1hatgTMAOm__pmwjTgdkOyzb7qUj7oRvir1XftD_cb5eE7J-06Q0SxWegFPfULR65PDc6Id0pDXZbugqaKhK24Gg9Gr0x74g4EW8HMYJ0PsW1uqvpOb_ph9mQxYllDQf598_uPZvuvxAMN4NOipKiHEXn9tExbn-rmJdrV9bwa3fAm5b2ALbzfBEKCuzuwLkrjH3cheaMC2TaksJ6M2we9IeAezapsyOVEl6QtnhY67L3Irp_bUV_SCZ6H-gbPDX3mFujxyw-lvRoC-ZcgIpM8EuWC3pN24abFp7Qaj8wE2X9_oAgvoJgn42lHl-x6M-QChdf78f6wiCPquk3OwwuBYsjmiJrHFQt4a6nRS-Y_A-J8IOdwq-_KZkvNTY5UI1ZmiGH5pvIDfaXTcvPn6r3C-sQecdImJM43Y_tgZ9a6p2gwPi8zNZSkXfwQH5bfal6tBcH43_8eGSp_M-VbLb7TotZfJgEIePSlZ--VV1aTaRMJ78_JBloFWvQabvhYEvvuvRaePbdGRgXzj95KgQeW8DzBXE6_zHSmhwkUEv11xgXimH3BFToSV_F25ZND7H3OkVM1-M8Jhbvrzh4SR5wRV5WfzC83Mk58HmDtI7xR0owYAu-9mtidFsK8mRC3nJzZSOWdGenZ","expiry_time":1757726518} \ No newline at end of file