modules/CDev/Coupons/src/Model/Coupon.php line 55

Open in your IDE?
  1. <?php
  2. /**
  3.  * Copyright (c) 2011-present Qualiteam software Ltd. All rights reserved.
  4.  * See https://www.x-cart.com/license-agreement.html for license details.
  5.  */
  6. namespace CDev\Coupons\Model;
  7. use ApiPlatform\Core\Annotation as ApiPlatform;
  8. use CDev\Coupons\API\Endpoint\Coupon\DTO\CouponInput;
  9. use CDev\Coupons\API\Endpoint\Coupon\DTO\CouponOutput;
  10. use Doctrine\ORM\Mapping as ORM;
  11. /**
  12.  * Coupon
  13.  *
  14.  * @ORM\Entity
  15.  * @ORM\Table  (name="coupons",
  16.  *      indexes={
  17.  *          @ORM\Index (name="ce", columns={"code", "enabled"})
  18.  *      }
  19.  * )
  20.  *
  21.  * @ApiPlatform\ApiResource(
  22.  *     input=CouponInput::class,
  23.  *     output=CouponOutput::class,
  24.  *     itemOperations={
  25.  *          "get"={
  26.  *              "method"="GET",
  27.  *              "path"="/coupons/{id}.{_format}",
  28.  *              "identifiers"={"id"},
  29.  *              "requirements"={"id"="\d+"}
  30.  *          },
  31.  *          "put"={
  32.  *              "method"="PUT",
  33.  *              "path"="/coupons/{id}.{_format}",
  34.  *              "identifiers"={"id"},
  35.  *              "requirements"={"id"="\d+"}
  36.  *          },
  37.  *          "delete"={
  38.  *              "method"="DELETE",
  39.  *              "path"="/coupons/{id}.{_format}",
  40.  *              "identifiers"={"id"},
  41.  *              "requirements"={"id"="\d+"}
  42.  *          }
  43.  *     },
  44.  *     collectionOperations={
  45.  *          "get"={
  46.  *              "method"="GET",
  47.  *              "identifiers"={},
  48.  *              "path"="/coupons.{_format}"
  49.  *          },
  50.  *          "post"={
  51.  *              "method"="POST",
  52.  *              "identifiers"={},
  53.  *              "path"="/coupons.{_format}"
  54.  *          }
  55.  *     }
  56.  * )
  57.  */
  58. class Coupon extends \XLite\Model\AEntity
  59. {
  60.     /**
  61.      * Coupon types
  62.      */
  63.     public const TYPE_PERCENT  '%';
  64.     public const TYPE_ABSOLUTE '$';
  65.     /**
  66.      * Coupon validation error codes
  67.      */
  68.     public const ERROR_DISABLED      'disabled';
  69.     public const ERROR_EXPIRED       'expired';
  70.     public const ERROR_USES          'uses';
  71.     public const ERROR_TOTAL         'total';
  72.     public const ERROR_PRODUCT_CLASS 'product_class';
  73.     public const ERROR_MEMBERSHIP    'membership';
  74.     public const ERROR_SINGLE_USE    'singleUse';
  75.     public const ERROR_SINGLE_USE2   'singleUse2';
  76.     public const ERROR_CATEGORY      'category';
  77.     public const TYPE_FREESHIP                 'S';
  78.     public const APPLY_FREESHIP_TO_CHEAPEST    'C';
  79.     public const APPLY_FREESHIP_TO_FREE_METHOD 'F';
  80.     /**
  81.      * Product unique ID
  82.      *
  83.      * @var   integer
  84.      *
  85.      * @ORM\Id
  86.      * @ORM\GeneratedValue (strategy="AUTO")
  87.      * @ORM\Column         (type="integer", options={ "unsigned": true })
  88.      */
  89.     protected $id;
  90.     /**
  91.      * Code
  92.      *
  93.      * @var   string
  94.      *
  95.      * @ORM\Column (type="string", options={ "fixed": true }, length=16)
  96.      */
  97.     protected $code;
  98.     /**
  99.      * Enabled status
  100.      *
  101.      * @var   boolean
  102.      *
  103.      * @ORM\Column (type="boolean")
  104.      */
  105.     protected $enabled true;
  106.     /**
  107.      * Value
  108.      *
  109.      * @var   float
  110.      *
  111.      * @ORM\Column (type="decimal", precision=14, scale=4)
  112.      */
  113.     protected $value 0.0000;
  114.     /**
  115.      * Type
  116.      *
  117.      * @var   string
  118.      *
  119.      * @ORM\Column (type="string", options={ "fixed": true }, length=1)
  120.      */
  121.     protected $type self::TYPE_PERCENT;
  122.     /**
  123.      * Comment
  124.      *
  125.      * @var   string
  126.      *
  127.      * @ORM\Column (type="string", length=64)
  128.      */
  129.     protected $comment '';
  130.     /**
  131.      * Uses count
  132.      *
  133.      * @var   integer
  134.      *
  135.      * @ORM\Column (type="integer", options={ "unsigned": true })
  136.      */
  137.     protected $uses 0;
  138.     /**
  139.      * Date range (begin)
  140.      *
  141.      * @var   integer
  142.      *
  143.      * @ORM\Column (type="integer", options={ "unsigned": true })
  144.      */
  145.     protected $dateRangeBegin 0;
  146.     /**
  147.      * Date range (end)
  148.      *
  149.      * @var   integer
  150.      *
  151.      * @ORM\Column (type="integer", options={ "unsigned": true })
  152.      */
  153.     protected $dateRangeEnd 0;
  154.     /**
  155.      * Total range (begin)
  156.      *
  157.      * @var   float
  158.      *
  159.      * @ORM\Column (type="decimal", precision=14, scale=4)
  160.      */
  161.     protected $totalRangeBegin 0;
  162.     /**
  163.      * Total range (end)
  164.      *
  165.      * @var   float
  166.      *
  167.      * @ORM\Column (type="decimal", precision=14, scale=4)
  168.      */
  169.     protected $totalRangeEnd 0;
  170.     /**
  171.      * Uses limit
  172.      *
  173.      * @var   integer
  174.      *
  175.      * @ORM\Column (type="integer", options={ "unsigned": true })
  176.      */
  177.     protected $usesLimit 0;
  178.     /**
  179.      * Uses limit per user
  180.      *
  181.      * @var   integer
  182.      *
  183.      * @ORM\Column (type="integer", options={ "unsigned": true })
  184.      */
  185.     protected $usesLimitPerUser 0;
  186.     /**
  187.      * Flag: Can a coupon be used together with other coupons (false) or no (true)
  188.      *
  189.      * @var boolean
  190.      *
  191.      * @ORM\Column (type="boolean")
  192.      */
  193.     protected $singleUse false;
  194.     /**
  195.      * Flag: Coupon is used for specific products or not
  196.      *
  197.      * @var boolean
  198.      *
  199.      * @ORM\Column (type="boolean")
  200.      */
  201.     protected $specificProducts false;
  202.     /**
  203.      * Product classes
  204.      *
  205.      * @var   \Doctrine\Common\Collections\ArrayCollection
  206.      *
  207.      * @ORM\ManyToMany (targetEntity="XLite\Model\ProductClass", inversedBy="coupons")
  208.      * @ORM\JoinTable (name="product_class_coupons",
  209.      *      joinColumns={@ORM\JoinColumn (name="coupon_id", referencedColumnName="id", onDelete="CASCADE")},
  210.      *      inverseJoinColumns={@ORM\JoinColumn (name="class_id", referencedColumnName="id", onDelete="CASCADE")}
  211.      * )
  212.      */
  213.     protected $productClasses;
  214.     /**
  215.      * Memberships
  216.      *
  217.      * @var   \Doctrine\Common\Collections\ArrayCollection
  218.      *
  219.      * @ORM\ManyToMany (targetEntity="XLite\Model\Membership", inversedBy="coupons")
  220.      * @ORM\JoinTable (name="membership_coupons",
  221.      *      joinColumns={@ORM\JoinColumn (name="coupon_id", referencedColumnName="id", onDelete="CASCADE")},
  222.      *      inverseJoinColumns={@ORM\JoinColumn (name="membership_id", referencedColumnName="membership_id", onDelete="CASCADE")}
  223.      * )
  224.      */
  225.     protected $memberships;
  226.     /**
  227.      * Zones
  228.      *
  229.      * @var   \Doctrine\Common\Collections\ArrayCollection
  230.      *
  231.      * @ORM\ManyToMany (targetEntity="XLite\Model\Zone", inversedBy="coupons")
  232.      * @ORM\JoinTable (name="zone_coupons",
  233.      *      joinColumns={@ORM\JoinColumn (name="coupon_id", referencedColumnName="id", onDelete="CASCADE")},
  234.      *      inverseJoinColumns={@ORM\JoinColumn (name="zone_id", referencedColumnName="zone_id", onDelete="CASCADE")}
  235.      * )
  236.      */
  237.     protected $zones;
  238.     /**
  239.      * Coupon products
  240.      *
  241.      * @var   \Doctrine\Common\Collections\ArrayCollection
  242.      *
  243.      * @ORM\OneToMany (targetEntity="CDev\Coupons\Model\CouponProduct", mappedBy="coupon", cascade={"persist"})
  244.      */
  245.     protected $couponProducts;
  246.     /**
  247.      * Used coupons
  248.      *
  249.      * @var   \Doctrine\Common\Collections\Collection
  250.      *
  251.      * @ORM\OneToMany (targetEntity="CDev\Coupons\Model\UsedCoupon", mappedBy="coupon")
  252.      */
  253.     protected $usedCoupons;
  254.     /**
  255.      * Categories
  256.      *
  257.      * @var   \Doctrine\Common\Collections\ArrayCollection
  258.      *
  259.      * @ORM\ManyToMany (targetEntity="XLite\Model\Category", inversedBy="coupons")
  260.      * @ORM\JoinTable (name="coupon_categories",
  261.      *      joinColumns={@ORM\JoinColumn (name="coupon_id", referencedColumnName="id", onDelete="CASCADE")},
  262.      *      inverseJoinColumns={@ORM\JoinColumn (name="category_id", referencedColumnName="category_id", onDelete="CASCADE")}
  263.      * )
  264.      */
  265.     protected $categories;
  266.     /**
  267.      * @ORM\Column (type="string", options={ "fixed": true }, length=1)
  268.      */
  269.     protected string $applyFreeShippingTo self::APPLY_FREESHIP_TO_CHEAPEST;
  270.     protected static $runtimeCacheForUsedCouponsCount = [];
  271.     /**
  272.      * Constructor
  273.      *
  274.      * @param array $data Entity properties OPTIONAL
  275.      */
  276.     public function __construct(array $data = [])
  277.     {
  278.         $this->productClasses = new \Doctrine\Common\Collections\ArrayCollection();
  279.         $this->memberships    = new \Doctrine\Common\Collections\ArrayCollection();
  280.         $this->zones    = new \Doctrine\Common\Collections\ArrayCollection();
  281.         $this->couponProducts    = new \Doctrine\Common\Collections\ArrayCollection();
  282.         $this->usedCoupons    = new \Doctrine\Common\Collections\ArrayCollection();
  283.         $this->categories     = new \Doctrine\Common\Collections\ArrayCollection();
  284.         parent::__construct($data);
  285.     }
  286.     /**
  287.      * Check - discount is absolute or not
  288.      *
  289.      * @return boolean
  290.      */
  291.     public function isAbsolute()
  292.     {
  293.         return $this->getType() === static::TYPE_ABSOLUTE;
  294.     }
  295.     /**
  296.      * Check - coupon is started
  297.      *
  298.      * @return boolean
  299.      */
  300.     public function isStarted()
  301.     {
  302.         return $this->getDateRangeBegin() === || $this->getDateRangeBegin() < \XLite\Core\Converter::time();
  303.     }
  304.     /**
  305.      * Check - coupon is expired or not
  306.      *
  307.      * @return boolean
  308.      */
  309.     public function isExpired()
  310.     {
  311.         return $this->getDateRangeEnd() && $this->getDateRangeEnd() < \XLite\Core\Converter::time();
  312.     }
  313.     /**
  314.      * Check coupon activity
  315.      *
  316.      * @param \XLite\Model\Order $order Order OPTIONAL
  317.      *
  318.      * @return boolean
  319.      */
  320.     public function isActive(\XLite\Model\Order $order null)
  321.     {
  322.         try {
  323.             $result $this->checkCompatibility($order);
  324.         } catch (\CDev\Coupons\Core\CompatibilityException $exception) {
  325.             $result false;
  326.         }
  327.         return $result;
  328.     }
  329.     /**
  330.      * Get public code
  331.      *
  332.      * @return string
  333.      */
  334.     public function getPublicCode()
  335.     {
  336.         $code $this->getCode();
  337.         if ($this->isFreeShipping()) {
  338.             $code sprintf('%s (%s)'$code, static::t('[coupons] Free shipping'));
  339.         }
  340.         return $code;
  341.     }
  342.     /**
  343.      * Get coupon public name
  344.      *
  345.      * @return string
  346.      */
  347.     public function getPublicName()
  348.     {
  349.         $suffix '';
  350.         if ($this->getType() === \CDev\Coupons\Model\Coupon::TYPE_PERCENT) {
  351.             $suffix sprintf('(%s%%)'$this->getValue());
  352.         }
  353.         return $this->getPublicCode() . ' ' $suffix;
  354.     }
  355.     // {{{ Amount
  356.     /**
  357.      * Get amount
  358.      *
  359.      * @param \XLite\Model\Order $order Order
  360.      *
  361.      * @return float
  362.      */
  363.     public function getAmount(\XLite\Model\Order $order)
  364.     {
  365.         if ($this->isFreeShipping()) {
  366.             return 0;
  367.         }
  368.         $total $this->getOrderTotal($order);
  369.         return $this->isAbsolute()
  370.             ? min($total$this->getValue())
  371.             : ($total $this->getValue() / 100);
  372.     }
  373.     /**
  374.      * Get order total
  375.      *
  376.      * @param \XLite\Model\Order $order Order
  377.      *
  378.      * @return float
  379.      */
  380.     protected function getOrderTotal(\XLite\Model\Order $order)
  381.     {
  382.         return array_reduce($this->getValidOrderItems($order), static function ($carry$item) {
  383.             return $carry $item->getSubtotal();
  384.         }, 0);
  385.     }
  386.     /**
  387.      * Get order items which are valid for the coupon
  388.      *
  389.      * @param \XLite\Model\Order $order Order
  390.      *
  391.      * @return array
  392.      */
  393.     protected function getValidOrderItems($order)
  394.     {
  395.         return $order->getValidItemsByCoupon($this);
  396.     }
  397.     /**
  398.      * Is coupon valid for product
  399.      *
  400.      * @param \XLite\Model\Product $product Product
  401.      *
  402.      * @return boolean
  403.      */
  404.     public function isValidForProduct(\XLite\Model\Product $product)
  405.     {
  406.         $result true;
  407.         if (count($this->getProductClasses())) {
  408.             // Check product class
  409.             $result $product->getProductClass()
  410.                 && $this->getProductClasses()->contains($product->getProductClass());
  411.         }
  412.         if ($result && count($this->getCategories())) {
  413.             // Check categories
  414.             $result false;
  415.             foreach ($product->getCategories() as $category) {
  416.                 if ($this->getCategories()->contains($category)) {
  417.                     $result true;
  418.                     break;
  419.                 }
  420.             }
  421.         }
  422.         if ($result && $this->getSpecificProducts()) {
  423.             // Check product
  424.             $result in_array($product->getProductId(), $this->getApplicableProductIds());
  425.         }
  426.         return $result;
  427.     }
  428.     // }}}
  429.     /**
  430.      * Check coupon compatibility
  431.      *
  432.      * @param \XLite\Model\Order $order Order
  433.      *
  434.      * @throws \CDev\Coupons\Core\CompatibilityException
  435.      *
  436.      * @return boolean
  437.      */
  438.     public function checkCompatibility(\XLite\Model\Order $order null)
  439.     {
  440.         if (!$this->getEnabled()) {
  441.             $this->throwCompatibilityException(
  442.                 '',
  443.                 'Sorry, the coupon you entered is invalid. Make sure the coupon code is spelled correctly'
  444.             );
  445.         }
  446.         $this->checkDate();
  447.         $this->checkUsage();
  448.         if ($order) {
  449.             if ($order->getProfile()) {
  450.                 $this->checkPerUserUsage($order->getProfile(), $order->containsCoupon($this));
  451.             }
  452.             $this->checkConflictsWithCoupons($order);
  453.             $this->checkMembership($order);
  454.             if ($this->isFreeShipping()) {
  455.                 $this->checkAllItemsForCategory($order);
  456.                 $this->checkAllItemsForProducts($order);
  457.                 $this->checkAllItemForProductClass($order);
  458.             } else {
  459.                 $this->checkCategory($order);
  460.                 $this->checkProductClass($order);
  461.                 $this->checkProducts($order);
  462.             }
  463.             $this->checkOrderTotal($order);
  464.             $this->checkZone($order);
  465.         }
  466.         return true;
  467.     }
  468.     // {{{ Date
  469.     /**
  470.      * Check coupon dates
  471.      *
  472.      * @throws \CDev\Coupons\Core\CompatibilityException
  473.      *
  474.      * @return void
  475.      */
  476.     protected function checkDate()
  477.     {
  478.         if (!$this->isStarted()) {
  479.             $this->throwCompatibilityException(
  480.                 '',
  481.                 'Sorry, the coupon you entered is invalid. Make sure the coupon code is spelled correctly'
  482.             );
  483.         }
  484.         if ($this->isExpired()) {
  485.             $this->throwCompatibilityException(
  486.                 '',
  487.                 'Sorry, the coupon has expired'
  488.             );
  489.         }
  490.     }
  491.     // }}}
  492.     // {{{ Usage
  493.     /**
  494.      * Check coupon usages
  495.      *
  496.      * @throws \CDev\Coupons\Core\CompatibilityException
  497.      *
  498.      * @return void
  499.      */
  500.     protected function checkUsage()
  501.     {
  502.         if ($this->getUsesLimit() && $this->getUsesLimit() <= $this->getUses()) {
  503.             $this->throwCompatibilityException(
  504.                 '',
  505.                 'Sorry, the coupon use limit has been reached'
  506.             );
  507.         }
  508.     }
  509.     /**
  510.      * Check coupon usages per user
  511.      *
  512.      * @throws \CDev\Coupons\Core\CompatibilityException
  513.      *
  514.      * @return void
  515.      */
  516.     protected function checkPerUserUsage(\XLite\Model\Profile $profile$inOrder)
  517.     {
  518.         if (>= $this->getUsesLimitPerUser()) {
  519.             return;
  520.         }
  521.         $profileUsesCount null;
  522.         if (array_key_exists($profile->getLogin(), static::$runtimeCacheForUsedCouponsCount)) {
  523.             $profileUsesCount = static::$runtimeCacheForUsedCouponsCount[$profile->getLogin()];
  524.         } else {
  525.             $profileUsesCount $this->calculatePerUserUsage($profile);
  526.             static::$runtimeCacheForUsedCouponsCount[$profile->getLogin()] = $profileUsesCount;
  527.         }
  528.         if ($inOrder) {
  529.             $profileUsesCount -= 1;
  530.         }
  531.         if ($this->getUsesLimitPerUser() <= $profileUsesCount) {
  532.             $this->throwCompatibilityException(
  533.                 '',
  534.                 'Sorry, the coupon use limit has been reached'
  535.             );
  536.         }
  537.     }
  538.     /**
  539.      * @param \XLite\Model\Profile $profile
  540.      *
  541.      * @return int
  542.      */
  543.     protected function calculatePerUserUsage(\XLite\Model\Profile $profile)
  544.     {
  545.         return $this->getUsedCoupons()->filter(
  546.             static function ($usedCoupon) use ($profile) {
  547.                 /** @var \CDev\Coupons\Model\UsedCoupon $usedCoupon */
  548.                 $orderProfileIdentificator $usedCoupon->getOrder()->getProfile()
  549.                     ? $usedCoupon->getOrder()->getProfile()->getLogin()
  550.                     : null;
  551.                 $currentProfileIdentificator $profile->getLogin();
  552.                 return $orderProfileIdentificator
  553.                     && $currentProfileIdentificator
  554.                     && $orderProfileIdentificator === $currentProfileIdentificator;
  555.             }
  556.         )->count();
  557.     }
  558.     // }}}
  559.     // {{{ Coupons conflicts
  560.     /**
  561.      * Check if coupon is unique within an order
  562.      *
  563.      * @param \XLite\Model\Order $order Order
  564.      *
  565.      * @throws \CDev\Coupons\Core\CompatibilityException
  566.      *
  567.      * @return boolean
  568.      */
  569.     public function checkUnique(\XLite\Model\Order $order)
  570.     {
  571.         if ($order->containsCoupon($this)) {
  572.             $this->throwCompatibilityException(
  573.                 '',
  574.                 'You have already used the coupon'
  575.             );
  576.         }
  577.         return true;
  578.     }
  579.     /**
  580.      * Check coupon usages
  581.      *
  582.      * @param \XLite\Model\Order $order Order
  583.      *
  584.      * @throws \CDev\Coupons\Core\CompatibilityException
  585.      *
  586.      * @return void
  587.      */
  588.     protected function checkConflictsWithCoupons(\XLite\Model\Order $order)
  589.     {
  590.         if (!$order->containsCoupon($this)) {
  591.             if ($this->getSingleUse() && count($this->getOrderUsedCoupons($order))) {
  592.                 $this->throwCompatibilityException(
  593.                     static::ERROR_SINGLE_USE,
  594.                     'This coupon cannot be combined with other coupons'
  595.                 );
  596.             }
  597.             if (!$this->getSingleUse() && $this->hasOrderSingleCoupon($order)) {
  598.                 $this->throwCompatibilityException(
  599.                     static::ERROR_SINGLE_USE2,
  600.                     'Sorry, this coupon cannot be combined with the coupon already applied. Revome the previously applied coupon and try again.'
  601.                 );
  602.             }
  603.         }
  604.     }
  605.     /**
  606.      * @param \XLite\Model\Order $order
  607.      *
  608.      * @return array
  609.      */
  610.     protected function getOrderUsedCoupons($order)
  611.     {
  612.         return $order->getUsedCoupons();
  613.     }
  614.     /**
  615.      * @param \XLite\Model\Order $order
  616.      *
  617.      * @return bool
  618.      */
  619.     protected function hasOrderSingleCoupon($order)
  620.     {
  621.         return $order->hasSingleUseCoupon();
  622.     }
  623.     // }}}
  624.     // {{{ Total
  625.     /**
  626.      * Check order total
  627.      *
  628.      * @param \XLite\Model\Order $order Order
  629.      *
  630.      * @throws \CDev\Coupons\Core\CompatibilityException
  631.      *
  632.      * @return void
  633.      */
  634.     protected function checkOrderTotal(\XLite\Model\Order $order)
  635.     {
  636.         $total $this->getOrderTotal($order);
  637.         $currency $order->getCurrency();
  638.         $rangeBegin $this->getTotalRangeBegin();
  639.         $rangeEnd $this->getTotalRangeEnd();
  640.         $betweenTotalCoupon $rangeBegin && $rangeEnd 0;
  641.         $rangeBeginValid $rangeBegin === 0.0 || $rangeBegin <= $total;
  642.         $rangeEndValid $rangeEnd === 0.0 || $rangeEnd >= $total;
  643.         $hasProductsConditions $this->getSpecificProducts()
  644.             || count($this->getCategories()) > 0
  645.             || count($this->getProductClasses()) > 0;
  646.         if ($betweenTotalCoupon && (!$rangeBeginValid || !$rangeEndValid)) {
  647.             $text $hasProductsConditions
  648.                 $this->getBetweenSubtotalConditionalExceptionText()
  649.                 : $this->getBetweenSubtotalExceptionText();
  650.             $this->throwCompatibilityException(
  651.                 static::ERROR_TOTAL,
  652.                 $text,
  653.                 [
  654.                     'min' => implode(''$currency->formatParts($rangeBegin)),
  655.                     'max' => implode(''$currency->formatParts($rangeEnd)),
  656.                 ]
  657.             );
  658.         } elseif (!$rangeBeginValid) {
  659.             $text $hasProductsConditions
  660.                 $this->getLeastSubtotalConditionalExceptionText()
  661.                 : $this->getLeastSubtotalExceptionText();
  662.             $this->throwCompatibilityException(
  663.                 static::ERROR_TOTAL,
  664.                 $text,
  665.                 [
  666.                     'min' => implode(''$currency->formatParts($rangeBegin))
  667.                 ]
  668.             );
  669.         } elseif (!$rangeEndValid) {
  670.             $text $hasProductsConditions
  671.                 $this->getExceedSubtotalConditionalExceptionText()
  672.                 : $this->getExceedSubtotalExceptionText();
  673.             $this->throwCompatibilityException(
  674.                 static::ERROR_TOTAL,
  675.                 $text,
  676.                 [
  677.                     'max' => implode(''$currency->formatParts($rangeEnd))
  678.                 ]
  679.             );
  680.         }
  681.     }
  682.     /**
  683.      * Return text of exception
  684.      *
  685.      * @return void
  686.      */
  687.     protected function getBetweenSubtotalExceptionText()
  688.     {
  689.         return 'To use the coupon, your order subtotal must be between X and Y';
  690.     }
  691.     /**
  692.      * Return text of exception
  693.      *
  694.      * @return void
  695.      */
  696.     protected function getLeastSubtotalExceptionText()
  697.     {
  698.         return 'To use the coupon, your order subtotal must be at least X';
  699.     }
  700.     /**
  701.      * Return text of exception
  702.      *
  703.      * @return void
  704.      */
  705.     protected function getExceedSubtotalExceptionText()
  706.     {
  707.         return 'To use the coupon, your order subtotal must not exceed Y';
  708.     }
  709.     /**
  710.      * Return text of exception
  711.      *
  712.      * @return void
  713.      */
  714.     protected function getBetweenSubtotalConditionalExceptionText()
  715.     {
  716.         return 'To use the coupon, your order subtotal must be between X and Y for specific products';
  717.     }
  718.     /**
  719.      * Return text of exception
  720.      *
  721.      * @return void
  722.      */
  723.     protected function getLeastSubtotalConditionalExceptionText()
  724.     {
  725.         return 'To use the coupon, your order subtotal must be at least X for specific products';
  726.     }
  727.     /**
  728.      * Return text of exception
  729.      *
  730.      * @return void
  731.      */
  732.     protected function getExceedSubtotalConditionalExceptionText()
  733.     {
  734.         return 'To use the coupon, your order subtotal must not exceed Y for specific products';
  735.     }
  736.     // }}}
  737.     // {{{ Category
  738.     /**
  739.      * Check coupon category
  740.      *
  741.      * @param \XLite\Model\Order $order Order
  742.      *
  743.      * @throws \CDev\Coupons\Core\CompatibilityException
  744.      *
  745.      * @return void
  746.      */
  747.     protected function checkCategory(\XLite\Model\Order $order)
  748.     {
  749.         if ($this->getCategories()->count()) {
  750.             $found false;
  751.             foreach ($order->getItems() as $item) {
  752.                 foreach ($item->getProduct()->getCategories() as $category) {
  753.                     if ($this->getCategories()->contains($category)) {
  754.                         $found true;
  755.                         break;
  756.                     }
  757.                 }
  758.                 if ($found) {
  759.                     break;
  760.                 }
  761.             }
  762.             if (!$found) {
  763.                 $this->throwCompatibilityException(
  764.                     '',
  765.                     'Sorry, the coupon you entered cannot be applied to the items in your cart'
  766.                 );
  767.             }
  768.         }
  769.     }
  770.     // }}}
  771.     // {{{ Products
  772.     /**
  773.      * Check coupon products
  774.      *
  775.      * @param \XLite\Model\Order $order Order
  776.      *
  777.      * @throws \CDev\Coupons\Core\CompatibilityException
  778.      *
  779.      * @return void
  780.      */
  781.     protected function checkProducts(\XLite\Model\Order $order)
  782.     {
  783.         if ($this->getSpecificProducts()) {
  784.             $applicableProductIds $this->getApplicableProductIds();
  785.             $found false;
  786.             foreach ($order->getItems() as $item) {
  787.                 if (in_array($item->getProduct()->getProductId(), $applicableProductIds)) {
  788.                     $found true;
  789.                     break;
  790.                 }
  791.             }
  792.             if (!$found) {
  793.                 $this->throwCompatibilityException(
  794.                     '',
  795.                     'Sorry, the coupon you entered cannot be applied to the items in your cart'
  796.                 );
  797.             }
  798.         }
  799.     }
  800.     // }}}
  801.     // {{{ Membership
  802.     /**
  803.      * Check coupon membership
  804.      *
  805.      * @param \XLite\Model\Order $order Order
  806.      *
  807.      * @throws \CDev\Coupons\Core\CompatibilityException
  808.      *
  809.      * @return void
  810.      */
  811.     protected function checkMembership(\XLite\Model\Order $order)
  812.     {
  813.         if (
  814.             $this->getMemberships()->count()
  815.             && (!$order->getProfile()
  816.                 || !$this->getMemberships()->contains($order->getProfile()->getMembership())
  817.             )
  818.         ) {
  819.             $this->throwCompatibilityException(
  820.                 '',
  821.                 'Sorry, the coupon you entered is not valid for your membership level. Contact the administrator'
  822.             );
  823.         }
  824.     }
  825.     // }}}
  826.     // {{{ Zone
  827.     /**
  828.      * Check coupon zone
  829.      *
  830.      * @param \XLite\Model\Order $order Order
  831.      *
  832.      * @throws \CDev\Coupons\Core\CompatibilityException
  833.      *
  834.      * @return void
  835.      */
  836.     protected function checkZone(\XLite\Model\Order $order)
  837.     {
  838.         $profile $order->getProfile();
  839.         $shippingAddress $profile $profile->getShippingAddress() : null;
  840.         if (!$shippingAddress) {
  841.             $shippingAddress \XLite\Model\Address::createDefaultShippingAddress();
  842.         }
  843.         if ($shippingAddress && !$this->getZones()->isEmpty()) {
  844.             $applicableZones \XLite\Core\Database::getRepo('XLite\Model\Zone')->findApplicableZones($shippingAddress->toArray());
  845.             $couponZoneIds array_map(static function ($zone) {
  846.                 return $zone->getZoneId();
  847.             }, $this->getZones()->toArray());
  848.             $isApplicable false;
  849.             foreach ($applicableZones as $zone) {
  850.                 if (in_array($zone->getZoneId(), $couponZoneIds)) {
  851.                     $isApplicable true;
  852.                     break;
  853.                 }
  854.             }
  855.             if (!$isApplicable) {
  856.                 $this->throwCompatibilityException(
  857.                     '',
  858.                     'Sorry, the coupon you entered cannot be applied to this delivery address'
  859.                 );
  860.             }
  861.         }
  862.     }
  863.     // }}}
  864.     // {{{ Product class
  865.     /**
  866.      * Check coupon product class
  867.      *
  868.      * @param \XLite\Model\Order $order Order
  869.      *
  870.      * @throws \CDev\Coupons\Core\CompatibilityException
  871.      *
  872.      * @return void
  873.      */
  874.     protected function checkProductClass(\XLite\Model\Order $order)
  875.     {
  876.         if ($this->getProductClasses()->count()) {
  877.             $found false;
  878.             foreach ($order->getItems() as $item) {
  879.                 if (
  880.                     $item->getProduct()->getProductClass()
  881.                     && $this->getProductClasses()->contains($item->getProduct()->getProductClass())
  882.                 ) {
  883.                     $found true;
  884.                     break;
  885.                 }
  886.             }
  887.             if (!$found) {
  888.                 $this->throwCompatibilityException(
  889.                     '',
  890.                     'Sorry, the coupon you entered cannot be applied to the items in your cart'
  891.                 );
  892.             }
  893.         }
  894.     }
  895.     // }}}
  896.     /**
  897.      * @throws \CDev\Coupons\Core\CompatibilityException
  898.      * @return void
  899.      */
  900.     protected function checkAllItemsForCategory(\XLite\Model\Order $order)
  901.     {
  902.         if ($this->getCategories()->count()) {
  903.             $isAll true;
  904.             foreach ($order->getItems() as $item) {
  905.                 if (!$this->isItemApplyCategoryCondition($item)) {
  906.                     $isAll false;
  907.                     break;
  908.                 }
  909.             }
  910.             if (!$isAll) {
  911.                 $this->throwCompatibilityException(
  912.                     '',
  913.                     'Sorry, the coupon you entered cannot be applied to the items in your cart'
  914.                 );
  915.             }
  916.         }
  917.     }
  918.     protected function isItemApplyCategoryCondition(\XLite\Model\OrderItem $item): bool
  919.     {
  920.         $found false;
  921.         foreach ($item->getProduct()->getCategories() as $category) {
  922.             if ($this->getCategories()->contains($category)) {
  923.                 $found true;
  924.                 break;
  925.             }
  926.         }
  927.         return $found;
  928.     }
  929.     /**
  930.      * @throws \CDev\Coupons\Core\CompatibilityException
  931.      * @return void
  932.      */
  933.     protected function checkAllItemsForProducts(\XLite\Model\Order $order)
  934.     {
  935.         if ($this->getSpecificProducts()) {
  936.             $applicableProductIds $this->getApplicableProductIds();
  937.             $isAll true;
  938.             foreach ($order->getItems() as $item) {
  939.                 if (!in_array($item->getProduct()->getProductId(), $applicableProductIds)) {
  940.                     $isAll false;
  941.                     break;
  942.                 }
  943.             }
  944.             if (!$isAll) {
  945.                 $this->throwCompatibilityException(
  946.                     '',
  947.                     'Sorry, the coupon you entered cannot be applied to the items in your cart'
  948.                 );
  949.             }
  950.         }
  951.     }
  952.     /**
  953.      * @throws \CDev\Coupons\Core\CompatibilityException
  954.      * @return void
  955.      */
  956.     protected function checkAllItemForProductClass(\XLite\Model\Order $order)
  957.     {
  958.         if ($this->getProductClasses()->count()) {
  959.             $isAll true;
  960.             foreach ($order->getItems() as $item) {
  961.                 $itemProductClass $item->getProduct()->getProductClass();
  962.                 if (
  963.                     !$itemProductClass
  964.                     || !$this->getProductClasses()->contains($itemProductClass)
  965.                 ) {
  966.                     $isAll false;
  967.                     break;
  968.                 }
  969.             }
  970.             if (!$isAll) {
  971.                 $this->throwCompatibilityException(
  972.                     '',
  973.                     'Sorry, the coupon you entered cannot be applied to the items in your cart'
  974.                 );
  975.             }
  976.         }
  977.     }
  978.     /**
  979.      * Throws exception
  980.      *
  981.      * @param string $code    Message params
  982.      * @param string $message Message text
  983.      * @param array  $params  Message params
  984.      *
  985.      * @throws \CDev\Coupons\Core\CompatibilityException
  986.      *
  987.      * @return void
  988.      */
  989.     protected function throwCompatibilityException($code ''$message null, array $params = [])
  990.     {
  991.         throw new \CDev\Coupons\Core\CompatibilityException($message$params$this$code);
  992.     }
  993.     /**
  994.      * Get id
  995.      *
  996.      * @return integer
  997.      */
  998.     public function getId()
  999.     {
  1000.         return $this->id;
  1001.     }
  1002.     /**
  1003.      * Set code
  1004.      *
  1005.      * @param string $code
  1006.      * @return Coupon
  1007.      */
  1008.     public function setCode($code)
  1009.     {
  1010.         $this->code $code;
  1011.         return $this;
  1012.     }
  1013.     /**
  1014.      * Get code
  1015.      *
  1016.      * @return string
  1017.      */
  1018.     public function getCode()
  1019.     {
  1020.         return $this->code;
  1021.     }
  1022.     /**
  1023.      * Set enabled
  1024.      *
  1025.      * @param boolean $enabled
  1026.      * @return Coupon
  1027.      */
  1028.     public function setEnabled($enabled)
  1029.     {
  1030.         $this->enabled = (bool)$enabled;
  1031.         return $this;
  1032.     }
  1033.     /**
  1034.      * Get enabled
  1035.      *
  1036.      * @return boolean
  1037.      */
  1038.     public function getEnabled()
  1039.     {
  1040.         return $this->enabled;
  1041.     }
  1042.     /**
  1043.      * Set value
  1044.      *
  1045.      * @param float $value
  1046.      * @return Coupon
  1047.      */
  1048.     public function setValue($value)
  1049.     {
  1050.         $this->value $value;
  1051.         return $this;
  1052.     }
  1053.     /**
  1054.      * Get value
  1055.      *
  1056.      * @return float
  1057.      */
  1058.     public function getValue()
  1059.     {
  1060.         return $this->value;
  1061.     }
  1062.     /**
  1063.      * Set type
  1064.      *
  1065.      * @param string $type
  1066.      * @return Coupon
  1067.      */
  1068.     public function setType($type)
  1069.     {
  1070.         $this->type $type;
  1071.         return $this;
  1072.     }
  1073.     /**
  1074.      * Get type
  1075.      *
  1076.      * @return string
  1077.      */
  1078.     public function getType()
  1079.     {
  1080.         return $this->type;
  1081.     }
  1082.     /**
  1083.      * Set comment
  1084.      *
  1085.      * @param string $comment
  1086.      * @return Coupon
  1087.      */
  1088.     public function setComment($comment)
  1089.     {
  1090.         $this->comment $comment;
  1091.         return $this;
  1092.     }
  1093.     /**
  1094.      * Get comment
  1095.      *
  1096.      * @return string
  1097.      */
  1098.     public function getComment()
  1099.     {
  1100.         return $this->comment;
  1101.     }
  1102.     /**
  1103.      * Set uses
  1104.      *
  1105.      * @param integer $uses
  1106.      * @return Coupon
  1107.      */
  1108.     public function setUses($uses)
  1109.     {
  1110.         $this->uses $uses;
  1111.         return $this;
  1112.     }
  1113.     /**
  1114.      * Get uses
  1115.      *
  1116.      * @return integer
  1117.      */
  1118.     public function getUses()
  1119.     {
  1120.         return $this->uses;
  1121.     }
  1122.     /**
  1123.      * Set dateRangeBegin
  1124.      *
  1125.      * @param integer $dateRangeBegin
  1126.      * @return Coupon
  1127.      */
  1128.     public function setDateRangeBegin($dateRangeBegin)
  1129.     {
  1130.         $this->dateRangeBegin $dateRangeBegin;
  1131.         return $this;
  1132.     }
  1133.     /**
  1134.      * Get dateRangeBegin
  1135.      *
  1136.      * @return integer
  1137.      */
  1138.     public function getDateRangeBegin()
  1139.     {
  1140.         return $this->dateRangeBegin;
  1141.     }
  1142.     /**
  1143.      * Set dateRangeEnd
  1144.      *
  1145.      * @param integer $dateRangeEnd
  1146.      * @return Coupon
  1147.      */
  1148.     public function setDateRangeEnd($dateRangeEnd)
  1149.     {
  1150.         $this->dateRangeEnd $dateRangeEnd;
  1151.         return $this;
  1152.     }
  1153.     /**
  1154.      * Get dateRangeEnd
  1155.      *
  1156.      * @return integer
  1157.      */
  1158.     public function getDateRangeEnd()
  1159.     {
  1160.         return $this->dateRangeEnd;
  1161.     }
  1162.     /**
  1163.      * Set totalRangeBegin
  1164.      *
  1165.      * @param float $totalRangeBegin
  1166.      * @return Coupon
  1167.      */
  1168.     public function setTotalRangeBegin($totalRangeBegin)
  1169.     {
  1170.         $this->totalRangeBegin $totalRangeBegin;
  1171.         return $this;
  1172.     }
  1173.     /**
  1174.      * Get totalRangeBegin
  1175.      *
  1176.      * @return float
  1177.      */
  1178.     public function getTotalRangeBegin()
  1179.     {
  1180.         return $this->totalRangeBegin;
  1181.     }
  1182.     /**
  1183.      * Set totalRangeEnd
  1184.      *
  1185.      * @param float $totalRangeEnd
  1186.      * @return Coupon
  1187.      */
  1188.     public function setTotalRangeEnd($totalRangeEnd)
  1189.     {
  1190.         $this->totalRangeEnd $totalRangeEnd;
  1191.         return $this;
  1192.     }
  1193.     /**
  1194.      * Get totalRangeEnd
  1195.      *
  1196.      * @return float
  1197.      */
  1198.     public function getTotalRangeEnd()
  1199.     {
  1200.         return $this->totalRangeEnd;
  1201.     }
  1202.     /**
  1203.      * Set usesLimit
  1204.      *
  1205.      * @param integer $usesLimit
  1206.      * @return Coupon
  1207.      */
  1208.     public function setUsesLimit($usesLimit)
  1209.     {
  1210.         $this->usesLimit $usesLimit;
  1211.         return $this;
  1212.     }
  1213.     /**
  1214.      * Get usesLimit
  1215.      *
  1216.      * @return integer
  1217.      */
  1218.     public function getUsesLimit()
  1219.     {
  1220.         return $this->usesLimit;
  1221.     }
  1222.     /**
  1223.      * Set usesLimitPerUser
  1224.      *
  1225.      * @param integer $usesLimitPerUser
  1226.      * @return Coupon
  1227.      */
  1228.     public function setUsesLimitPerUser($usesLimitPerUser)
  1229.     {
  1230.         $this->usesLimitPerUser $usesLimitPerUser;
  1231.         return $this;
  1232.     }
  1233.     /**
  1234.      * Get usesLimitPerUser
  1235.      *
  1236.      * @return integer
  1237.      */
  1238.     public function getUsesLimitPerUser()
  1239.     {
  1240.         return $this->usesLimitPerUser;
  1241.     }
  1242.     /**
  1243.      * Set singleUse
  1244.      *
  1245.      * @param boolean $singleUse
  1246.      * @return Coupon
  1247.      */
  1248.     public function setSingleUse($singleUse)
  1249.     {
  1250.         $this->singleUse $singleUse;
  1251.         return $this;
  1252.     }
  1253.     /**
  1254.      * Get singleUse
  1255.      *
  1256.      * @return boolean
  1257.      */
  1258.     public function getSingleUse()
  1259.     {
  1260.         return $this->singleUse;
  1261.     }
  1262.     /**
  1263.      * Set specificProducts
  1264.      *
  1265.      * @param boolean $specificProducts
  1266.      * @return Coupon
  1267.      */
  1268.     public function setSpecificProducts($specificProducts)
  1269.     {
  1270.         $this->specificProducts $specificProducts;
  1271.         return $this;
  1272.     }
  1273.     /**
  1274.      * Get specificProducts
  1275.      *
  1276.      * @return boolean
  1277.      */
  1278.     public function getSpecificProducts()
  1279.     {
  1280.         return $this->specificProducts;
  1281.     }
  1282.     /**
  1283.      * Add productClasses
  1284.      *
  1285.      * @param \XLite\Model\ProductClass $productClasses
  1286.      * @return Coupon
  1287.      */
  1288.     public function addProductClasses(\XLite\Model\ProductClass $productClasses)
  1289.     {
  1290.         $this->productClasses[] = $productClasses;
  1291.         return $this;
  1292.     }
  1293.     /**
  1294.      * Get productClasses
  1295.      *
  1296.      * @return \Doctrine\Common\Collections\Collection
  1297.      */
  1298.     public function getProductClasses()
  1299.     {
  1300.         return $this->productClasses;
  1301.     }
  1302.     /**
  1303.      * Clear product classes
  1304.      */
  1305.     public function clearProductClasses()
  1306.     {
  1307.         foreach ($this->getProductClasses()->getKeys() as $key) {
  1308.             $this->getProductClasses()->remove($key);
  1309.         }
  1310.     }
  1311.     /**
  1312.      * Add memberships
  1313.      *
  1314.      * @param \XLite\Model\Membership $memberships
  1315.      * @return Coupon
  1316.      */
  1317.     public function addMemberships(\XLite\Model\Membership $memberships)
  1318.     {
  1319.         $this->memberships[] = $memberships;
  1320.         return $this;
  1321.     }
  1322.     /**
  1323.      * Get memberships
  1324.      *
  1325.      * @return \Doctrine\Common\Collections\Collection
  1326.      */
  1327.     public function getMemberships()
  1328.     {
  1329.         return $this->memberships;
  1330.     }
  1331.     /**
  1332.      * Add coupon products
  1333.      *
  1334.      * @param \CDev\Coupons\Model\CouponProduct $couponProduct
  1335.      * @return Coupon
  1336.      */
  1337.     public function addCouponProducts(\CDev\Coupons\Model\CouponProduct $couponProduct)
  1338.     {
  1339.         $this->couponProducts[] = $couponProduct;
  1340.         return $this;
  1341.     }
  1342.     /**
  1343.      * Get product ids if coupon is specificProducts
  1344.      *
  1345.      * @return array
  1346.      */
  1347.     public function getApplicableProductIds()
  1348.     {
  1349.         $ids = [];
  1350.         if ($this->isPersistent() && $this->getSpecificProducts()) {
  1351.             $ids \XLite\Core\Database::getRepo('CDev\Coupons\Model\CouponProduct')
  1352.                 ->getCouponProductIds($this->getId());
  1353.         }
  1354.         return $ids;
  1355.     }
  1356.     /**
  1357.      * Get coupon products
  1358.      *
  1359.      * @return \Doctrine\Common\Collections\Collection
  1360.      */
  1361.     public function getCouponProducts()
  1362.     {
  1363.         return $this->couponProducts;
  1364.     }
  1365.     /**
  1366.      * Clear memberships
  1367.      */
  1368.     public function clearMemberships()
  1369.     {
  1370.         foreach ($this->getMemberships()->getKeys() as $key) {
  1371.             $this->getMemberships()->remove($key);
  1372.         }
  1373.     }
  1374.     /**
  1375.      * Add zones
  1376.      *
  1377.      * @param \XLite\Model\Zone $zone
  1378.      * @return Coupon
  1379.      */
  1380.     public function addZones(\XLite\Model\Zone $zone)
  1381.     {
  1382.         $this->zones[] = $zone;
  1383.         return $this;
  1384.     }
  1385.     /**
  1386.      * Get zones
  1387.      *
  1388.      * @return \Doctrine\Common\Collections\ArrayCollection
  1389.      */
  1390.     public function getZones()
  1391.     {
  1392.         return $this->zones;
  1393.     }
  1394.     /**
  1395.      * Clear zones
  1396.      */
  1397.     public function clearZones()
  1398.     {
  1399.         foreach ($this->getZones()->getKeys() as $key) {
  1400.             $this->getZones()->remove($key);
  1401.         }
  1402.     }
  1403.     /**
  1404.      * Add usedCoupons
  1405.      *
  1406.      * @param \CDev\Coupons\Model\UsedCoupon $usedCoupons
  1407.      * @return Coupon
  1408.      */
  1409.     public function addUsedCoupons(\CDev\Coupons\Model\UsedCoupon $usedCoupons)
  1410.     {
  1411.         $this->usedCoupons[] = $usedCoupons;
  1412.         return $this;
  1413.     }
  1414.     /**
  1415.      * Get usedCoupons
  1416.      *
  1417.      * @return \Doctrine\Common\Collections\Collection|\CDev\Coupons\Model\UsedCoupon[]
  1418.      */
  1419.     public function getUsedCoupons()
  1420.     {
  1421.         return $this->usedCoupons;
  1422.     }
  1423.     /**
  1424.      * Add categories
  1425.      *
  1426.      * @param \XLite\Model\Category $categories
  1427.      * @return Coupon
  1428.      */
  1429.     public function addCategories(\XLite\Model\Category $categories)
  1430.     {
  1431.         $this->getCategories()->add($categories);
  1432.         return $this;
  1433.     }
  1434.     /**
  1435.      * Get categories
  1436.      *
  1437.      * @return \Doctrine\Common\Collections\Collection
  1438.      */
  1439.     public function getCategories()
  1440.     {
  1441.         return $this->categories;
  1442.     }
  1443.     /**
  1444.      * Clear categories
  1445.      */
  1446.     public function clearCategories()
  1447.     {
  1448.         foreach ($this->getCategories()->getKeys() as $key) {
  1449.             $this->getCategories()->remove($key);
  1450.         }
  1451.     }
  1452.     public function getApplyFreeShippingTo(): string
  1453.     {
  1454.         return $this->applyFreeShippingTo;
  1455.     }
  1456.     public function setApplyFreeShippingTo(string $applyFreeShippingTo): void
  1457.     {
  1458.         $this->applyFreeShippingTo $applyFreeShippingTo;
  1459.     }
  1460.     public static function getReadableApplyFreeShippingTo(): array
  1461.     {
  1462.         return [
  1463.             static::APPLY_FREESHIP_TO_CHEAPEST    => static::t('[coupons] The cheapest shipping option'),
  1464.             static::APPLY_FREESHIP_TO_FREE_METHOD => static::t('[coupons] Free shipping method'),
  1465.         ];
  1466.     }
  1467.     public function isFreeShippingCheapestType(): bool
  1468.     {
  1469.         return $this->isFreeShipping()
  1470.             && $this->applyFreeShippingTo === static::APPLY_FREESHIP_TO_CHEAPEST;
  1471.     }
  1472.     public function isFreeShippingFreeMethodType(): bool
  1473.     {
  1474.         return $this->isFreeShipping()
  1475.             && $this->applyFreeShippingTo === static::APPLY_FREESHIP_TO_FREE_METHOD;
  1476.     }
  1477.     public function isFreeShipping(): bool
  1478.     {
  1479.         return $this->getType() === static::TYPE_FREESHIP;
  1480.     }
  1481. }