| @@ -6,5 +6,6 @@ require __DIR__ . '/IsotopeDatabaseHandler.php'; | |||||
| $apiExact = new ApiExact(); | $apiExact = new ApiExact(); | ||||
| $databaseHandler = new IsotopeDatabaseHandler(); | $databaseHandler = new IsotopeDatabaseHandler(); | ||||
| $products = $apiExact->getProducts(); | $products = $apiExact->getProducts(); | ||||
| $databaseHandler->processProducts($products); | |||||
| var_dump($products); | |||||
| //$databaseHandler->processProducts($products); | |||||
| @@ -0,0 +1,271 @@ | |||||
| <?php | |||||
| /** | |||||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||||
| * @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; | |||||
| } | |||||
| } | |||||
| @@ -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} | |||||
| {"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} | |||||
| @@ -1,809 +0,0 @@ | |||||
| <?php | |||||
| /* | |||||
| * Isotope eCommerce for Contao Open Source CMS | |||||
| * | |||||
| * Copyright (C) 2009 - 2019 terminal42 gmbh & Isotope eCommerce Workgroup | |||||
| * | |||||
| * @link https://isotopeecommerce.org | |||||
| * @license https://opensource.org/licenses/lgpl-3.0.html | |||||
| */ | |||||
| namespace Isotope\Model; | |||||
| use Contao\Database; | |||||
| use Contao\Date; | |||||
| use Contao\DcaExtractor; | |||||
| use Contao\Model; | |||||
| use Isotope\Interfaces\IsotopeProduct; | |||||
| use Isotope\Model\Attribute; | |||||
| use Isotope\RequestCache\Filter; | |||||
| use Model\Collection; | |||||
| /** | |||||
| * The basic Isotope product model | |||||
| * | |||||
| * @property int $id | |||||
| * @property int $pid | |||||
| * @property int $gid | |||||
| * @property int $tstamp | |||||
| * @property string $language | |||||
| * @property int $dateAdded | |||||
| * @property int $type | |||||
| * @property array $pages | |||||
| * @property array $orderPages | |||||
| * @property array $inherit | |||||
| * @property bool $fallback | |||||
| * @property string $alias | |||||
| * @property string $sku | |||||
| * @property string $name | |||||
| * @property string $teaser | |||||
| * @property string $description | |||||
| * @property string $meta_title | |||||
| * @property string $meta_description | |||||
| * @property string $meta_keywords | |||||
| * @property bool $shipping_exempt | |||||
| * @property array $images | |||||
| * @property bool $protected | |||||
| * @property array $groups | |||||
| * @property bool $guests | |||||
| * @property array $cssID | |||||
| * @property bool $published | |||||
| * @property string $start | |||||
| * @property string $stop | |||||
| */ | |||||
| abstract class Product extends TypeAgent implements IsotopeProduct | |||||
| { | |||||
| /** | |||||
| * Table name | |||||
| * @var string | |||||
| */ | |||||
| protected static $strTable = 'tl_iso_product'; | |||||
| /** | |||||
| * Interface to validate attribute | |||||
| * @var string | |||||
| */ | |||||
| protected static $strInterface = '\Isotope\Interfaces\IsotopeProduct'; | |||||
| /** | |||||
| * List of types (classes) for this model | |||||
| * @var array | |||||
| */ | |||||
| protected static $arrModelTypes = array(); | |||||
| /** | |||||
| * Currently active product (LIFO queue) | |||||
| * @var array | |||||
| */ | |||||
| protected static $arrActive = array(); | |||||
| /** | |||||
| * Get categories (pages) assigned to this product | |||||
| * | |||||
| * @param bool $blnPublished Only return published categories (pages) | |||||
| * | |||||
| * @return array | |||||
| */ | |||||
| abstract public function getCategories($blnPublished = false); | |||||
| /** | |||||
| * Get product that is currently active (needed e.g. for insert tag replacement) | |||||
| * | |||||
| * @return IsotopeProduct|null | |||||
| */ | |||||
| public static function getActive() | |||||
| { | |||||
| return 0 === \count(static::$arrActive) ? null : end(static::$arrActive); | |||||
| } | |||||
| /** | |||||
| * Set product that is currently active (needed e.g. for insert tag replacement) | |||||
| * | |||||
| * @param IsotopeProduct $objProduct | |||||
| */ | |||||
| public static function setActive(IsotopeProduct $objProduct) | |||||
| { | |||||
| static::$arrActive[] = $objProduct; | |||||
| } | |||||
| /** | |||||
| * Unset product that is currently active (prevent later use of it) | |||||
| */ | |||||
| public static function unsetActive() | |||||
| { | |||||
| array_pop(static::$arrActive); | |||||
| } | |||||
| /** | |||||
| * Find all published products | |||||
| * | |||||
| * @param array $arrOptions | |||||
| * | |||||
| * @return Collection|Product[]|null | |||||
| */ | |||||
| public static function findPublished(array $arrOptions = array()) | |||||
| { | |||||
| return static::findPublishedBy(array(), array(), $arrOptions); | |||||
| } | |||||
| /** | |||||
| * Find published products by condition | |||||
| * | |||||
| * @param mixed $arrColumns | |||||
| * @param mixed $arrValues | |||||
| * @param array $arrOptions | |||||
| * | |||||
| * @return Collection|Product[]|null | |||||
| */ | |||||
| public static function findPublishedBy($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::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); | |||||
| } | |||||
| } | |||||
| @@ -1,489 +0,0 @@ | |||||
| <?php | |||||
| /* | |||||
| * Isotope eCommerce for Contao Open Source CMS | |||||
| * | |||||
| * Copyright (C) 2009 - 2019 terminal42 gmbh & Isotope eCommerce Workgroup | |||||
| * | |||||
| * @link https://isotopeecommerce.org | |||||
| * @license https://opensource.org/licenses/lgpl-3.0.html | |||||
| */ | |||||
| namespace Isotope\Model; | |||||
| use Contao\Database; | |||||
| use Contao\Model; | |||||
| use Contao\StringUtil; | |||||
| use Contao\System; | |||||
| use Haste\Data\Plain; | |||||
| use Isotope\Interfaces\IsotopeProduct; | |||||
| use Isotope\Interfaces\IsotopeProductWithOptions; | |||||
| use Isotope\Isotope; | |||||
| /** | |||||
| * ProductCollectionItem represents an item in a product collection. | |||||
| * | |||||
| * @property int $id | |||||
| * @property int $pid | |||||
| * @property int $tstamp | |||||
| * @property int $product_id | |||||
| * @property string $type | |||||
| * @property string $sku | |||||
| * @property string $name | |||||
| * @property mixed $configuration | |||||
| * @property int $quantity | |||||
| * @property float $price | |||||
| * @property float $tax_free_price | |||||
| * @property string $tax_id | |||||
| * @property int $jumpTo | |||||
| */ | |||||
| class ProductCollectionItem extends \Model | |||||
| { | |||||
| /** | |||||
| * Name of the current table | |||||
| * @var string | |||||
| */ | |||||
| protected static $strTable = 'tl_iso_product_collection_item'; | |||||
| /** | |||||
| * Cache the current product | |||||
| * @var IsotopeProduct|IsotopeProductWithOptions|false | |||||
| */ | |||||
| protected $objProduct = false; | |||||
| /** | |||||
| * Cache downloads for the collection item | |||||
| * @var array | |||||
| */ | |||||
| protected $arrDownloads; | |||||
| /** | |||||
| * Errors | |||||
| * @var array | |||||
| */ | |||||
| protected $arrErrors = []; | |||||
| /** | |||||
| * True if product collection is locked | |||||
| * @var bool | |||||
| */ | |||||
| protected $blnLocked = false; | |||||
| /** | |||||
| * Check if collection item is available | |||||
| * | |||||
| * @return bool | |||||
| */ | |||||
| public function isAvailable() | |||||
| { | |||||
| if ($this->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; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,36 @@ | |||||
| <?php | |||||
| namespace App\Command; | |||||
| use App\ExactApi\IsotopeDatabaseHandler; | |||||
| use App\ExactApi\ApiExact; | |||||
| use Doctrine\DBAL\Connection; | |||||
| use Symfony\Component\Console\Command\Command; | |||||
| use Symfony\Component\Console\Input\InputInterface; | |||||
| use Symfony\Component\Console\Output\OutputInterface; | |||||
| class CmdUpdateProducts extends Command | |||||
| { | |||||
| protected static $defaultName = 'app:update-products'; | |||||
| protected $connection; | |||||
| private $statusCode = 0; | |||||
| public function __construct(Connection $connection) | |||||
| { | |||||
| $this->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; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,122 @@ | |||||
| <?php | |||||
| namespace App\EventListener; | |||||
| use Contao\CoreBundle\ServiceAnnotation\Hook; | |||||
| use Contao\FrontendTemplate; | |||||
| use Contao\Module; | |||||
| use App\ExactApi\ApiExact; | |||||
| use Isotope\Model\Address; | |||||
| use Isotope\Model\Product; | |||||
| use Isotope\Model\ProductCollection\Order; | |||||
| use Isotope\Model\ProductCollectionItem; | |||||
| class PostCheckoutListener | |||||
| { | |||||
| const WAREHOUSE_ID = 'd8a6a9b8-d0ac-4d36-8d00-bd420d0d81f5'; | |||||
| /** | |||||
| * @Hook("postCheckout") | |||||
| */ | |||||
| // public function __invoke(int $userId, array $userData, Module $module): void | |||||
| public function __invoke(Order $order, $item): void | |||||
| { | |||||
| $apiExact = new ApiExact(); | |||||
| //ob_start(); | |||||
| $salesOrderItems = []; | |||||
| /** @var ProductCollectionItem $orderItem */ | |||||
| foreach($order->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; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,170 @@ | |||||
| <?php | |||||
| namespace App\ExactApi; | |||||
| use App\ExactApi\Product; | |||||
| class ApiExact | |||||
| { | |||||
| const DIVISION = '58687'; | |||||
| const API_URL = "https://start.exactonline.de/api"; | |||||
| const API_URL_WNR = self::API_URL . "/v1/" . self::DIVISION; | |||||
| const CLIENT_ID = "5a04d118-349c-4750-aac3-fa3a386999c6"; | |||||
| const CLIENT_SECRET = "E7Wuqcsp4Lih"; | |||||
| const ACCESS_TOKEN_VALID_DURATION = 600 - 50; | |||||
| const ITEM_GROUP_UUID = 'df17bdaf-2af7-4e9f-8d60-326e36b57764'; | |||||
| const PRODUCT_CODE_PREFIX = "WR"; | |||||
| public function getAccessToken() | |||||
| { | |||||
| $tokenData = json_decode(file_get_contents(__DIR__ . '/tokenData', true)); | |||||
| ob_start(); | |||||
| var_dump($tokenData); | |||||
| ob_end_clean(); | |||||
| if (!property_exists($tokenData, 'expiry_time') || (int)$tokenData->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; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,193 @@ | |||||
| <?php | |||||
| namespace App\ExactApi; | |||||
| use App\ExactApi\Product; | |||||
| use Doctrine\DBAL\Connection; | |||||
| class IsotopeDatabaseHandler | |||||
| { | |||||
| private $connection; | |||||
| public function __construct(Connection $connection) | |||||
| { | |||||
| $this->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 . '";}'; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,267 @@ | |||||
| <?php | |||||
| namespace App\ExactApi; | |||||
| 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; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,23 @@ | |||||
| <?php | |||||
| //require __DIR__ . '/ApiExact.php'; | |||||
| //require __DIR__ . '/IsotopeDatabaseHandler.php'; | |||||
| namespace App\ExactApi; | |||||
| //use App\ExactApi\ApiExact; | |||||
| //use App\ExactApi\IsotopeDatabaseHandler; | |||||
| //use App\ExactApi\ApiExact as ApiExact; | |||||
| //use App\ExactApi\IsotopeDatabaseHandler as IsotopeDatabaseHandler; | |||||
| //include_once('App\ExactApi\ApiExact.php'); | |||||
| //include_once('App\ExactApi\IsotopeDatabaseHandler.php'); | |||||
| require_once './ApiExact.php'; | |||||
| require_once './IsotopeDatabaseHandler.php'; | |||||
| $apiConnector = new ApiExact(); | |||||
| $databaseHandler = new IsotopeDatabaseHandler(); | |||||
| //echo $apiConnector->getAccessToken() . "\n"; | |||||
| $products = $apiConnector->getProducts(); | |||||
| $databaseHandler->processProducts($products); | |||||
| @@ -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} | |||||