| @@ -0,0 +1,119 @@ | |||
| <?php | |||
| /** | |||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||
| * @date 11.07.22 | |||
| */ | |||
| require_once('Product.php'); | |||
| class ApiExact | |||
| { | |||
| const API_URL = "https://start.exactonline.de/api"; | |||
| const CLIENT_ID = "5a04d118-349c-4750-aac3-fa3a386999c6"; | |||
| const CLIENT_SECRET = "E7Wuqcsp4Lih"; | |||
| const ACCESS_TOKEN_VALID_DURATION = 600 - 50; | |||
| const DIVISION = '58687'; | |||
| const ITEM_GROUP_UUID = 'df17bdaf-2af7-4e9f-8d60-326e36b57764'; | |||
| const PRODUCT_CODE_PREFIX = "WR"; | |||
| public function getAccessToken() | |||
| { | |||
| $tokenData = json_decode(file_get_contents('./tokenData', true)); | |||
| 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('./tokenData', json_encode($jsonResult)); | |||
| $tokenData = json_decode(file_get_contents('./tokenData', true)); | |||
| } | |||
| return $tokenData->access_token; | |||
| } | |||
| public function getProducts() | |||
| { | |||
| $baseUrl = self::API_URL . '/v1/' . self::DIVISION . "/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; | |||
| } | |||
| 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, | |||
| )); | |||
| return curl_exec($ch); | |||
| } | |||
| 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,147 @@ | |||
| <?php | |||
| /** | |||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||
| * @date 11.07.22 | |||
| */ | |||
| require_once('Product.php'); | |||
| class IsotopeDatabaseHandler | |||
| { | |||
| const USER = 'root'; | |||
| const PASSWORD = ''; | |||
| const HOST = '127.0.0.1'; | |||
| const DB_NAME = 'wash_n_roll'; | |||
| private $dbHandle; | |||
| public function __construct() | |||
| { | |||
| try { | |||
| $this->dbHandle = new PDO('mysql:host='.self::HOST.';dbname='.self::DB_NAME.';charset=utf8',self::USER, self::PASSWORD); | |||
| $this->dbHandle->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); | |||
| } catch (PDOException $e) { | |||
| print "Error!: " . $e->getMessage() . "<br/>"; | |||
| die(); | |||
| } | |||
| } | |||
| public function updateProducts(array $products) | |||
| { | |||
| /** @var Product $product */ | |||
| foreach ($products as $product) { | |||
| $productId = $this->updateProduct($product); | |||
| $productPriceId = $this->updatePrice($product, $productId); | |||
| $this->updatePriceTier($product, $productPriceId); | |||
| } | |||
| } | |||
| private function updateProduct(Product $product) | |||
| { | |||
| $alias = 'exact-product-'.$product->getCode(); | |||
| $shippingWeight = $product->getGrossWeight() !== '' ? | |||
| ($product->getNetWeight() !== '' ? $product->getNetWeight() : '') : ''; | |||
| $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 | |||
| ) VALUES ( | |||
| 0, | |||
| 0, | |||
| ".time().", | |||
| '', | |||
| ".time().", | |||
| 2, | |||
| '', | |||
| '$alias', | |||
| '', | |||
| '".$product->getCode()."', | |||
| '".$product->getDescription()."', | |||
| '".$product->getExtraDescription()."', | |||
| '', | |||
| '', | |||
| '$shippingWeight', | |||
| '', | |||
| '', | |||
| 0.00, | |||
| '', | |||
| '', | |||
| '', | |||
| '0', | |||
| '', | |||
| '' | |||
| )"; | |||
| $this->dbHandle->query($sql); | |||
| return $this->dbHandle->lastInsertId(); | |||
| } | |||
| private function updatePrice(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->dbHandle->query($sql); | |||
| return $this->dbHandle->lastInsertId(); | |||
| } | |||
| private function updatePriceTier(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->dbHandle->query($sql); | |||
| return $this->dbHandle->lastInsertId(); | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| <?php | |||
| require __DIR__ . '/ApiExact.php'; | |||
| require __DIR__ . '/IsotopeDatabaseHandler.php'; | |||
| $apiConnector = new ApiExact(); | |||
| $databaseHandler = new IsotopeDatabaseHandler(); | |||
| //echo $apiConnector->getAccessToken() . "\n"; | |||
| $products = $apiConnector->getProducts(); | |||
| $databaseHandler->updateProducts($products); | |||
| $b = 0; | |||
| @@ -0,0 +1 @@ | |||
| {"access_token":"stampDE001.gAAAAHwOSGEkvQm9cR0GLsLKi-DF5HztPTxwFs1cElb0YqYwsA4Du2xEN2_QDZQaRP2S7JqojAxH7Pgh9bGX8aMk_w2Mc6E8Za3DfOCs5_IU89TUfIZfo-OlriYaWguDBjIhWDV7uyb0fyMk83Vplk4hffbmyTdvoTA1GbmvO9x-wEWYVAIAAIAAAAAMQCUSoZTK5-qcCHPI6G15WhlRHy2qHRJKJIJ3ugg6F95dz8Wq4oLsgD4Ej7n_9zloDUbYzABmiTMs6vu-wa4R3DjXmDtvZ5tFauTNTvlSVdbo-rT5rbHUo_LcdGTdE18njXZDBpK7K58cQXVq4zGxViUoqzrEu8aiqDhfwI93_pAXr1ABdwTj6sFxzeqRTAwIo2WVBG7uwzvYLQCrCUqpzxfxUgwH0FBbjEzcglBx56hNshomL4SNqbqqGIKaK1lNGOKz0jkxnuRdY0-nlBPsi0FN6goQDAE3wfKUFzj8DU0nyQAZhdedAtk4q3AT1mPnM9wuOxIwp3EXDeuYkiCIQNWSvU7WJJaWMqfRPrab86diQ5ffBxWDVc0HnpHgQVWr0peKvzLXS4lx5_pTgwLaJUurvlRbG0LI88x-qcMmGgbj2a54FaF93RrNOtHaheiGZh_CBDwqbT54vrFaxxwp-yNmYLVk2-2c70RUwobUInw9E_eR2RNPU9oHGQTMTQR9at_3jZ5dR68UyrY6BMCGJrqrTvSpXkmJNKhkjP9WHlWtz-dC-gt_m2cYaWifow76NAKHONyRk3-i6Qu3vKDc8vXyyi-RLeX6R0nLYV07p7yHsgSo2E8em5C6KdkHIQjJIanYyHUVf7noHJMuKIrrvaHBfdTuiC1ZbemwzPCsb7sdOftAb5vW3oxSEMmckuCVEROmeTyoZCDAV5oyecNAah73Sx7WLx87npWFhC0F71myL11vkWf5MSt8lQ-o-kZCt0Wws7h9KStVrb3mux9v","token_type":"bearer","expires_in":"600","refresh_token":"stampDE001.vhK0!IAAAANf_ePo7TNLoW4eBzZwdyynZfR7KhO8joJp6EgWw0pF58QEAAAFBqPStZfQ04DNGVJ23edCAkbZsdhwNjz3GfWOQoMaGhf3uoG8iydpqma1dDKY3MmFI5CNNxIKvpiN0jnvHqgi0g6O5QijAmgtB3_sVmzs4L0ODxGt3Befp2rJKu2MMBDySEJ_00XVZzBIKmZqVA6XUflZ7mfbotp22vTRbthYOMZlyz0OIT1leJKHfU8XYgB5Zzp8gPHxsSca93Kh3_3CQRp0ZoGR5YKSh1C2zw8QyVCBU_092gLonzTGHKlaIUd6nVT4oIhzXC9i9Dh7wL-9PSfk7m9OqJ7wX0h-p8wkO4bqjkSJofeglSrAvS3G1U8yo0QpxmJQW1FHsm-7q1s6w4O03LV9LsSZX93Wjm8mMq789tlZFZh7-MqO5_zQcwmgizMVDMbBsxPUMU1ITOrTYBAalNf9kyLvrGE-M4jhK_qQbGQ3FATy25U3QjoEQC9K_PWNyqY2XrzPDHSAoprI6hZxY9lYY71tDg3L10KB6BFXDM6bDj_oWa0BAI-TfO_cKRPhXxt9bAquWxhLYHNFdV-d0Il7FeRgI_Dmp7OsKYuFw6rrd6C8Vuz1AxuTaNT1G3V-ugUn5L-VV12JiLgMTTrBLK6OGh5fooDE3Li8tM7WiA5-K9HgP3Jsesdrl2O7ClBUFRrrhtiQINNgkLN1f","expiry_time":1657581261} | |||