From 4c30e3b8d9e3bc152d921701d080be92b72ba536 Mon Sep 17 00:00:00 2001 From: Murray Wood Date: Sat, 2 Mar 2024 16:18:10 +0800 Subject: [PATCH] Add custom DynamicTaxModeProduct --- .../commerce_dynamictaxmode/composer.json | 3 +- .../lexicon/en/default.inc.php | 4 + .../dynamictaxmodeproduct.class.php | 227 ++++++++++++++++++ .../metadata.mysql.php | 8 + .../mysql/dynamictaxmodeproduct.class.php | 16 ++ .../mysql/dynamictaxmodeproduct.map.inc.php | 27 +++ .../commerce_dynamictaxmode.mysql.schema.xml | 2 +- .../commerce_dynamictaxmode/src/Module.php | 10 +- 8 files changed, 293 insertions(+), 4 deletions(-) create mode 100644 core/components/commerce_dynamictaxmode/model/commerce_dynamictaxmode/dynamictaxmodeproduct.class.php create mode 100644 core/components/commerce_dynamictaxmode/model/commerce_dynamictaxmode/metadata.mysql.php create mode 100644 core/components/commerce_dynamictaxmode/model/commerce_dynamictaxmode/mysql/dynamictaxmodeproduct.class.php create mode 100644 core/components/commerce_dynamictaxmode/model/commerce_dynamictaxmode/mysql/dynamictaxmodeproduct.map.inc.php diff --git a/core/components/commerce_dynamictaxmode/composer.json b/core/components/commerce_dynamictaxmode/composer.json index fc3d95d..8b0e055 100644 --- a/core/components/commerce_dynamictaxmode/composer.json +++ b/core/components/commerce_dynamictaxmode/composer.json @@ -1,6 +1,7 @@ { "require": { - "php": ">=7.4.0" + "php": ">=7.4.0", + "ext-json": "*" }, "autoload": { "psr-4": { diff --git a/core/components/commerce_dynamictaxmode/lexicon/en/default.inc.php b/core/components/commerce_dynamictaxmode/lexicon/en/default.inc.php index 34b957b..580bf27 100644 --- a/core/components/commerce_dynamictaxmode/lexicon/en/default.inc.php +++ b/core/components/commerce_dynamictaxmode/lexicon/en/default.inc.php @@ -1,6 +1,10 @@ sessionKey)) { + $this->sessionKey = \modmore\Commerce_DynamicTaxMode\Module::DEFAULT_SESSION_KEY; + $this->module = $this->adapter->getObject(comModule::class, [ + 'class_name' => 'modmore\Commerce_DynamicTaxMode\Module', + ]); + if ($this->module && !empty($this->module->getProperty('session_key'))) { + $this->sessionKey = $this->module->getProperty('session_key'); + } + } + } + + /** + * @param comCurrency $currency + * @return ItemPricingInterface + */ + public function getBusinessPricing(comCurrency $currency): ?ItemPricingInterface + { + $instance = $this->getBusinessPricingInstance($currency); + if (!$instance instanceof ItemPricingInterface) { + $legacyPrice = $this->getPrice(); + $newPrice = new \modmore\Commerce\Pricing\Price($currency, $legacyPrice->getPriceForCurrency($currency)); + $instance = new ProductPricing($currency, $newPrice); + } + + return $instance; + } + + /** + * @param ItemPricingInterface $pricing + * @return bool + */ + public function saveBusinessPricing(ItemPricingInterface $pricing): bool + { + $data = $pricing->serialize(); + $currency = $pricing->getCurrency(); + $currencyCode = $currency->get('alpha_code'); + + $rawPricing = $this->getRawBusinessPricing(); + $rawPricing[$currencyCode] = $data; + $this->setRawBusinessPricing($rawPricing); + return $this->save(); + } + + /** + * @param array $rawValue + * @param comCurrency $currency + * @return ProductPricing|null + */ + protected function loadPricingInstance(array $rawValue, comCurrency $currency): ?ProductPricing + { + $instance = null; + $currencyCode = $currency->get('alpha_code'); + if (array_key_exists($currencyCode, $rawValue)) { + try { + $instance = ProductPricing::unserialize( + $currency, + is_array($rawValue[$currencyCode]) + ? $rawValue[$currencyCode] + : (array)json_decode($rawValue[$currencyCode], true) + ); + } catch (InvalidPriceTypeException $e) { + $this->adapter->log( + 1, + 'Could not create Pricing instance for product ' + . $this->get('id') . ' with currency ' . $currencyCode . ', received ' . get_class($e) + . ' with message: ' . $e->getMessage() . ' // ' . $e->getTraceAsString() + ); + } + } + + return $instance; + } + + /** + * Retrieves either normal pricing instance, or business pricing instance, based on is_inclusive or session value + * @param comCurrency $currency + * @return ProductPricing|null + */ + protected function getPricingInstance(comCurrency $currency): ?ProductPricing + { + // 1. Check for comOrder::is_inclusive + $order = null; + if (isset($_SESSION[comOrder::SESSION_NAME]) && $_SESSION[comOrder::SESSION_NAME] > 0) { + // Check the session for an ID + $orderId = (int)$_SESSION[comOrder::SESSION_NAME]; + $order = $this->adapter->getObject(comOrder::class, [ + 'id' => $orderId, + 'test' => $this->commerce->isTestMode(), + ]); + } elseif (isset($_COOKIE[comOrder::COOKIE_NAME]) && !empty($_COOKIE[comOrder::COOKIE_NAME])) { + // Check the cookies for a more complex secret + $orderSecret = (string)$_COOKIE[comOrder::COOKIE_NAME]; + $order = $this->adapter->getObject(comOrder::class, [ + 'secret' => $orderSecret, + 'test' => $this->commerce->isTestMode(), + ]); + } + if ($order instanceof comOrder) { + $taxMode = $order->get('is_inclusive') ? 'inclusive' : 'exclusive'; + } + + // 2. As a backup, check for dynamic tax mode inclusive/exclusive session value + if (empty($taxMode)) { + $taxMode = $_SESSION[$this->sessionKey] ?? ''; + } + + $raw = $taxMode === 'inclusive' ? $this->getRawBusinessPricing() : $this->getRawPricing(); + + return $this->loadPricingInstance($raw, $currency); + } + + /** + * Retrieve normal pricing instance + * @param comCurrency $currency + * @return ProductPricing|null + */ + protected function getNormalPricingInstance(comCurrency $currency): ?ProductPricing + { + return $this->checkPricingInstance( + $currency, + $this->loadPricingInstance($this->getRawPricing(), $currency), + ); + } + + /** + * Retrieve business pricing instance + * @param comCurrency $currency + * @return ProductPricing|null + */ + protected function getBusinessPricingInstance(comCurrency $currency): ?ProductPricing + { + return $this->checkPricingInstance( + $currency, + $this->loadPricingInstance($this->getRawBusinessPricing(), $currency), + ); + } + + /** + * Make sure we have a pricing instance + * @param comCurrency $currency + * @param ProductPricing|null $instance + * @return ProductPricing + */ + protected function checkPricingInstance(comCurrency $currency, ProductPricing $instance = null): ProductPricing + { + if (!$instance instanceof ItemPricingInterface) { + $legacyPrice = $this->getPrice(); + $newPrice = new \modmore\Commerce\Pricing\Price($currency, $legacyPrice->getPriceForCurrency($currency)); + $instance = new ProductPricing($currency, $newPrice); + } + + return $instance; + } + + /** + * @return array + */ + public function getRawBusinessPricing(): array + { + return $this->getProperty('pricing_business') ?? []; + } + + /** + * Stores the raw per-currency business pricing information. + * @param array $pricing + */ + protected function setRawBusinessPricing(array $pricing) + { + $this->setProperty('pricing_business', json_encode($pricing)); + } + + /** + * @return array + */ + public function getModelFields(): array + { + $fields = parent::getModelFields(); + + $enabledCurrencies = $this->commerce->getEnabledCurrencies(); + /** @var ItemPricingInterface $pricing */ + $normalPricing = []; + $businessPricing = []; + foreach ($enabledCurrencies as $currencyCode => $currency) { + $normalPricing[$currencyCode] = $this->getNormalPricingInstance($currency); + $businessPricing[$currencyCode] = $this->getBusinessPricingInstance($currency); + } + foreach ($fields as $k => $field) { + if ($field->getName() === 'pricing') { + // The reason we're overriding the normal pricing field as well is because getPricingInstance() + // for this product is dynamic. Here, we ALWAYS want the normal price. + $normalPricingField = new PricingField($this->commerce, [ + 'name' => 'pricing', + 'label' => $this->adapter->lexicon('commerce.price'), + 'pricing' => $normalPricing, + ]); + $businessPricingField = new PricingField($this->commerce, [ + 'name' => 'properties[pricing_business]', + 'label' => $this->adapter->lexicon('commerce_dynamictaxmode.business_price'), + 'pricing' => $businessPricing, + ]); + + // Overwrite the existing pricing field, replacing it with the above two. + array_splice($fields, $k, 1, [$normalPricingField, $businessPricingField]); + break; + } + } + + return $fields; + } +} diff --git a/core/components/commerce_dynamictaxmode/model/commerce_dynamictaxmode/metadata.mysql.php b/core/components/commerce_dynamictaxmode/model/commerce_dynamictaxmode/metadata.mysql.php new file mode 100644 index 0000000..a500340 --- /dev/null +++ b/core/components/commerce_dynamictaxmode/model/commerce_dynamictaxmode/metadata.mysql.php @@ -0,0 +1,8 @@ + + array ( + 0 => 'DynamicTaxModeProduct', + ), +); \ No newline at end of file diff --git a/core/components/commerce_dynamictaxmode/model/commerce_dynamictaxmode/mysql/dynamictaxmodeproduct.class.php b/core/components/commerce_dynamictaxmode/model/commerce_dynamictaxmode/mysql/dynamictaxmodeproduct.class.php new file mode 100644 index 0000000..2a60057 --- /dev/null +++ b/core/components/commerce_dynamictaxmode/model/commerce_dynamictaxmode/mysql/dynamictaxmodeproduct.class.php @@ -0,0 +1,16 @@ + + * + * This file is meant to be used with Commerce by modmore. A valid Commerce license is required. + * + * @package commerce_dynamictaxmode + * @license See core/components/commerce_dynamictaxmode/docs/license.txt + */ +class DynamicTaxModeProduct_mysql extends DynamicTaxModeProduct +{ + +} diff --git a/core/components/commerce_dynamictaxmode/model/commerce_dynamictaxmode/mysql/dynamictaxmodeproduct.map.inc.php b/core/components/commerce_dynamictaxmode/model/commerce_dynamictaxmode/mysql/dynamictaxmodeproduct.map.inc.php new file mode 100644 index 0000000..bb56fbd --- /dev/null +++ b/core/components/commerce_dynamictaxmode/model/commerce_dynamictaxmode/mysql/dynamictaxmodeproduct.map.inc.php @@ -0,0 +1,27 @@ + + * + * This file is meant to be used with Commerce by modmore. A valid Commerce license is required. + * + * @package commerce_dynamictaxmode + * @license See core/components/commerce_dynamictaxmode/docs/license.txt + */ + +$xpdo_meta_map['DynamicTaxModeProduct']= array ( + 'package' => 'commerce_dynamictaxmode', + 'version' => '1.1', + 'extends' => 'comProduct', + 'tableMeta' => + array ( + 'engine' => 'InnoDB', + ), + 'fields' => + array ( + ), + 'fieldMeta' => + array ( + ), +); diff --git a/core/components/commerce_dynamictaxmode/model/schema/commerce_dynamictaxmode.mysql.schema.xml b/core/components/commerce_dynamictaxmode/model/schema/commerce_dynamictaxmode.mysql.schema.xml index fa92fc8..05dbcf0 100644 --- a/core/components/commerce_dynamictaxmode/model/schema/commerce_dynamictaxmode.mysql.schema.xml +++ b/core/components/commerce_dynamictaxmode/model/schema/commerce_dynamictaxmode.mysql.schema.xml @@ -1,6 +1,6 @@ + - \ No newline at end of file diff --git a/core/components/commerce_dynamictaxmode/src/Module.php b/core/components/commerce_dynamictaxmode/src/Module.php index 95f2e5e..971e684 100644 --- a/core/components/commerce_dynamictaxmode/src/Module.php +++ b/core/components/commerce_dynamictaxmode/src/Module.php @@ -14,6 +14,8 @@ class Module extends BaseModule { + public const DEFAULT_SESSION_KEY = 'commerce_dynamictaxmode'; + public function getName(): string { $this->adapter->loadLexicon('commerce_dynamictaxmode:default'); @@ -35,15 +37,19 @@ public function initialize(EventDispatcher $dispatcher): void // Load our lexicon $this->adapter->loadLexicon('commerce_dynamictaxmode:default'); + $root = dirname(__DIR__); + $path = $root . '/model/'; + $this->adapter->loadPackage('commerce_dynamictaxmode', $path); + $dispatcher->addListener(\Commerce::EVENT_ORDER_BEFORE_LOAD, [$this, 'setDynamicTaxMode']); // Add composer libraries to the about section $dispatcher->addListener(\Commerce::EVENT_DASHBOARD_LOAD_ABOUT, [$this, 'addLibrariesToAbout']); } - public function setDynamicTaxMode(Order $event) + public function setDynamicTaxMode(Order $event): void { - $sessionKey = 'commerce_dynamictaxmode'; + $sessionKey = self::DEFAULT_SESSION_KEY; $module = $this->adapter->getObject(\comModule::class, [ 'class_name' => 'modmore\Commerce_DynamicTaxMode\Module', ]);