vendor/isotope/isotope-core/system/modules/isotope_rules/library/Isotope/Rules.php line 243

Open in your IDE?
  1. <?php
  2. /*
  3.  * Isotope eCommerce for Contao Open Source CMS
  4.  *
  5.  * Copyright (C) 2009 - 2019 terminal42 gmbh & Isotope eCommerce Workgroup
  6.  *
  7.  * @link       https://isotopeecommerce.org
  8.  * @license    https://opensource.org/licenses/lgpl-3.0.html
  9.  */
  10. namespace Isotope;
  11. use Contao\Controller;
  12. use Contao\Database;
  13. use Contao\Environment;
  14. use Contao\Input;
  15. use Contao\ModuleModel;
  16. use Contao\StringUtil;
  17. use Isotope\Interfaces\IsotopePrice;
  18. use Isotope\Interfaces\IsotopeProductCollection;
  19. use Isotope\Model\ProductCollection\Cart;
  20. use Isotope\Model\ProductCollection\Order;
  21. use Isotope\Model\ProductCollectionSurcharge\Rule as RuleSurcharge;
  22. use Isotope\Model\Rule;
  23. class Rules extends Controller
  24. {
  25.     /**
  26.      * Current object instance (Singleton)
  27.      * @var object
  28.      */
  29.     protected static $objInstance;
  30.     /**
  31.      * Prevent cloning of the object (Singleton)
  32.      */
  33.     private function __clone() {}
  34.     /**
  35.      * Prevent direct instantiation (Singleton)
  36.      */
  37.     protected function __construct()
  38.     {
  39.         parent::__construct();
  40.         // User object must be loaded from cart, e.g. for postsale handling
  41.         if (Isotope::getCart()->member 0) {
  42.             $this->User Database::getInstance()->prepare('SELECT * FROM tl_member WHERE id=?')->execute(Isotope::getCart()->member);
  43.         }
  44.     }
  45.     /**
  46.      * Instantiate the singleton if necessary and return it
  47.      * @return object
  48.      */
  49.     public static function getInstance()
  50.     {
  51.         if (!\is_object(static::$objInstance)) {
  52.             static::$objInstance = new Rules();
  53.         }
  54.         return static::$objInstance;
  55.     }
  56.     /**
  57.      * Calculate the price for a product, applying rules and coupons
  58.      *
  59.      * @param    float
  60.      * @param    object
  61.      * @param    string
  62.      * @param    int
  63.      * @return float
  64.      */
  65.     public function calculatePrice($fltPrice$objSource$strField$intTaxClass)
  66.     {
  67.         if ($objSource instanceof IsotopePrice && ('price' === $strField || 'low_price' === $strField || 'net_price' === $strField || 'gross_price' === $strField)) {
  68.         // @todo try not to use getRelated() because it loads variants
  69.             $objRules Rule::findByProduct($objSource->getRelated('pid'), $strField$fltPrice);
  70.         if (null !== $objRules) {
  71.                 while ($objRules->next()) {
  72.                     // Check cart quantity
  73.                     if ($objRules->minItemQuantity || $objRules->maxItemQuantity 0) {
  74.                         if ('cart_products' === $objRules->quantityMode) {
  75.                             $intTotal Isotope::getCart()->countItems();
  76.                         } elseif ('cart_items' === $objRules->quantityMode) {
  77.                             $intTotal Isotope::getCart()->sumItemsQuantity();
  78.                         } else {
  79.                             $objItem Isotope::getCart()->getItemForProduct($objSource->getRelated('pid'));
  80.                             $intTotal = (null === $objItem) ? $objItem->quantity;
  81.                         }
  82.                         if (($objRules->minItemQuantity && $objRules->minItemQuantity $intTotal) || ($objRules->maxItemQuantity && $objRules->maxItemQuantity $intTotal)) {
  83.                     continue;
  84.                         }
  85.                     }
  86.                     // We're unable to apply variant price rules to low_price (see #3189)
  87.                     if ('low_price' === $strField && 'variants' === $objRules->productRestrictions) {
  88.                         continue;
  89.                     }
  90.                     if ($objRules->current()->isPercentage()) {
  91.                         $fltDiscount 100 $objRules->current()->getPercentage();
  92.                         $fltDiscount round($fltPrice - ($fltPrice 100 $fltDiscount), 10);
  93.                         $precision Isotope::getConfig()->priceRoundPrecision;
  94.                         $factor    pow(102);
  95.                         $up        $fltDiscount 'ceil' 'floor';
  96.                         $down      $fltDiscount 'floor' 'ceil';
  97.                         switch ($objRules->rounding) {
  98.                             case Rule::ROUND_NORMAL:
  99.                                 $fltDiscount round($fltDiscount$precision);
  100.                                 break;
  101.                             case Rule::ROUND_UP:
  102.                                 $fltDiscount $up($fltDiscount $factor) / $factor;
  103.                                 break;
  104.                             case Rule::ROUND_DOWN:
  105.                             default:
  106.                                 $fltDiscount $down($fltDiscount $factor) / $factor;
  107.                                 break;
  108.                         }
  109.                         $fltPrice $fltPrice $fltDiscount;
  110.                     } else {
  111.                         $fltPrice $fltPrice $objRules->discount;
  112.                     }
  113.                 }
  114.             }
  115.         }
  116.         return $fltPrice;
  117.     }
  118.     /**
  119.      * Add cart rules to surcharges
  120.      */
  121.     public function findSurcharges(IsotopeProductCollection $objCollection)
  122.     {
  123.         $objCart $objCollection;
  124.         // The checkout review pages shows an order, but we need the cart
  125.         // Only the cart contains coupons etc.
  126.         if ($objCollection instanceof Order) {
  127.             $objCart $objCollection->getRelated('source_collection_id');
  128.         }
  129.         // Rules should only be applied to Cart, not any other product collection
  130.         if (!($objCart instanceof Cart)) {
  131.             return array();
  132.         }
  133.         $arrSurcharges = array();
  134.         $objRules Rule::findForCart();
  135.         if (null !== $objRules) {
  136.             foreach ($objRules as $objRule) {
  137.                 $this->addSurchargesForRule($objRule$objCollection$arrSurcharges);
  138.             }
  139.         }
  140.         $arrCoupons StringUtil::deserialize($objCart->coupons);
  141.         if (!empty($arrCoupons) && \is_array($arrCoupons)) {
  142.             $blnHasCode false;
  143.             $arrDropped = array();
  144.             foreach ($arrCoupons as $code) {
  145.                 $objRule Rule::findOneByCouponCode($code$objCollection->getItems());
  146.                 if (null === $objRule || ($blnHasCode && $objRule->singleCode)) {
  147.                     $arrDropped[] = $code;
  148.                 } else {
  149.                     $blnHasCode $this->addSurchargesForRule($objRule$objCollection$arrSurcharges) ?: $blnHasCode;
  150.                 }
  151.             }
  152.             if (!empty($arrDropped)) {
  153.                 // @todo show dropped coupons
  154.                 $arrCoupons array_diff($arrCoupons$arrDropped);
  155.                 Database::getInstance()->query("UPDATE tl_iso_product_collection SET coupons='" serialize($arrCoupons) . "' WHERE id=" . (int) Isotope::getCart()->id);
  156.             }
  157.         }
  158.         return $arrSurcharges;
  159.     }
  160.     /**
  161.      * Returns a rule form if needed
  162.      *
  163.      * @param ModuleModel $objModule
  164.      *
  165.      * @return string
  166.      *
  167.      * @deprecated Deprecated since Isotope 2.5, use the Coupons front end module instead.
  168.      */
  169.     public function getCouponForm($objModule)
  170.     {
  171.         $arrCoupons StringUtil::deserialize(Isotope::getCart()->coupons);
  172.         if (!\is_array($arrCoupons)) {
  173.             $arrCoupons = array();
  174.         }
  175.         $strCoupon Input::get('coupon_' $objModule->id);
  176.         if ($strCoupon == '') {
  177.             $strCoupon Input::get('coupon');
  178.         }
  179.         if ($strCoupon != '') {
  180.             $objRule Rule::findOneByCouponCode($strCouponIsotope::getCart()->getItems());
  181.             if (null === $objRule) {
  182.                 $_SESSION['COUPON_FAILED'][$objModule->id] = sprintf($GLOBALS['TL_LANG']['MSC']['couponInvalid'], $strCoupon);
  183.             } elseif (\in_array(mb_strtolower($strCoupon), array_map('mb_strtolower'$arrCoupons), true)) {
  184.                 $_SESSION['COUPON_FAILED'][$objModule->id] = sprintf($GLOBALS['TL_LANG']['MSC']['couponDuplicate'], $strCoupon);
  185.             } elseif ($objRule->singleCode && !empty($arrCoupons)) {
  186.                 $_SESSION['COUPON_FAILED'][$objModule->id] = sprintf($GLOBALS['TL_LANG']['MSC']['couponSingle'], $strCoupon);
  187.             } else {
  188.                 $arrCoupons[] = $objRule->code;
  189.                 Isotope::getCart()->coupons serialize($arrCoupons);
  190.                 Isotope::getCart()->save();
  191.                 $_SESSION['COUPON_SUCCESS'][$objModule->id] = sprintf($GLOBALS['TL_LANG']['MSC']['couponApplied'], $objRule->code);
  192.             }
  193.             Controller::redirect(preg_replace('@[?&]coupon(_[0-9]+)?=[^&]*@'''Environment::get('request')));
  194.         }
  195.         $objRules Rule::findForCartWithCoupons();
  196.         if (null === $objRules || ModuleModel::countBy('type''iso_coupons') > 0) {
  197.             return '';
  198.         }
  199.         //build template
  200.         $objTemplate = new Template('iso_coupons');
  201.         $objTemplate->id $objModule->id;
  202.         $objTemplate->action Environment::get('request');
  203.         $objTemplate->headline $GLOBALS['TL_LANG']['MSC']['couponHeadline'];
  204.         $objTemplate->inputLabel $GLOBALS['TL_LANG']['MSC']['couponLabel'];
  205.         $objTemplate->sLabel $GLOBALS['TL_LANG']['MSC']['couponApply'];
  206.         $objTemplate->usedCoupons $arrCoupons;
  207.         $objTemplate->rules $objRules;
  208.         if ($_SESSION['COUPON_FAILED'][$objModule->id] != '') {
  209.             $objTemplate->message $_SESSION['COUPON_FAILED'][$objModule->id];
  210.             $objTemplate->mclass 'failed';
  211.             unset($_SESSION['COUPON_FAILED']);
  212.         } elseif ($_SESSION['COUPON_SUCCESS'][$objModule->id] != '') {
  213.             $objTemplate->message $_SESSION['COUPON_SUCCESS'][$objModule->id];
  214.             $objTemplate->mclass 'success';
  215.             unset($_SESSION['COUPON_SUCCESS']);
  216.         }
  217.         return $objTemplate->parse();
  218.     }
  219.     /**
  220.      * Callback for checkout Hook. Transfer active rules to usage table.
  221.      */
  222.     public function writeRuleUsages($objOrder)
  223.     {
  224.         $objCart Cart::findByPk($objOrder->source_collection_id);
  225.         $objRules Rule::findActiveWithoutCoupons();
  226.         $arrRules = (null === $objRules) ? array() : $objRules->fetchEach('id');
  227.         $arrCoupons StringUtil::deserialize($objCart->coupons);
  228.         if (\is_array($arrCoupons) && !empty($arrCoupons)) {
  229.             $blnError false;
  230.             foreach ($arrCoupons as $k => $code) {
  231.                 $objRule Rule::findOneByCouponCode($code$objCart->getItems());
  232.                 if (null === $objRule) {
  233.                     Message::addError(sprintf($GLOBALS['TL_LANG']['ERR']['couponCodeDropped'], $code));
  234.                     unset($arrCoupons[$k]);
  235.                     $blnError true;
  236.                 } else {
  237.                     $arrRules[] = $objRule->id;
  238.                 }
  239.             }
  240.             if ($blnError) {
  241.                 $objCart->coupons $arrCoupons;
  242.                 return false;
  243.             }
  244.         }
  245.         if (!empty($arrRules)) {
  246.             $time time();
  247.             Database::getInstance()->query("INSERT INTO tl_iso_rule_usage (pid,tstamp,order_id,config_id,member_id) VALUES (" implode(", $time{$objOrder->id}, " . (int) Isotope::getConfig()->id ", {$objOrder->member}), ("$arrRules) . ", $time{$objOrder->id}, " . (int) Isotope::getConfig()->id ", {$objOrder->member})");
  248.         }
  249.         return true;
  250.     }
  251.     /**
  252.      * Callback for checkout step "review". Remove rule usages if an order failed.
  253.      * @todo this will no longer work
  254.      */
  255.     public function cleanRuleUsages(&$objModule)
  256.     {
  257.         Database::getInstance()->query("DELETE FROM tl_iso_rule_usage WHERE pid=(SELECT id FROM tl_iso_product_collection WHERE type='order' AND source_collection_id=" . (int) Isotope::getCart()->id ")");
  258.         return '';
  259.     }
  260.     /**
  261.      * Transfer coupons from one cart to another. This happens if a guest cart is moved to user cart.
  262.      *
  263.      * @param IsotopeProductCollection $oldCollection
  264.      * @param IsotopeProductCollection $newCollection
  265.      */
  266.     public function transferCoupons(IsotopeProductCollection $oldCollectionIsotopeProductCollection $newCollection)
  267.     {
  268.         if ($oldCollection instanceof Cart && $newCollection instanceof Cart) {
  269.             $oldCoupons StringUtil::deserialize($oldCollection->couponstrue);
  270.             $newCoupons StringUtil::deserialize($newCollection->couponstrue);
  271.             $newCollection->coupons array_unique(array_merge($oldCoupons$newCoupons));
  272.             $newCollection->save();
  273.         }
  274.     }
  275.     /**
  276.      * Delete rule usages after an order has been deleted
  277.      *
  278.      * @param IsotopeProductCollection $objCollection
  279.      * @param int                      $intId
  280.      */
  281.     public function deleteRuleUsages($objCollection$intId)
  282.     {
  283.         Database::getInstance()->prepare("DELETE FROM tl_iso_rule_usage WHERE order_id=?")->execute($intId);
  284.     }
  285.     private function addSurchargesForRule(Rule $objRuleIsotopeProductCollection $objCollection, array &$arrSurcharges)
  286.     {
  287.         switch ($objRule->type) {
  288.             case 'cart':
  289.                 $objSurcharge RuleSurcharge::createForRuleInCollection($objRule$objCollection);
  290.                 if (null === $objSurcharge) {
  291.                     return false;
  292.                 }
  293.                 $arrSurcharges[] = $objSurcharge;
  294.                 return true;
  295.             case 'cart_group':
  296.                 $blnResult false;
  297.                 $ids StringUtil::deserialize($objRule->groupRules);
  298.                 if (!empty($ids) && \is_array($ids)) {
  299.                     foreach ($ids as $id) {
  300.                         $objRules Rule::findForCart($id);
  301.                         if (null === $objRules) {
  302.                             continue;
  303.                         }
  304.                         $blnResult $this->addSurchargesForRule($objRules->current(), $objCollection$arrSurcharges) ?: $blnResult;
  305.                         if ($blnResult && $objRule->groupCondition === Rule::GROUP_FIRST) {
  306.                             break;
  307.                         }
  308.                     }
  309.                 }
  310.                 return $blnResult;
  311.             default:
  312.                 throw new \RuntimeException('Unsupported rule type "'.$objRule->type.'" for cart group');
  313.         }
  314.     }
  315. }