| @@ -71,8 +71,6 @@ class IsotopeDatabaseHandler | |||||
| $sql = | $sql = | ||||
| "UPDATE `tl_iso_product` SET | "UPDATE `tl_iso_product` SET | ||||
| tstamp = ".time().", | tstamp = ".time().", | ||||
| name = '".$product->getDescription()."', | |||||
| description = '".$product->getExtraDescription()."', | |||||
| shipping_weight = '$shippingWeight' | shipping_weight = '$shippingWeight' | ||||
| WHERE `id` = ".$productDbId; | WHERE `id` = ".$productDbId; | ||||
| $this->dbHandle->query($sql); | $this->dbHandle->query($sql); | ||||
| @@ -1,6 +1,6 @@ | |||||
| <?php | <?php | ||||
| class Product | |||||
| class Product | |||||
| { | { | ||||
| const VAT = 0.19; | const VAT = 0.19; | ||||
| @@ -1,22 +1,122 @@ | |||||
| <?php | <?php | ||||
| /** | |||||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||||
| * @date 12.07.22 | |||||
| */ | |||||
| namespace App\EventListener; | namespace App\EventListener; | ||||
| use Contao\CoreBundle\ServiceAnnotation\Hook; | use Contao\CoreBundle\ServiceAnnotation\Hook; | ||||
| use Contao\FrontendTemplate; | use Contao\FrontendTemplate; | ||||
| use Contao\Module; | 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 | class PostCheckoutListener | ||||
| { | { | ||||
| const WAREHOUSE_ID = 'd8a6a9b8-d0ac-4d36-8d00-bd420d0d81f5'; | |||||
| /** | /** | ||||
| * @Hook("postCheckout") | * @Hook("postCheckout") | ||||
| */ | */ | ||||
| public function __invoke(int $userId, array $userData, Module $module): void | |||||
| // 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) | |||||
| { | { | ||||
| throw new \Exception('ARRRRRRRGGGGGGGGGG'); | |||||
| 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 @@ | |||||
| {"access_token":"stampDE001.gAAAADRG83iY-fhuf-2rSWvPzCjE4DL5hlcUcyDpkEk4rP6B5FF9PnD3qKjveOit6vWjnxeM0UzXu1kUOX0cR0AV0jf16XSQy_-3nQ6XsTx5wIucuNmT52WpjP7-LU2E3TzSvjVilP-ufMtgTLnmuLvtC7ALRoAcoiNrHuU6sKACxco0VAIAAIAAAABYJx_THjmOsX2jvj-lBpx0NXCBxY4506xGpS8bmvY0JoYWsebqFuORCSupfTU3VebYy-Vn_oyv7nUkBwPpsmLUVmJWJB-VX5X9dTj23-tJMYkUxjNzvdrbbTdnwuNbIFYws_5bwsJ-1NiP9nZJYllsGstqbvUyfGmsnXrBSyX6eujzHH7eWOEfL7wHIfVbl2Eg2HwP1w3vMzUgG9h-P7OCw69-EpsfJVaOKYWL_F39zic6yBf0oWDSEBvsbIeL2o4KdkCSJD0NY0bqGViDfd_vJVfXP9lossW9fu-rtQNehko5GSkEybe4x30kDbUZHqB_LhL8a5_b9zqPF79-iDxN_vuY_FDCopvBBNUjcyeDGZcn_GDpzqEhK4BxIqJFWlw2umG4qd61OB3WkSXv952cPKBsVQJWoZWrudQymHa6WLy27DXNoyS3WJiO2VnyZybszl0Mk1G6_ZHso4PmgEsOdM2RyrsGaN7_pOoIyOG7c2ZlBLJTM_q_lDVVaAfPrNcrBon2UTFDqUtexlrTpt50nEjBUlxOyrDva1KOGyfHrHdMGEpetr6YoEmAbcMM6jId72OHLy2ztfoYL91kzXScnEAJB3PL3-btD85hU8rBaFYAgZemoPY0T4g6ZqBcYLXoMDT5ltW2McI74u819Q1V7ZrAPkVB2oICaFppnKzOe9ot7DChNk5nrNFUaK49hosQpPtTUzPyfPb2oX3Z5GhjHe2X4b6wT4DiSORY6gtu7ZdPyJeDWnoyCE_c1m9PLj_v4i6cyfWMJHbIaCHLuZGU","token_type":"bearer","expires_in":"600","refresh_token":"stampDE001.vhK0!IAAAAG4XGo_RV3d60tiy1gRnVAQrHp9xnEP4_CuBLQ_D5Z0C8QEAAAFnedzeXXOKto6NMohmWMQOWm7Qptgu2o0z6LYlWxTRWf9IPpRYC-fa5XigKwDbB5lquTf8gj90Fg0YkuniSwf9jIBUjBOzul0ZdAPWdolHZLAQtHQr3a53ylG4d7yLyrQCMctPl8yUHNWk1e5PH4YCOZH9lKTW7qWlt1SJAfLcgPa2F0I-9XnS-n-AZK7bxpCL4coztVGCA6vP9igYofpArxYc7W-9rNbzTOyF-fxp_hXzTtCaPetA5Zpg8v-4sx3Mvx1AY_NscwCcG-V1mJnolxlLowgL8fBeUdv9WrWrx5uHVqcKyhAYCB7BR_QqZfgl5mlv_62hbK2s_HKNcQsG591qymyG5KYjOlpSlKyEe3h-hGYaP3PaD-xiUx87C9Z3iRPKWE79i_9B6H4rCcltIJARKp3UGUJbJtYikzeexB7wCM3uHkcljXziwXkBO9PIVWTJhsauv7BTSAmyODuuSSbL1PTGHPIJJdRCQUn8U0xOlSvWWngpLD9CenpAiMRt8-uFfC3zAOBxIHZ5FNDuNlcvKhgEG0Q-94qFySKFt1J5xCUCKpKZ1VQyDz33uylXzb30SoGLmAIPVPhMFdJo08xIAFZaPRQKsvqOEj7M4t3KeGYkehyAv7ugQcJrlWo5TD9kYlHA1CRZs5YiK2a7","expiry_time":1657816808} | |||||
| @@ -1 +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":1657726518} | |||||
| {"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} | |||||
| @@ -0,0 +1,441 @@ | |||||
| <?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\Controller; | |||||
| use Contao\MemberModel; | |||||
| use Contao\Model; | |||||
| use Contao\StringUtil; | |||||
| use Contao\System; | |||||
| use Database\Result; | |||||
| use Haste\Util\Format; | |||||
| use Isotope\Backend; | |||||
| use Isotope\Interfaces\IsotopeProductCollection; | |||||
| use Isotope\Interfaces\IsotopeVatNoValidator; | |||||
| use Isotope\Isotope; | |||||
| /** | |||||
| * Class Address | |||||
| * | |||||
| * @property int $id | |||||
| * @property int $pid | |||||
| * @property string $ptable | |||||
| * @property string $label | |||||
| * @property int $store_id | |||||
| * @property string $gender | |||||
| * @property string $salutation | |||||
| * @property string $firstname | |||||
| * @property string $lastname | |||||
| * @property int $dateOfBirth | |||||
| * @property string $company | |||||
| * @property string $vat_no | |||||
| * @property string $street_1 | |||||
| * @property string $street_2 | |||||
| * @property string $street_3 | |||||
| * @property string $postal | |||||
| * @property string $city | |||||
| * @property string $subdivision | |||||
| * @property string $country | |||||
| * @property string $phone | |||||
| * @property string $email | |||||
| * @property bool $isDefaultShipping | |||||
| * @property bool $isDefaultBilling | |||||
| */ | |||||
| class Address extends Model | |||||
| { | |||||
| /** | |||||
| * Table | |||||
| * @var string | |||||
| */ | |||||
| protected static $strTable = 'tl_iso_address'; | |||||
| /** | |||||
| * Construct the model | |||||
| * | |||||
| * @param Result $objResult | |||||
| */ | |||||
| public function __construct(Result $objResult = null) | |||||
| { | |||||
| parent::__construct($objResult); | |||||
| if (!\is_array($GLOBALS['ISO_ADR'] ?? null)) { | |||||
| Controller::loadDataContainer(static::$strTable); | |||||
| System::loadLanguageFile('addresses'); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * @return string | |||||
| */ | |||||
| public function __toString() | |||||
| { | |||||
| try { | |||||
| return $this->generate(); | |||||
| } catch (\Exception $e) { | |||||
| return ''; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Check if the address has a valid VAT number | |||||
| * | |||||
| * @param Config $config | |||||
| * | |||||
| * @return bool | |||||
| * | |||||
| * @throws \LogicException if a validator does not implement the correct interface | |||||
| * @throws \RuntimeException if a validators reports an error about the VAT number | |||||
| */ | |||||
| public function hasValidVatNo(Config $config = null) | |||||
| { | |||||
| if (null === $config) { | |||||
| $config = Isotope::getConfig(); | |||||
| } | |||||
| $validators = StringUtil::deserialize($config->vatNoValidators); | |||||
| // if no validators are enabled, the VAT No is always valid | |||||
| if (!\is_array($validators) || 0 === \count($validators)) { | |||||
| return true; | |||||
| } | |||||
| foreach ($validators as $class) { | |||||
| $service = new $class(); | |||||
| if (!($service instanceof IsotopeVatNoValidator)) { | |||||
| throw new \LogicException($class . ' does not implement IsotopeVatNoValidator interface'); | |||||
| } | |||||
| $result = $service->validate($this); | |||||
| if (true === $result) { | |||||
| return true; | |||||
| } | |||||
| } | |||||
| return false; | |||||
| } | |||||
| /** | |||||
| * Return formatted address (hCard) | |||||
| * | |||||
| * @param array $arrFields | |||||
| * | |||||
| * @return string | |||||
| * | |||||
| * @throws \Exception on error parsing simple tokens | |||||
| */ | |||||
| public function generate($arrFields = null) | |||||
| { | |||||
| // We need a country to format the address, use default country if none is available | |||||
| $strCountry = $this->country ?: Isotope::getConfig()->country; | |||||
| // Use generic format if no country specific format is available | |||||
| $strFormat = $GLOBALS['ISO_ADR'][$strCountry] ?? $GLOBALS['ISO_ADR']['generic']; | |||||
| $arrTokens = $this->getTokens($arrFields); | |||||
| return StringUtil::parseSimpleTokens($strFormat, $arrTokens); | |||||
| } | |||||
| /** | |||||
| * Return this address formatted as text | |||||
| * | |||||
| * @param array $arrFields | |||||
| * | |||||
| * @return string | |||||
| * | |||||
| * @deprecated use Address::generate() and strip_tags | |||||
| * @throws \Exception on invalid simple tokens | |||||
| */ | |||||
| public function generateText($arrFields = null) | |||||
| { | |||||
| return strip_tags($this->generate($arrFields)); | |||||
| } | |||||
| /** | |||||
| * Return an address formatted with HTML (hCard) | |||||
| * | |||||
| * @param array $arrFields | |||||
| * | |||||
| * @return string | |||||
| * | |||||
| * @deprecated use Address::generate() | |||||
| * @throws \Exception on invalid simple tokens | |||||
| */ | |||||
| public function generateHtml($arrFields = null) | |||||
| { | |||||
| return $this->generate($arrFields); | |||||
| } | |||||
| /** | |||||
| * Compile the list of hCard tokens for this address | |||||
| * | |||||
| * @param array $arrFields | |||||
| * | |||||
| * @return array | |||||
| */ | |||||
| public function getTokens($arrFields = null) | |||||
| { | |||||
| if (!\is_array($arrFields)) { | |||||
| $arrFields = Isotope::getConfig()->getBillingFieldsConfig(); | |||||
| } | |||||
| $arrTokens = array('outputFormat' => 'html'); | |||||
| foreach ($arrFields as $arrField) { | |||||
| $strField = $arrField['value']; | |||||
| // Set an empty value for disabled fields, otherwise the token would not be replaced | |||||
| if (!$arrField['enabled']) { | |||||
| $arrTokens[$strField] = ''; | |||||
| continue; | |||||
| } | |||||
| if ('subdivision' === $strField && $this->subdivision != '') { | |||||
| [$country, $subdivision] = explode('-', $this->subdivision); | |||||
| $arrTokens['subdivision_abbr'] = $subdivision; | |||||
| $arrTokens['subdivision'] = Backend::getLabelForSubdivision($country, $this->subdivision); | |||||
| continue; | |||||
| } | |||||
| $arrTokens[$strField] = Format::dcaValue(static::$strTable, $strField, $this->$strField); | |||||
| } | |||||
| /** | |||||
| * Generate hCard fields | |||||
| * See http://microformats.org/wiki/hcard | |||||
| */ | |||||
| // Set "fn" (full name) to company if no first- and lastname is given | |||||
| if ($arrTokens['company'] != '') { | |||||
| $fn = $arrTokens['company']; | |||||
| $fnCompany = ' fn'; | |||||
| } else { | |||||
| $fn = trim($arrTokens['firstname'] . ' ' . $arrTokens['lastname']); | |||||
| $fnCompany = ''; | |||||
| } | |||||
| $street = implode('<br>', array_filter([$this->street_1, $this->street_2, $this->street_3])); | |||||
| $arrTokens += [ | |||||
| 'hcard_fn' => $fn ? '<span class="fn">' . $fn . '</span>' : '', | |||||
| 'hcard_n' => ($arrTokens['firstname'] || $arrTokens['lastname']) ? '1' : '', | |||||
| 'hcard_honorific_prefix' => $arrTokens['salutation'] ? '<span class="honorific-prefix">' . $arrTokens['salutation'] . '</span>' : '', | |||||
| 'hcard_given_name' => $arrTokens['firstname'] ? '<span class="given-name">' . $arrTokens['firstname'] . '</span>' : '', | |||||
| 'hcard_family_name' => $arrTokens['lastname'] ? '<span class="family-name">' . $arrTokens['lastname'] . '</span>' : '', | |||||
| 'hcard_org' => $arrTokens['company'] ? '<div class="org' . $fnCompany . '">' . $arrTokens['company'] . '</div>' : '', | |||||
| 'hcard_email' => $arrTokens['email'] ? '<a href="mailto:' . $arrTokens['email'] . '">' . $arrTokens['email'] . '</a>' : '', | |||||
| 'hcard_tel' => $arrTokens['phone'] ? '<div class="tel">' . $arrTokens['phone'] . '</div>' : '', | |||||
| 'hcard_adr' => ($street || $arrTokens['city'] || $arrTokens['postal'] || $arrTokens['subdivision'] || $arrTokens['country']) ? '1' : '', | |||||
| 'hcard_street_address' => $street ? '<div class="street-address">' . $street . '</div>' : '', | |||||
| 'hcard_locality' => $arrTokens['city'] ? '<span class="locality">' . $arrTokens['city'] . '</span>' : '', | |||||
| 'hcard_region' => $arrTokens['subdivision'] ? '<span class="region">' . $arrTokens['subdivision'] . '</span>' : '', | |||||
| 'hcard_region_abbr' => !empty($arrTokens['subdivision_abbr']) ? '<abbr class="region" title="' . $arrTokens['subdivision'] . '">' . $arrTokens['subdivision_abbr'] . '</abbr>' : '', | |||||
| 'hcard_postal_code' => $arrTokens['postal'] ? '<span class="postal-code">' . $arrTokens['postal'] . '</span>' : '', | |||||
| 'hcard_country_name' => $arrTokens['country'] ? '<div class="country-name">' . $arrTokens['country'] . '</div>' : '', | |||||
| ]; | |||||
| return $arrTokens; | |||||
| } | |||||
| /** | |||||
| * Find address for member, automatically checking the current store ID and tl_member parent table | |||||
| * | |||||
| * @param int $intMember | |||||
| * @param array $arrOptions | |||||
| * | |||||
| * @return \Model\Collection|null | |||||
| */ | |||||
| public static function findForMember($intMember, array $arrOptions = array()) | |||||
| { | |||||
| return static::findBy( | |||||
| array('pid=?', 'ptable=?', 'store_id=?'), | |||||
| array($intMember, 'tl_member', Isotope::getCart()->store_id), | |||||
| $arrOptions | |||||
| ); | |||||
| } | |||||
| /** | |||||
| * Find address by ID and member, automatically checking the current store ID and tl_member parent table | |||||
| * | |||||
| * @param int $intId | |||||
| * @param int $intMember | |||||
| * @param array $arrOptions | |||||
| * | |||||
| * @return Address|null | |||||
| */ | |||||
| public static function findOneForMember($intId, $intMember, array $arrOptions = array()) | |||||
| { | |||||
| return static::findOneBy( | |||||
| array('id=?', 'pid=?', 'ptable=?', 'store_id=?'), | |||||
| array($intId, $intMember, 'tl_member', Isotope::getCart()->store_id), | |||||
| $arrOptions | |||||
| ); | |||||
| } | |||||
| /** | |||||
| * Find default billing adddress for a member, automatically checking the current store ID and tl_member parent table | |||||
| * @param int | |||||
| * @param array | |||||
| * @return static|null | |||||
| */ | |||||
| public static function findDefaultBillingForMember($intMember, array $arrOptions = array()) | |||||
| { | |||||
| return static::findOneBy( | |||||
| array('pid=?', 'ptable=?', 'store_id=?', 'isDefaultBilling=?'), | |||||
| array($intMember, 'tl_member', Isotope::getCart()->store_id, '1'), | |||||
| $arrOptions | |||||
| ); | |||||
| } | |||||
| /** | |||||
| * Find default shipping adddress for a member, automatically checking the current store ID and tl_member parent table | |||||
| * @param int | |||||
| * @param array | |||||
| * @return static|null | |||||
| */ | |||||
| public static function findDefaultShippingForMember($intMember, array $arrOptions = array()) | |||||
| { | |||||
| return static::findOneBy(array('pid=?', 'ptable=?', 'store_id=?', 'isDefaultShipping=?'), array($intMember, 'tl_member', Isotope::getCart()->store_id, '1'), $arrOptions); | |||||
| } | |||||
| /** | |||||
| * Find default billing address for a product collection | |||||
| * | |||||
| * @param int $intCollection | |||||
| * @param array $arrOptions | |||||
| * | |||||
| * @return static|null | |||||
| */ | |||||
| public static function findDefaultBillingForProductCollection($intCollection, array $arrOptions = array()) | |||||
| { | |||||
| return static::findOneBy( | |||||
| array('pid=?', 'ptable=?', 'isDefaultBilling=?'), | |||||
| array($intCollection, 'tl_iso_product_collection', '1'), | |||||
| $arrOptions | |||||
| ); | |||||
| } | |||||
| /** | |||||
| * Find default shipping address for a product collection | |||||
| * | |||||
| * @param int $intCollection | |||||
| * @param array $arrOptions | |||||
| * | |||||
| * @return static|null | |||||
| */ | |||||
| public static function findDefaultShippingForProductCollection($intCollection, array $arrOptions = array()) | |||||
| { | |||||
| return static::findOneBy( | |||||
| array('pid=?', 'ptable=?', 'isDefaultShipping=?'), | |||||
| array($intCollection, 'tl_iso_product_collection', '1'), | |||||
| $arrOptions | |||||
| ); | |||||
| } | |||||
| /** | |||||
| * Create a new address for a member and automatically set default properties | |||||
| * | |||||
| * @param int $intMember | |||||
| * @param array|null $arrFill | |||||
| * | |||||
| * @return static | |||||
| */ | |||||
| public static function createForMember($intMember, $arrFill = null) | |||||
| { | |||||
| $objAddress = new static(); | |||||
| $arrData = array( | |||||
| 'pid' => $intMember, | |||||
| 'ptable' => 'tl_member', | |||||
| 'tstamp' => time(), | |||||
| 'store_id' => (int) Isotope::getCart()->store_id, | |||||
| ); | |||||
| if (!empty($arrFill) && \is_array($arrFill) && ($objMember = MemberModel::findByPk($intMember)) !== null) { | |||||
| $arrData = array_merge(static::getAddressDataForMember($objMember, $arrFill), $arrData); | |||||
| } | |||||
| $objAddress->setRow($arrData); | |||||
| return $objAddress; | |||||
| } | |||||
| /** | |||||
| * Create a new address for a product collection | |||||
| * | |||||
| * @param IsotopeProductCollection $objCollection | |||||
| * @param array|null $arrFill an array of member fields to inherit | |||||
| * @param bool $blnDefaultBilling | |||||
| * @param bool $blnDefaultShipping | |||||
| * | |||||
| * @return static | |||||
| */ | |||||
| public static function createForProductCollection( | |||||
| IsotopeProductCollection $objCollection, | |||||
| $arrFill = null, | |||||
| $blnDefaultBilling = false, | |||||
| $blnDefaultShipping = false | |||||
| ) { | |||||
| $objAddress = new static(); | |||||
| $arrData = array( | |||||
| 'pid' => $objCollection->getId(), | |||||
| 'ptable' => 'tl_iso_product_collection', | |||||
| 'tstamp' => time(), | |||||
| 'store_id' => $objCollection->getStoreId(), | |||||
| 'isDefaultBilling' => $blnDefaultBilling ? '1' : '', | |||||
| 'isDefaultShipping' => $blnDefaultShipping ? '1' : '', | |||||
| ); | |||||
| if (!empty($arrFill) && \is_array($arrFill) && ($objMember = $objCollection->getMember()) !== null) { | |||||
| $arrData = array_merge(static::getAddressDataForMember($objMember, $arrFill), $arrData); | |||||
| } | |||||
| if (empty($arrData['country']) && null !== ($objConfig = $objCollection->getConfig())) { | |||||
| if ($blnDefaultBilling) { | |||||
| $arrData['country'] = $objConfig->billing_country ?: $objConfig->country; | |||||
| } elseif ($blnDefaultShipping) { | |||||
| $arrData['country'] = $objConfig->shipping_country ?: $objConfig->country; | |||||
| } | |||||
| } | |||||
| $objAddress->setRow($arrData); | |||||
| return $objAddress; | |||||
| } | |||||
| /** | |||||
| * Generate address data from tl_member, limit to fields enabled in the shop configuration | |||||
| */ | |||||
| public static function getAddressDataForMember(MemberModel $member, array $fields) | |||||
| { | |||||
| return array_intersect_key( | |||||
| array_merge( | |||||
| $member->row(), | |||||
| array( | |||||
| 'street_1' => $member->street, | |||||
| // Trying to guess subdivision by country and state | |||||
| 'subdivision' => strtoupper($member->country . '-' . $member->state) | |||||
| ) | |||||
| ), | |||||
| array_flip($fields) | |||||
| ); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,178 @@ | |||||
| <?php | |||||
| namespace App\ExactApi; | |||||
| use App\ExactApi\Product; | |||||
| //require_once('Product.php'); | |||||
| 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); | |||||
| // | |||||
| // file_put_contents('flo.txt', ob_get_contents()); | |||||
| // 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')) { | |||||
| throw new \Exception($jsonResult); | |||||
| } | |||||
| $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_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; | |||||
| } | |||||
| public function test() | |||||
| { | |||||
| return 'fuck off contao'; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,197 @@ | |||||
| <?php | |||||
| namespace App\ExactApi; | |||||
| use App\ExactApi\Product; | |||||
| 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 processProducts(array $products) | |||||
| { | |||||
| $this->dbHandle->beginTransaction(); | |||||
| $sql = "SELECT * FROM `tl_iso_product`"; | |||||
| $stmt = $this->dbHandle->query($sql); | |||||
| $dbProducts = $stmt->fetchAll(PDO::FETCH_ASSOC); | |||||
| $sql = "SELECT * FROM `tl_iso_product_price`"; | |||||
| $stmt = $this->dbHandle->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->dbHandle->commit(); | |||||
| } | |||||
| private function updateProduct(Product $product, $productDbId) | |||||
| { | |||||
| $shippingWeight = $product->getGrossWeight() !== '' ? | |||||
| ($product->getNetWeight() !== '' ? $product->getNetWeight() : '') : ''; | |||||
| $sql = | |||||
| "UPDATE `tl_iso_product` SET | |||||
| tstamp = ".time().", | |||||
| shipping_weight = '$shippingWeight' | |||||
| WHERE `id` = ".$productDbId; | |||||
| $this->dbHandle->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->dbHandle->query($sql); | |||||
| } | |||||
| private function createProduct(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, | |||||
| exact_id | |||||
| ) VALUES ( | |||||
| 0, | |||||
| 0, | |||||
| ".time().", | |||||
| '', | |||||
| ".time().", | |||||
| 2, | |||||
| '', | |||||
| '$alias', | |||||
| '', | |||||
| '".$product->getCode()."', | |||||
| '".$product->getDescription()."', | |||||
| '".$product->getExtraDescription()."', | |||||
| '', | |||||
| '', | |||||
| '$shippingWeight', | |||||
| '', | |||||
| '', | |||||
| 0.00, | |||||
| '', | |||||
| '', | |||||
| '', | |||||
| '0', | |||||
| '', | |||||
| '', | |||||
| '".$product->getId()."' | |||||
| )"; | |||||
| $this->dbHandle->query($sql); | |||||
| return $this->dbHandle->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->dbHandle->query($sql); | |||||
| return $this->dbHandle->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->dbHandle->query($sql); | |||||
| return $this->dbHandle->lastInsertId(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,614 @@ | |||||
| <?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\ProductCollection; | |||||
| use Contao\Controller; | |||||
| use Contao\Message; | |||||
| use Contao\StringUtil; | |||||
| use Contao\System; | |||||
| use Contao\Template; | |||||
| use Haste\Generator\RowClass; | |||||
| use Haste\Util\Format; | |||||
| use Isotope\Interfaces\IsotopeNotificationTokens; | |||||
| use Isotope\Interfaces\IsotopeOrderStatusAware; | |||||
| use Isotope\Interfaces\IsotopePurchasableCollection; | |||||
| use Isotope\Isotope; | |||||
| use Isotope\Model\Address; | |||||
| use Isotope\Model\Document; | |||||
| use Isotope\Model\OrderStatus; | |||||
| use Isotope\Model\ProductCollection; | |||||
| use Isotope\Model\ProductCollectionLog; | |||||
| use NotificationCenter\Model\Notification; | |||||
| /** | |||||
| * Class Order | |||||
| * | |||||
| * Provide methods to handle Isotope orders. | |||||
| * | |||||
| * @method static Order findOneBy(string $strColumn, $varValue, array $arrOptions=array()) | |||||
| * | |||||
| * @property array $checkout_info | |||||
| * @property array $payment_data | |||||
| * @property array $shipping_data | |||||
| * @property string $document_number | |||||
| * @property int $order_status | |||||
| * @property int $date_paid | |||||
| * @property int $date_shipped | |||||
| * @property string $notes | |||||
| * | |||||
| * @method static Order|null findByPk($id) | |||||
| */ | |||||
| class Order extends ProductCollection implements IsotopePurchasableCollection | |||||
| { | |||||
| const STATUS_UPDATE_SKIP_NOTIFICATION = 1; | |||||
| const STATUS_UPDATE_SKIP_LOG = 2; | |||||
| /** | |||||
| * @inheritdoc | |||||
| */ | |||||
| public function isPaid() | |||||
| { | |||||
| // Order is paid if a payment date is set | |||||
| if (null !== $this->date_paid && $this->date_paid <= time()) { | |||||
| return true; | |||||
| } | |||||
| // Otherwise we check the orderstatus checkbox | |||||
| try { | |||||
| /** @var OrderStatus $objStatus */ | |||||
| $objStatus = $this->getRelated('order_status'); | |||||
| return (null !== $objStatus && $objStatus->isPaid()); | |||||
| } catch (\Exception $e) { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * @inheritdoc | |||||
| */ | |||||
| public function getDatePaid() | |||||
| { | |||||
| return $this->date_paid; | |||||
| } | |||||
| /** | |||||
| * @inheritdoc | |||||
| */ | |||||
| public function setDatePaid($timestamp = null) | |||||
| { | |||||
| $this->date_paid = $timestamp; | |||||
| } | |||||
| /** | |||||
| * @inheritdoc | |||||
| */ | |||||
| public function isShipped() | |||||
| { | |||||
| return null !== $this->date_shipped; | |||||
| } | |||||
| /** | |||||
| * @inheritdoc | |||||
| */ | |||||
| public function getDateShipped() | |||||
| { | |||||
| return $this->date_shipped; | |||||
| } | |||||
| /** | |||||
| * @inheritdoc | |||||
| */ | |||||
| public function setDateShipped($timestamp = null) | |||||
| { | |||||
| $this->date_shipped = $timestamp; | |||||
| } | |||||
| /** | |||||
| * Returns true if checkout has been completed | |||||
| * | |||||
| * @return bool | |||||
| */ | |||||
| public function isCheckoutComplete() | |||||
| { | |||||
| return (bool) $this->checkout_complete; | |||||
| } | |||||
| /** | |||||
| * Get label for current order status | |||||
| * | |||||
| * @return string | |||||
| */ | |||||
| public function getStatusLabel() | |||||
| { | |||||
| try { | |||||
| /** @var OrderStatus $objStatus */ | |||||
| $objStatus = $this->getRelated('order_status'); | |||||
| return (null === $objStatus) ? '' : $objStatus->getName(); | |||||
| } catch (\Exception $e) { | |||||
| return ''; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Get the alias for current order status | |||||
| * | |||||
| * @return string | |||||
| */ | |||||
| public function getStatusAlias() | |||||
| { | |||||
| try { | |||||
| /** @var OrderStatus $objStatus */ | |||||
| $objStatus = $this->getRelated('order_status'); | |||||
| return null === $objStatus ? $this->order_status : $objStatus->getAlias(); | |||||
| } catch (\Exception $e) { | |||||
| return $this->order_status; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Process the order checkout | |||||
| * | |||||
| * @return bool | |||||
| */ | |||||
| public function checkout() | |||||
| { | |||||
| if ($this->isCheckoutComplete()) { | |||||
| return true; | |||||
| } | |||||
| // Finish and lock the order | |||||
| // (do this now, because otherwise surcharges etc. will not be loaded form the database) | |||||
| $this->checkout_complete = true; | |||||
| $this->generateDocumentNumber( | |||||
| $this->getConfig()->orderPrefix, | |||||
| (int) $this->getConfig()->orderDigits | |||||
| ); | |||||
| if (!$this->isLocked()) { | |||||
| $this->lock(); | |||||
| } | |||||
| System::log('New order ID ' . $this->id . ' has been placed', __METHOD__, TL_ACCESS); | |||||
| // Delete cart after migrating to order | |||||
| if (($objCart = Cart::findByPk($this->source_collection_id)) !== null) { | |||||
| $objCart->delete(); | |||||
| } | |||||
| // Delete all other orders that relate to the current cart | |||||
| if (($objOrders = static::findSiblingsBy('source_collection_id', $this)) !== null) { | |||||
| /** @var static $objOrder */ | |||||
| foreach ($objOrders as $objOrder) { | |||||
| if (!$objOrder->isCheckoutComplete()) { | |||||
| $objOrder->delete(true); | |||||
| } | |||||
| } | |||||
| } | |||||
| $notificationIds = array_filter(explode(',', $this->nc_notification)); | |||||
| // Send the notifications | |||||
| if (\count($notificationIds) > 0) { | |||||
| foreach ($notificationIds as $notificationId) { | |||||
| // Generate tokens | |||||
| $arrTokens = $this->getNotificationTokens($notificationId); | |||||
| // Send notification | |||||
| $blnNotificationError = true; | |||||
| /** @var Notification $objNotification */ | |||||
| if (($objNotification = Notification::findByPk($notificationId)) !== null) { | |||||
| $arrResult = $objNotification->send($arrTokens, $this->language); | |||||
| if (\count($arrResult) > 0 && !\in_array(false, $arrResult, true)) { | |||||
| $blnNotificationError = false; | |||||
| } | |||||
| } | |||||
| if ($blnNotificationError === true) { | |||||
| System::log('Error sending new order notification for order ID ' . $this->id, __METHOD__, TL_ERROR); | |||||
| } | |||||
| } | |||||
| } | |||||
| // Set order status only if a payment module has not already set it | |||||
| if ($this->order_status == 0) { | |||||
| $this->updateOrderStatus($this->getRelated('config_id')->orderstatus_new); | |||||
| } | |||||
| // !HOOK: post-process checkout | |||||
| if (isset($GLOBALS['ISO_HOOKS']['postCheckout']) && \is_array($GLOBALS['ISO_HOOKS']['postCheckout'])) { | |||||
| // Generate the default notification tokens if none set yet | |||||
| if (!isset($arrTokens)) { | |||||
| $arrTokens = $this->getNotificationTokens(0); | |||||
| } | |||||
| foreach ($GLOBALS['ISO_HOOKS']['postCheckout'] as $callback) { | |||||
| System::importStatic($callback[0])->{$callback[1]}($this, $arrTokens); | |||||
| } | |||||
| } | |||||
| return true; | |||||
| } | |||||
| /** | |||||
| * Complete order if the checkout has been made. This will cleanup session data | |||||
| * | |||||
| * @return bool | |||||
| */ | |||||
| public function complete() | |||||
| { | |||||
| if ($this->isCheckoutComplete()) { | |||||
| unset($_SESSION['CHECKOUT_DATA'], $_SESSION['FILES']); | |||||
| // Retain custom config ID | |||||
| if (($objCart = Isotope::getCart()) !== null && $objCart->config_id != $this->config_id) { | |||||
| $objCart->config_id = $this->config_id; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| /** | |||||
| * Update the status of this order and trigger actions (email & hook) | |||||
| * | |||||
| * @param int|array $updates | |||||
| * @param int $flags Order::STATUS_UPDATE_SKIP_NOTIFICATION and/or Order::STATUS_UPDATE_SKIP_LOG | |||||
| * | |||||
| * @return bool | |||||
| */ | |||||
| public function updateOrderStatus($updates, $flags = 0) | |||||
| { | |||||
| // For BC reasons the parameter can be the new order status ID | |||||
| if (!is_array($updates)) { | |||||
| $updates = ['order_status' => $updates]; | |||||
| } | |||||
| $previous = []; | |||||
| $hasChanges = false; | |||||
| foreach ($updates as $k => $v) { | |||||
| $previous[$k] = $this->{$k}; | |||||
| $hasChanges = $hasChanges ?: $v !== $previous[$k]; | |||||
| } | |||||
| if (!isset($updates['order_status'])) { | |||||
| throw new \LogicException('You must update the order status when calling Order::updateOrderStatus()'); | |||||
| } | |||||
| if (!$hasChanges) { | |||||
| return true; | |||||
| } | |||||
| /** @var OrderStatus $objNewStatus */ | |||||
| $objNewStatus = OrderStatus::findByPk($updates['order_status']); | |||||
| if (null === $objNewStatus) { | |||||
| return false; | |||||
| } | |||||
| // !HOOK: allow to cancel a status update | |||||
| if (isset($GLOBALS['ISO_HOOKS']['preOrderStatusUpdate']) | |||||
| && \is_array($GLOBALS['ISO_HOOKS']['preOrderStatusUpdate']) | |||||
| ) { | |||||
| foreach ($GLOBALS['ISO_HOOKS']['preOrderStatusUpdate'] as $callback) { | |||||
| $blnCancel = System::importStatic($callback[0])->{$callback[1]}($this, $objNewStatus, $updates); | |||||
| if ($blnCancel === true) { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| } | |||||
| // Add the payment date if there is none | |||||
| if ($objNewStatus->isPaid() && $this->date_paid == '' && !isset($updates['date_paid'])) { | |||||
| $updates['date_paid'] = time(); | |||||
| } | |||||
| // Store old status and set the new one | |||||
| $oldStatusId = $this->order_status; | |||||
| $oldStatusLabel = $this->getStatusLabel(); | |||||
| foreach ($updates as $k => $v) { | |||||
| $this->{$k} = $v; | |||||
| } | |||||
| $this->save(); | |||||
| if (!($flags & static::STATUS_UPDATE_SKIP_NOTIFICATION)) { | |||||
| // Trigger notification | |||||
| $blnNotificationError = null; | |||||
| foreach (array_filter(explode(',', $objNewStatus->notification)) as $notificationId) { | |||||
| $arrTokens = $this->getNotificationTokens($notificationId); | |||||
| // Override order status and save the old one to the tokens too | |||||
| $arrTokens['order_status_id'] = $objNewStatus->id; | |||||
| $arrTokens['order_status'] = $objNewStatus->getName(); | |||||
| $arrTokens['order_status_old'] = $oldStatusLabel; | |||||
| $arrTokens['order_status_id_old'] = $oldStatusId; | |||||
| $blnNotificationError = true; | |||||
| /** @var Notification $objNotification */ | |||||
| if (($objNotification = Notification::findByPk($notificationId)) !== null) { | |||||
| $arrResult = $objNotification->send($arrTokens, $this->language); | |||||
| if (\in_array(false, $arrResult, true)) { | |||||
| $blnNotificationError = true; | |||||
| System::log( | |||||
| 'Error sending status update notification for order ID ' . $this->id, | |||||
| __METHOD__, | |||||
| TL_ERROR | |||||
| ); | |||||
| } elseif (\count($arrResult) > 0) { | |||||
| $blnNotificationError = false; | |||||
| } | |||||
| } else { | |||||
| System::log('Invalid notification for order status ID ' . $objNewStatus->id, __METHOD__, TL_ERROR); | |||||
| } | |||||
| } | |||||
| if ('BE' === TL_MODE) { | |||||
| Message::addConfirmation($GLOBALS['TL_LANG']['tl_iso_product_collection']['orderStatusUpdate']); | |||||
| if ($blnNotificationError === true) { | |||||
| Message::addError($GLOBALS['TL_LANG']['tl_iso_product_collection']['orderStatusNotificationError']); | |||||
| } elseif ($blnNotificationError === false) { | |||||
| Message::addConfirmation($GLOBALS['TL_LANG']['tl_iso_product_collection']['orderStatusNotificationSuccess']); | |||||
| } | |||||
| } | |||||
| } | |||||
| // Add a log entry | |||||
| if (!($flags & static::STATUS_UPDATE_SKIP_LOG)) { | |||||
| $log = new ProductCollectionLog(); | |||||
| $log->pid = $this->id; | |||||
| $log->tstamp = time(); | |||||
| $log->setData([ | |||||
| 'order_status' => $this->order_status, | |||||
| 'date_paid' => $this->date_paid, | |||||
| 'date_shipped' => $this->date_shipped, | |||||
| ]); | |||||
| $log->save(); | |||||
| } | |||||
| // !HOOK: order status has been updated | |||||
| if (isset($GLOBALS['ISO_HOOKS']['postOrderStatusUpdate']) | |||||
| && \is_array($GLOBALS['ISO_HOOKS']['postOrderStatusUpdate']) | |||||
| ) { | |||||
| foreach ($GLOBALS['ISO_HOOKS']['postOrderStatusUpdate'] as $callback) { | |||||
| System::importStatic($callback[0])->{$callback[1]}($this, $oldStatusId, $objNewStatus); | |||||
| } | |||||
| } | |||||
| // Trigger payment and shipping methods that implement the interface | |||||
| if (($objPayment = $this->getPaymentMethod()) !== null && $objPayment instanceof IsotopeOrderStatusAware) { | |||||
| $objPayment->onOrderStatusUpdate($this, $oldStatusId, $objNewStatus); | |||||
| } | |||||
| if (($objShipping = $this->getShippingMethod()) !== null && $objShipping instanceof IsotopeOrderStatusAware) { | |||||
| $objShipping->onOrderStatusUpdate($this, $oldStatusId, $objNewStatus); | |||||
| } | |||||
| return true; | |||||
| } | |||||
| /** | |||||
| * Retrieve the array of notification data for parsing simple tokens | |||||
| * | |||||
| * @param int $intNotification | |||||
| * | |||||
| * @return array | |||||
| */ | |||||
| public function getNotificationTokens($intNotification) | |||||
| { | |||||
| $objConfig = $this->getRelated('config_id') ?: Isotope::getConfig(); | |||||
| Isotope::setConfig($objConfig); | |||||
| $arrTokens = StringUtil::deserialize($this->email_data, true); | |||||
| $arrTokens['uniqid'] = $this->uniqid; | |||||
| $arrTokens['order_status_id'] = $this->order_status; | |||||
| $arrTokens['order_status'] = $this->getStatusLabel(); | |||||
| $arrTokens['recipient_email'] = $this->getEmailRecipient(); | |||||
| $arrTokens['order_id'] = $this->id; | |||||
| $arrTokens['order_items'] = $this->sumItemsQuantity(); | |||||
| $arrTokens['order_products'] = $this->countItems(); | |||||
| $arrTokens['order_subtotal'] = Isotope::formatPriceWithCurrency($this->getSubtotal(), false, $objConfig->currency); | |||||
| $arrTokens['order_total'] = Isotope::formatPriceWithCurrency($this->getTotal(), false, $objConfig->currency); | |||||
| $arrTokens['document_number'] = $this->document_number; | |||||
| $arrTokens['bank_name'] = $objConfig->bankName; | |||||
| $arrTokens['bank_account'] = $objConfig->bankAccount; | |||||
| $arrTokens['bank_code'] = $objConfig->bankCode; | |||||
| $arrTokens['tax_number'] = $objConfig->taxNumber; | |||||
| $arrTokens['cart_html'] = ''; | |||||
| $arrTokens['cart_text'] = ''; | |||||
| $arrTokens['document'] = ''; | |||||
| // Add all the collection fields | |||||
| foreach ($this->row() as $k => $v) { | |||||
| $arrTokens['collection_' . $k] = $v; | |||||
| } | |||||
| // Add billing/customer address fields | |||||
| if (($objAddress = $this->getBillingAddress()) !== null) { | |||||
| foreach ($objAddress->row() as $k => $v) { | |||||
| $arrTokens['billing_address_' . $k] = Format::dcaValue(Address::getTable(), $k, $v); | |||||
| // @deprecated (use ##billing_address_*##) | |||||
| $arrTokens['billing_' . $k] = $arrTokens['billing_address_' . $k]; | |||||
| } | |||||
| $arrTokens['billing_address'] = $objAddress->generate($objConfig->getBillingFieldsConfig()); | |||||
| // @deprecated (use ##billing_address##) | |||||
| $arrTokens['billing_address_text'] = $arrTokens['billing_address']; | |||||
| } | |||||
| // Add shipping address fields | |||||
| if (($objAddress = $this->getShippingAddress()) !== null) { | |||||
| foreach ($objAddress->row() as $k => $v) { | |||||
| $arrTokens['shipping_address_' . $k] = Format::dcaValue(Address::getTable(), $k, $v); | |||||
| // @deprecated (use ##billing_address_*##) | |||||
| $arrTokens['shipping_' . $k] = $arrTokens['shipping_address_' . $k]; | |||||
| } | |||||
| $arrTokens['shipping_address'] = $objAddress->generate($objConfig->getShippingFieldsConfig()); | |||||
| // Shipping address equals billing address | |||||
| // @deprecated (use ##shipping_address##) | |||||
| if ($objAddress->id == $this->getBillingAddress()->id) { | |||||
| $arrTokens['shipping_address_text'] = ($this->requiresPayment() ? $GLOBALS['TL_LANG']['MSC']['useBillingAddress'] : $GLOBALS['TL_LANG']['MSC']['useCustomerAddress']); | |||||
| } else { | |||||
| $arrTokens['shipping_address_text'] = $arrTokens['shipping_address']; | |||||
| } | |||||
| } | |||||
| // Add payment method info | |||||
| if ($this->hasPayment() && ($objPayment = $this->getPaymentMethod()) !== null) { | |||||
| $arrTokens['payment_id'] = $objPayment->getId(); | |||||
| $arrTokens['payment_label'] = $objPayment->getLabel(); | |||||
| $arrTokens['payment_note'] = $objPayment->getNote(); | |||||
| if ($objPayment instanceof IsotopeNotificationTokens) { | |||||
| $arrTokens = array_merge($arrTokens, $objPayment->getNotificationTokens($this)); | |||||
| } | |||||
| } | |||||
| // Add shipping method info | |||||
| if ($this->hasShipping() && ($objShipping = $this->getShippingMethod()) !== null) { | |||||
| $arrTokens['shipping_id'] = $objShipping->getId(); | |||||
| $arrTokens['shipping_label'] = $objShipping->getLabel(); | |||||
| $arrTokens['shipping_note'] = $objShipping->getNote(); | |||||
| if ($objShipping instanceof IsotopeNotificationTokens) { | |||||
| $arrTokens = array_merge($arrTokens, $objShipping->getNotificationTokens($this)); | |||||
| } | |||||
| } | |||||
| // Add config fields | |||||
| if ($this->getRelated('config_id') !== null) { | |||||
| foreach ($this->getRelated('config_id')->row() as $k => $v) { | |||||
| $arrTokens['config_' . $k] = Format::dcaValue($this->getRelated('config_id')->getTable(), $k, $v); | |||||
| } | |||||
| } | |||||
| // Add member fields | |||||
| if ($this->member > 0 && $this->getRelated('member') !== null) { | |||||
| foreach ($this->getRelated('member')->row() as $k => $v) { | |||||
| $arrTokens['member_' . $k] = Format::dcaValue($this->getRelated('member')->getTable(), $k, $v); | |||||
| } | |||||
| } | |||||
| /** @var Notification|object $objNotification */ | |||||
| if ($intNotification > 0 && ($objNotification = Notification::findByPk($intNotification)) !== null) { | |||||
| /** @var \Isotope\Template|object $objTemplate */ | |||||
| $objTemplate = new \Isotope\Template($objNotification->iso_collectionTpl); | |||||
| $objTemplate->isNotification = true; | |||||
| $this->addToTemplate( | |||||
| $objTemplate, | |||||
| array( | |||||
| 'gallery' => $objNotification->iso_gallery, | |||||
| 'sorting' => static::getItemsSortingCallable($objNotification->iso_orderCollectionBy), | |||||
| ) | |||||
| ); | |||||
| $arrTokens['cart_html'] = Controller::replaceInsertTags($objTemplate->parse(), false); | |||||
| $objTemplate->textOnly = true; | |||||
| $arrTokens['cart_text'] = strip_tags(Controller::replaceInsertTags($objTemplate->parse(), true)); | |||||
| // Generate and "attach" document | |||||
| /** @var \Isotope\Interfaces\IsotopeDocument $objDocument */ | |||||
| if ($objNotification->iso_document > 0 | |||||
| && (($objDocument = Document::findByPk($objNotification->iso_document)) !== null) | |||||
| ) { | |||||
| $strFilePath = $objDocument->outputToFile($this, TL_ROOT . '/system/tmp'); | |||||
| $arrTokens['document'] = str_replace(TL_ROOT . '/', '', $strFilePath); | |||||
| } | |||||
| } | |||||
| // !HOOK: add custom email tokens | |||||
| if (isset($GLOBALS['ISO_HOOKS']['getOrderNotificationTokens']) | |||||
| && \is_array($GLOBALS['ISO_HOOKS']['getOrderNotificationTokens']) | |||||
| ) { | |||||
| foreach ($GLOBALS['ISO_HOOKS']['getOrderNotificationTokens'] as $callback) { | |||||
| $arrTokens = System::importStatic($callback[0])->{$callback[1]}($this, $arrTokens); | |||||
| } | |||||
| } | |||||
| return $arrTokens; | |||||
| } | |||||
| /** | |||||
| * Include downloads when adding items to template | |||||
| * | |||||
| * @return array | |||||
| */ | |||||
| protected function addItemsToTemplate(Template $objTemplate, $varCallable = null) | |||||
| { | |||||
| $taxIds = []; | |||||
| $arrItems = []; | |||||
| $arrAllDownloads = []; | |||||
| foreach ($this->getItems($varCallable) as $objItem) { | |||||
| $arrDownloads = []; | |||||
| $arrItem = $this->generateItem($objItem); | |||||
| foreach ($objItem->getDownloads() as $objDownload) { | |||||
| $arrDownloads = array_merge($arrDownloads, $objDownload->getForTemplate($this->isPaid(), $this->orderdetails_page)); | |||||
| } | |||||
| $arrItem['downloads'] = $arrDownloads; | |||||
| $arrAllDownloads = array_merge($arrAllDownloads, $arrDownloads); | |||||
| $taxIds[] = $arrItem['tax_id']; | |||||
| $arrItems[] = $arrItem; | |||||
| } | |||||
| RowClass::withKey('rowClass')->addCount('row_')->addFirstLast('row_')->addEvenOdd('row_')->applyTo($arrItems); | |||||
| $objTemplate->items = $arrItems; | |||||
| $objTemplate->downloads = $arrAllDownloads; | |||||
| $objTemplate->total_tax_ids = \count(array_count_values($taxIds)); | |||||
| return $arrItems; | |||||
| } | |||||
| /** | |||||
| * Generate unique order ID including the order prefix | |||||
| * | |||||
| * @return string | |||||
| */ | |||||
| protected function generateUniqueId() | |||||
| { | |||||
| if (!empty($this->arrData['uniqid'])) { | |||||
| return $this->arrData['uniqid']; | |||||
| } | |||||
| $objConfig = $this->getConfig(); | |||||
| if (null === $objConfig) { | |||||
| $objConfig = Isotope::getConfig(); | |||||
| } | |||||
| return uniqid( | |||||
| Controller::replaceInsertTags((string) $objConfig->orderPrefix, false), | |||||
| true | |||||
| ); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,147 @@ | |||||
| <?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 | |||||
| { | |||||
| //$a = $order->getBillingAddress(); | |||||
| // ob_start(); | |||||
| // var_dump($order); | |||||
| // file_put_contents('flo.txt', ob_get_contents()); | |||||
| // ob_end_clean(); | |||||
| // | |||||
| // ob_start(); | |||||
| // var_dump($item); | |||||
| // file_put_contents('dan.txt', ob_get_contents()); | |||||
| // ob_end_clean(); | |||||
| // throw new \Exception('ARRRRRRRGGGGGGGGGG'); | |||||
| $apiExact = new ApiExact(); | |||||
| ob_start(); | |||||
| /** @var ProductCollectionItem $orderItem */ | |||||
| foreach($order->getItems() as $orderItem) { | |||||
| /** @var Product $orderItem */ | |||||
| $product = $orderItem->getProduct(); | |||||
| var_dump($orderItem->id); | |||||
| var_dump($orderItem->sku); | |||||
| var_dump($product->id); | |||||
| var_dump($product->name); | |||||
| var_dump($product->is_set); | |||||
| } | |||||
| var_dump($apiExact->test()); | |||||
| // var_dump($order->getItems()); | |||||
| //var_dump($order); | |||||
| 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()) | |||||
| // ); | |||||
| // | |||||
| // $this->createSalesOrderData($order, $billingCustomer, $shippingCustomer); | |||||
| // $billingFirstName = $order->getBillingAddress()->firstname; | |||||
| // $billingLastName = $order->getBillingAddress()->lastname; | |||||
| // $billingStreet = $order->getBillingAddress()->street_1; | |||||
| // $billingPostcode = $order->getBillingAddress()->postal; | |||||
| // $billingCity = $order->getBillingAddress()->city; | |||||
| // $billingCompany = $order->getBillingAddress()->company; | |||||
| // $billingPhone = $order->getBillingAddress()->phone; | |||||
| // $billingEmail = $order->getBillingAddress()->email; | |||||
| // | |||||
| // $shippingFirstName = $order->getShippingAddress()->firstname; | |||||
| // $shippingLastName = $order->getShippingAddress()->lastname; | |||||
| // $shippingStreet = $order->getShippingAddress()->street_1; | |||||
| // $shippingPostcode = $order->getShippingAddress()->postal; | |||||
| // $shippingCity = $order->getShippingAddress()->city; | |||||
| // $shippingCompany = $order->getShippingAddress()->company; | |||||
| // $shippingPhone = $order->getShippingAddress()->phone; | |||||
| // $shippingEmail = $order->getShippingAddress()->email; | |||||
| } | |||||
| 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 createSalesOrderData(Order $order, $billingCustomer, $shippingCustomer) | |||||
| { | |||||
| $date = new \DateTime('now'); | |||||
| $orderItems = $order->getItems(); | |||||
| return [ | |||||
| 'Description' => $billingCustomer['Name'], | |||||
| 'OrderDate' => $date->format('m/d/Y H:i:s'), | |||||
| 'OrderedBy' => $billingCustomer['ID'], | |||||
| 'WarehouseID' => self::WAREHOUSE_ID, | |||||
| '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' | |||||
| ], | |||||
| ] | |||||
| ]; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,809 @@ | |||||
| <?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); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,489 @@ | |||||
| <?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,7 @@ | |||||
| <?php | |||||
| require __DIR__ . '/ApiExact.php'; | |||||
| require __DIR__ . '/IsotopeDatabaseHandler.php'; | |||||
| $apiExact = new ApiExact(); | |||||
| var_dump($apiExact->createCustomer()); | |||||
| @@ -0,0 +1,7 @@ | |||||
| <?php | |||||
| require __DIR__ . '/ApiExact.php'; | |||||
| require __DIR__ . '/IsotopeDatabaseHandler.php'; | |||||
| $apiExact = new ApiExact(); | |||||
| var_dump($apiExact->createSalesOrder()); | |||||
| @@ -0,0 +1,9 @@ | |||||
| <?php | |||||
| require __DIR__ . '/ApiExact.php'; | |||||
| require __DIR__ . '/IsotopeDatabaseHandler.php'; | |||||
| $b = __DIR__ . '/ApiExact.php'; | |||||
| $apiExact = new ApiExact(); | |||||
| echo $apiExact->getAccessToken() . "\n"; | |||||
| @@ -0,0 +1,10 @@ | |||||
| <?php | |||||
| require __DIR__ . '/ApiExact.php'; | |||||
| require __DIR__ . '/IsotopeDatabaseHandler.php'; | |||||
| $apiExact = new ApiExact(); | |||||
| $databaseHandler = new IsotopeDatabaseHandler(); | |||||
| $products = $apiExact->getProducts(); | |||||
| $databaseHandler->processProducts($products); | |||||
| @@ -0,0 +1,11 @@ | |||||
| string(3) "586" | |||||
| string(8) "WR000400" | |||||
| string(2) "41" | |||||
| string(24) "Schaum-Sprüher 1,5Liter" | |||||
| NULL | |||||
| string(3) "587" | |||||
| string(8) "WR000250" | |||||
| string(2) "42" | |||||
| string(11) "Tragebeutel" | |||||
| NULL | |||||
| string(15) "fuck off contao" | |||||
| @@ -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":1657726518} | |||||