classes/XLite/Model/Product.php line 65

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 XLite\Model;
  7. use ApiPlatform\Core\Annotation as ApiPlatform;
  8. use Doctrine\ORM\Mapping as ORM;
  9. use XCart\Framework\ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\IntegerDateFilter;
  10. use XLite\API\Endpoint\Product\DTO\ProductInput as ProductInput;
  11. use XLite\API\Endpoint\Product\DTO\ProductOutput as ProductOutput;
  12. use XLite\API\Filter\TranslationAwareOrderFilter;
  13. use XLite\Core\Cache\ExecuteCachedTrait;
  14. use XLite\Core\Converter;
  15. use XLite\Core\Exception\FatalException;
  16. use XLite\Core\Model\EntityVersion\EntityVersionInterface;
  17. use XLite\Core\Model\EntityVersion\EntityVersionTrait;
  18. use XLite\Model\Product\ProductStockAvailabilityPolicy;
  19. /**
  20.  * The "product" model class
  21.  *
  22.  * @ORM\Entity
  23.  * @ORM\Table  (name="products",
  24.  *      indexes={
  25.  *          @ORM\Index (name="sku", columns={"sku"}),
  26.  *          @ORM\Index (name="price", columns={"price"}),
  27.  *          @ORM\Index (name="weight", columns={"weight"}),
  28.  *          @ORM\Index (name="free_shipping", columns={"free_shipping"}),
  29.  *          @ORM\Index (name="customerArea", columns={"enabled","arrivalDate"})
  30.  *      }
  31.  * )
  32.  * @ApiPlatform\ApiResource(
  33.  *     input=ProductInput::class,
  34.  *     output=ProductOutput::class,
  35.  *     itemOperations={
  36.  *         "get"={
  37.  *             "method"="GET",
  38.  *             "path"="/products/{product_id}",
  39.  *             "identifiers"={"product_id"},
  40.  *         },
  41.  *         "put"={
  42.  *             "method"="PUT",
  43.  *             "path"="/products/{product_id}",
  44.  *             "identifiers"={"product_id"},
  45.  *         },
  46.  *         "delete"={
  47.  *             "method"="DELETE",
  48.  *             "path"="/products/{product_id}",
  49.  *             "identifiers"={"product_id"},
  50.  *         }
  51.  *     },
  52.  *     collectionOperations={
  53.  *         "get"={
  54.  *             "method"="GET",
  55.  *             "path"="/products",
  56.  *             "identifiers"={"product_id"},
  57.  *         },
  58.  *         "post"={
  59.  *             "method"="POST",
  60.  *             "path"="/products",
  61.  *             "identifiers"={"product_id"},
  62.  *         }
  63.  *     }
  64.  * )
  65.  * @ApiPlatform\ApiFilter(IntegerDateFilter::class, properties={"updateDate"})
  66.  * @ApiPlatform\ApiFilter(TranslationAwareOrderFilter::class, properties={"name"="ASC","price"="ASC","arrivalDate"="ASC"})
  67.  */
  68. class Product extends \XLite\Model\Base\Catalog implements \XLite\Model\Base\IOrderItemEntityVersionInterface
  69. {
  70.     use EntityVersionTrait;
  71.     use ExecuteCachedTrait;
  72.     /**
  73.      * Default amounts
  74.      */
  75.     public const AMOUNT_DEFAULT_INV_TRACK 1000;
  76.     public const AMOUNT_DEFAULT_LOW_LIMIT 10;
  77.     /**
  78.      * Meta description type
  79.      */
  80.     public const META_DESC_TYPE_AUTO   'A';
  81.     public const META_DESC_TYPE_CUSTOM 'C';
  82.     /**
  83.      * Product unique ID
  84.      *
  85.      * @var integer
  86.      *
  87.      * @ORM\Id
  88.      * @ORM\GeneratedValue (strategy="AUTO")
  89.      * @ORM\Column         (type="integer", options={ "unsigned": true })
  90.      */
  91.     protected $product_id;
  92.     /**
  93.      * Product price
  94.      *
  95.      * @var float
  96.      *
  97.      * @ORM\Column (type="decimal", precision=14, scale=4)
  98.      */
  99.     protected $price 0.0000;
  100.     /**
  101.      * Product SKU
  102.      *
  103.      * @var string
  104.      *
  105.      * @ORM\Column (type="string", length=32, nullable=true)
  106.      */
  107.     protected $sku;
  108.     /**
  109.      * Is product available or not
  110.      *
  111.      * @var boolean
  112.      *
  113.      * @ORM\Column (type="boolean")
  114.      */
  115.     protected $enabled true;
  116.     /**
  117.      * Product weight
  118.      *
  119.      * @var float
  120.      *
  121.      * @ORM\Column (type="decimal", precision=14, scale=4)
  122.      */
  123.     protected $weight 0.0000;
  124.     /**
  125.      * Is product shipped in separate box
  126.      *
  127.      * @var boolean
  128.      *
  129.      * @ORM\Column (type="boolean")
  130.      */
  131.     protected $useSeparateBox false;
  132.     /**
  133.      * Product box width
  134.      *
  135.      * @var float
  136.      *
  137.      * @ORM\Column (type="decimal", precision=14, scale=4)
  138.      */
  139.     protected $boxWidth 0.0000;
  140.     /**
  141.      * Product box length
  142.      *
  143.      * @var float
  144.      *
  145.      * @ORM\Column (type="decimal", precision=14, scale=4)
  146.      */
  147.     protected $boxLength 0.0000;
  148.     /**
  149.      * Product box height
  150.      *
  151.      * @var float
  152.      *
  153.      * @ORM\Column (type="decimal", precision=14, scale=4)
  154.      */
  155.     protected $boxHeight 0.0000;
  156.     /**
  157.      * How many product items can be placed in a box
  158.      *
  159.      * @var integer
  160.      *
  161.      * @ORM\Column (type="integer")
  162.      */
  163.     protected $itemsPerBox 1;
  164.     /**
  165.      * Flag: false - product is shippable, true - product is not shippable
  166.      *
  167.      * @var boolean
  168.      *
  169.      * @ORM\Column (type="boolean")
  170.      */
  171.     protected $free_shipping false;
  172.     /**
  173.      * If false then the product is free from any taxes
  174.      *
  175.      * @var boolean
  176.      *
  177.      * @ORM\Column (type="boolean")
  178.      */
  179.     protected $taxable true;
  180.     /**
  181.      * Arrival date (UNIX timestamp)
  182.      *
  183.      * @var integer
  184.      *
  185.      * @ORM\Column (type="integer")
  186.      */
  187.     protected $arrivalDate 0;
  188.     /**
  189.      * Creation date (UNIX timestamp)
  190.      *
  191.      * @var integer
  192.      *
  193.      * @ORM\Column (type="integer")
  194.      */
  195.     protected $date 0;
  196.     /**
  197.      * Update date (UNIX timestamp)
  198.      *
  199.      * @var integer
  200.      *
  201.      * @ORM\Column (type="integer", options={ "unsigned": true })
  202.      */
  203.     protected $updateDate 0;
  204.     /**
  205.      * Is product need process or not
  206.      *
  207.      * @var boolean
  208.      *
  209.      * @ORM\Column (type="boolean")
  210.      */
  211.     protected $needProcess true;
  212.     /**
  213.      * Relation to a CategoryProducts entities
  214.      *
  215.      * @var \Doctrine\ORM\PersistentCollection
  216.      *
  217.      * @ORM\OneToMany (targetEntity="XLite\Model\CategoryProducts", mappedBy="product", cascade={"all"})
  218.      * @ORM\OrderBy   ({"orderbyInProduct" = "ASC"})
  219.      */
  220.     protected $categoryProducts;
  221.     /**
  222.      * Product order items
  223.      *
  224.      * @var \XLite\Model\OrderItem
  225.      *
  226.      * @ORM\OneToMany (targetEntity="XLite\Model\OrderItem", mappedBy="object")
  227.      */
  228.     protected $order_items;
  229.     /**
  230.      * Product images
  231.      *
  232.      * @var \Doctrine\Common\Collections\Collection
  233.      *
  234.      * @ORM\OneToMany (targetEntity="XLite\Model\Image\Product\Image", mappedBy="product", cascade={"all"})
  235.      * @ORM\OrderBy   ({"orderby" = "ASC"})
  236.      */
  237.     protected $images;
  238.     // {{{ Inventory properties
  239.     /**
  240.      * Is inventory tracking enabled or not
  241.      *
  242.      * @var boolean
  243.      *
  244.      * @ORM\Column (type="boolean")
  245.      */
  246.     protected $inventoryEnabled true;
  247.     /**
  248.      * Amount
  249.      *
  250.      * @var integer
  251.      *
  252.      * @ORM\Column (type="integer", options={ "unsigned": true })
  253.      */
  254.     protected $amount self::AMOUNT_DEFAULT_INV_TRACK;
  255.     /**
  256.      * Is low limit notification enabled for customer or not
  257.      *
  258.      * @var boolean
  259.      *
  260.      * @ORM\Column (type="boolean")
  261.      */
  262.     protected $lowLimitEnabledCustomer true;
  263.     /**
  264.      * Is low limit notification enabled for admin or not
  265.      *
  266.      * @var boolean
  267.      *
  268.      * @ORM\Column (type="boolean")
  269.      */
  270.     protected $lowLimitEnabled false;
  271.     /**
  272.      * Low limit amount
  273.      *
  274.      * @var integer
  275.      *
  276.      * @ORM\Column (type="integer", options={ "unsigned": true })
  277.      */
  278.     protected $lowLimitAmount self::AMOUNT_DEFAULT_LOW_LIMIT;
  279.     // }}}
  280.     /**
  281.      * Product class (relation)
  282.      *
  283.      * @var \XLite\Model\ProductClass
  284.      *
  285.      * @ORM\ManyToOne  (targetEntity="XLite\Model\ProductClass")
  286.      * @ORM\JoinColumn (name="product_class_id", referencedColumnName="id", onDelete="SET NULL")
  287.      */
  288.     protected $productClass;
  289.     /**
  290.      * Tax class (relation)
  291.      *
  292.      * @var \XLite\Model\TaxClass
  293.      *
  294.      * @ORM\ManyToOne  (targetEntity="XLite\Model\TaxClass")
  295.      * @ORM\JoinColumn (name="tax_class_id", referencedColumnName="id", onDelete="SET NULL")
  296.      */
  297.     protected $taxClass;
  298.     /**
  299.      * Attributes
  300.      *
  301.      * @var \Doctrine\Common\Collections\Collection
  302.      *
  303.      * @ORM\OneToMany (targetEntity="XLite\Model\Attribute", mappedBy="product", cascade={"all"})
  304.      * @ORM\OrderBy   ({"position" = "ASC"})
  305.      */
  306.     protected $attributes;
  307.     /**
  308.      * Attribute value (checkbox)
  309.      *
  310.      * @var \Doctrine\Common\Collections\Collection
  311.      *
  312.      * @ORM\OneToMany (targetEntity="XLite\Model\AttributeValue\AttributeValueCheckbox", mappedBy="product", cascade={"all"})
  313.      */
  314.     protected $attributeValueC;
  315.     /**
  316.      * Attribute value (text)
  317.      *
  318.      * @var \Doctrine\Common\Collections\Collection
  319.      *
  320.      * @ORM\OneToMany (targetEntity="XLite\Model\AttributeValue\AttributeValueText", mappedBy="product", cascade={"all"})
  321.      */
  322.     protected $attributeValueT;
  323.     /**
  324.      * Attribute value (select)
  325.      *
  326.      * @var \Doctrine\Common\Collections\Collection
  327.      *
  328.      * @ORM\OneToMany (targetEntity="XLite\Model\AttributeValue\AttributeValueSelect", mappedBy="product", cascade={"all"})
  329.      */
  330.     protected $attributeValueS;
  331.     /**
  332.      * Attribute value (hidden)
  333.      *
  334.      * @var \Doctrine\Common\Collections\Collection
  335.      *
  336.      * @ORM\OneToMany (targetEntity="XLite\Model\AttributeValue\AttributeValueHidden", mappedBy="product", cascade={"all"})
  337.      */
  338.     protected $attributeValueH;
  339.     /**
  340.      * Show product attributes in a separate tab
  341.      *
  342.      * @var boolean
  343.      *
  344.      * @ORM\Column (type="boolean")
  345.      */
  346.     protected $attrSepTab true;
  347.     /**
  348.      * How much product is sold (used in Top selling products statistics)
  349.      *
  350.      * @var integer
  351.      */
  352.     protected $sold 0;
  353.     /**
  354.      * Quick data
  355.      *
  356.      * @var \Doctrine\Common\Collections\Collection
  357.      *
  358.      * @ORM\OneToMany (targetEntity="XLite\Model\QuickData", mappedBy="product", cascade={"all"})
  359.      */
  360.     protected $quickData;
  361.     /**
  362.      * Storage of current attribute values according to the clear price will be calculated
  363.      *
  364.      * @var array
  365.      */
  366.     protected $attrValues = [];
  367.     /**
  368.      * Memberships
  369.      *
  370.      * @var \Doctrine\Common\Collections\ArrayCollection
  371.      *
  372.      * @ORM\ManyToMany (targetEntity="XLite\Model\Membership", inversedBy="products")
  373.      * @ORM\JoinTable (name="product_membership_links",
  374.      *      joinColumns={@ORM\JoinColumn (name="product_id", referencedColumnName="product_id", onDelete="CASCADE")},
  375.      *      inverseJoinColumns={@ORM\JoinColumn (name="membership_id", referencedColumnName="membership_id", onDelete="CASCADE")}
  376.      * )
  377.      */
  378.     protected $memberships;
  379.     /**
  380.      * Clean URLs
  381.      *
  382.      * @var \Doctrine\Common\Collections\Collection
  383.      *
  384.      * @ORM\OneToMany (targetEntity="XLite\Model\CleanURL", mappedBy="product", cascade={"all"})
  385.      * @ORM\OrderBy   ({"id" = "ASC"})
  386.      */
  387.     protected $cleanURLs;
  388.     /**
  389.      * Meta description type
  390.      *
  391.      * @var string
  392.      *
  393.      * @ORM\Column (type="string", length=1)
  394.      */
  395.     protected $metaDescType 'A';
  396.     /**
  397.      * Sales
  398.      *
  399.      * @var integer
  400.      *
  401.      * @ORM\Column (type="integer", options={ "unsigned": true })
  402.      */
  403.     protected $sales 0;
  404.     /**
  405.      * Flag to exporting entities
  406.      *
  407.      * @var boolean
  408.      *
  409.      * @ORM\Column (type="boolean")
  410.      */
  411.     protected $xcPendingExport false;
  412.     /**
  413.      * @var \Doctrine\Common\Collections\Collection
  414.      *
  415.      * @ORM\OneToMany (targetEntity="XLite\Model\ProductTranslation", mappedBy="owner", cascade={"all"})
  416.      */
  417.     protected $translations;
  418.     /**
  419.      * Flag for demo products.
  420.      *
  421.      * @ORM\Column (type="boolean")
  422.      */
  423.     protected bool $isDemoProduct false;
  424.   /**
  425.      * @ORM\Column (type="integer", options={ "unsigned": true, "default": 0 })
  426.      */
  427.     protected $quantity1 0;
  428.     /**
  429.      * Get csiMultiple
  430.      *
  431.      * @return int
  432.      */
  433.     public function getquantity1()
  434.     {
  435.         return (int) $this->quantity1;
  436.     }
  437.     /**
  438.      * Set csiMultiple
  439.      *
  440.      * @param int $csiMultiple
  441.      *
  442.      * @return self
  443.      */
  444.     public function setquantity1($quantity1)
  445.     {
  446.         $this->quantity1 = (int) $quantity1;
  447.         return $this;
  448.     }
  449.     /**
  450.      * Return GlobalTabs
  451.      *
  452.      * @return \XLite\Model\Product\GlobalTab[]
  453.      */
  454.     public function getGlobalTabs()
  455.     {
  456.         return $this->executeCachedRuntime(static function () {
  457.             return \XLite\Core\Database::getRepo('\XLite\Model\Product\GlobalTab')->findAll();
  458.         }, ['getGlobalTabs']);
  459.     }
  460.     /**
  461.      * Clone
  462.      *
  463.      * @return static
  464.      */
  465.     public function cloneEntity()
  466.     {
  467.         /** @var Product $newProduct */
  468.         $newProduct parent::cloneEntity();
  469.         $this->cloneEntityScalar($newProduct);
  470.         if (!$newProduct->update() || !$newProduct->getProductId()) {
  471.             throw new FatalException('Can not clone product');
  472.         }
  473.         $this->cloneEntityModels($newProduct);
  474.         $this->cloneEntityCategories($newProduct);
  475.         $this->cloneEntityAttributes($newProduct);
  476.         $this->cloneEntityImages($newProduct);
  477.         $this->cloneEntityMemberships($newProduct);
  478.         $newProduct->update();
  479.         foreach ($newProduct->getCleanURLs() as $url) {
  480.             $newProduct->getCleanURLs()->removeElement($url);
  481.             \XLite\Core\Database::getEM()->remove($url);
  482.         }
  483.         $newProduct->setSales(0);
  484.         return $newProduct;
  485.     }
  486.     /**
  487.      * Clone entity (scalar fields)
  488.      *
  489.      * @param Product $newProduct New product
  490.      *
  491.      * @return void
  492.      */
  493.     protected function cloneEntityScalar(Product $newProduct)
  494.     {
  495.         $newProduct->setSku(\XLite\Core\Database::getRepo('XLite\Model\Product')->assembleUniqueSKU($this->getSku()));
  496.         $newProduct->setName($this->getCloneName($this->getName()));
  497.     }
  498.     /**
  499.      * Clone entity (model fields)
  500.      *
  501.      * @param Product $newProduct New product
  502.      *
  503.      * @return void
  504.      */
  505.     protected function cloneEntityModels(Product $newProduct)
  506.     {
  507.         $newProduct->setTaxClass($this->getTaxClass());
  508.         $newProduct->setProductClass($this->getProductClass());
  509.     }
  510.     /**
  511.      * Clone entity (categories)
  512.      *
  513.      * @param Product $newProduct New product
  514.      *
  515.      * @return void
  516.      */
  517.     protected function cloneEntityCategories(Product $newProduct)
  518.     {
  519.         foreach ($this->getCategories() as $category) {
  520.             $link = new \XLite\Model\CategoryProducts();
  521.             $link->setProduct($newProduct);
  522.             $link->setCategory($category);
  523.             $newProduct->addCategoryProducts($link);
  524.             \XLite\Core\Database::getEM()->persist($link);
  525.         }
  526.     }
  527.     /**
  528.      * Clone entity (attributes)
  529.      *
  530.      * @param Product $newProduct New product
  531.      *
  532.      * @return void
  533.      */
  534.     protected function cloneEntityAttributes(Product $newProduct)
  535.     {
  536.         foreach (\XLite\Model\Attribute::getTypes() as $type => $name) {
  537.             $methodGet 'getAttributeValue' $type;
  538.             $methodAdd 'addAttributeValue' $type;
  539.             $groups    = [];
  540.             foreach ($this->$methodGet() as $value) {
  541.                 if (!$value->getAttribute()->getProduct()) {
  542.                     $newValue $value->cloneEntity();
  543.                     $newValue->setProduct($newProduct);
  544.                     $newProduct->$methodAdd($newValue);
  545.                     \XLite\Core\Database::getEM()->persist($newValue);
  546.                     $attribute $value->getAttribute();
  547.                     $property  $attribute->getProperty($value->getProduct());
  548.                     if ($property && $value instanceof \XLite\Model\AttributeValue\Multiple) {
  549.                         $groups[$attribute->getId()] = [
  550.                             'attr'     => $attribute,
  551.                             'property' => $property,
  552.                         ];
  553.                     }
  554.                 }
  555.             }
  556.             foreach ($groups as $group) {
  557.                 $newProperty $group['property']->cloneEntity();
  558.                 $newProperty->setProduct($newProduct);
  559.                 $newProperty->setAttribute($group['attr']);
  560.                 \XLite\Core\Database::getEM()->persist($newProperty);
  561.             }
  562.         }
  563.         foreach ($this->getAttributes() as $attribute) {
  564.             $class        $attribute->getAttributeValueClass($attribute->getType());
  565.             $repo         \XLite\Core\Database::getRepo($class);
  566.             $methodAdd    'addAttributeValue' $attribute->getType();
  567.             $newAttribute $attribute->cloneEntity();
  568.             $newAttribute->setProduct($newProduct);
  569.             $newProduct->addAttributes($newAttribute);
  570.             \XLite\Core\Database::getEM()->persist($newAttribute);
  571.             foreach ($repo->findBy(['attribute' => $attribute'product' => $this]) as $value) {
  572.                 $newValue $value->cloneEntity();
  573.                 $newValue->setProduct($newProduct);
  574.                 $newValue->setAttribute($newAttribute);
  575.                 $newProduct->$methodAdd($newValue);
  576.                 \XLite\Core\Database::getEM()->persist($newValue);
  577.             }
  578.         }
  579.     }
  580.     /**
  581.      * Clone entity (images)
  582.      *
  583.      * @param Product $newProduct New product
  584.      *
  585.      * @return void
  586.      */
  587.     protected function cloneEntityImages(Product $newProduct)
  588.     {
  589.         foreach ($this->getImages() as $image) {
  590.             $newImage $image->cloneEntity();
  591.             $newImage->setProduct($newProduct);
  592.             $newProduct->addImages($newImage);
  593.         }
  594.     }
  595.     /**
  596.      * Clone entity (memberships)
  597.      *
  598.      * @param Product $newProduct New product
  599.      *
  600.      * @return void
  601.      */
  602.     protected function cloneEntityMemberships(Product $newProduct)
  603.     {
  604.         foreach ($this->getMemberships() as $membership) {
  605.             $newProduct->addMemberships($membership);
  606.         }
  607.     }
  608.     /**
  609.      * Constructor
  610.      *
  611.      * @param array $data Entity properties OPTIONAL
  612.      */
  613.     public function __construct(array $data = [])
  614.     {
  615.         $this->categoryProducts = new \Doctrine\Common\Collections\ArrayCollection();
  616.         $this->images           = new \Doctrine\Common\Collections\ArrayCollection();
  617.         $this->order_items      = new \Doctrine\Common\Collections\ArrayCollection();
  618.         $this->memberships      = new \Doctrine\Common\Collections\ArrayCollection();
  619.         $this->attributeValueC  = new \Doctrine\Common\Collections\ArrayCollection();
  620.         $this->attributeValueS  = new \Doctrine\Common\Collections\ArrayCollection();
  621.         $this->attributeValueT  = new \Doctrine\Common\Collections\ArrayCollection();
  622.         $this->attributes       = new \Doctrine\Common\Collections\ArrayCollection();
  623.         $this->quickData        = new \Doctrine\Common\Collections\ArrayCollection();
  624.         $this->free_shipping    = !\XLite\Core\Config::getInstance()->General->requires_shipping_default;
  625.         parent::__construct($data);
  626.     }
  627.     /**
  628.      * Get object unique id
  629.      *
  630.      * @return integer
  631.      */
  632.     public function getId()
  633.     {
  634.         return $this->getProductId();
  635.     }
  636.     /**
  637.      * Get weight
  638.      *
  639.      * @return float
  640.      */
  641.     public function getWeight()
  642.     {
  643.         return $this->weight;
  644.     }
  645.     /**
  646.      * Get price: modules should never overwrite this method
  647.      *
  648.      * @return float
  649.      */
  650.     public function getPrice()
  651.     {
  652.         return $this->price;
  653.     }
  654.     /**
  655.      * Get clear price: this price can be overwritten by modules
  656.      *
  657.      * @return float
  658.      */
  659.     public function getClearPrice()
  660.     {
  661.         return $this->getPrice();
  662.     }
  663.     /**
  664.      * Get net Price
  665.      *
  666.      * @return float
  667.      */
  668.     public function getNetPrice()
  669.     {
  670.         return \XLite\Logic\Price::getInstance()->apply($this'getClearPrice', ['taxable'], 'net');
  671.     }
  672.     /**
  673.      * Get display Price
  674.      *
  675.      * @return float
  676.      */
  677.     public function getDisplayPrice()
  678.     {
  679.         return \XLite\Logic\Price::getInstance()->apply($this'getNetPrice', ['taxable'], 'display');
  680.     }
  681.     /**
  682.      * Get quick data price
  683.      *
  684.      * @return float
  685.      */
  686.     public function getQuickDataPrice()
  687.     {
  688.         $price $this->getClearPrice();
  689.         foreach ($this->prepareAttributeValues() as $av) {
  690.             if (is_object($av)) {
  691.                 $price += $av->getAbsoluteValue('price');
  692.             }
  693.         }
  694.         return $price;
  695.     }
  696.     /**
  697.      * Get clear weight
  698.      *
  699.      * @return float
  700.      */
  701.     public function getClearWeight()
  702.     {
  703.         return $this->getWeight();
  704.     }
  705.     /**
  706.      * Get name
  707.      *
  708.      * @return string
  709.      */
  710.     public function getName()
  711.     {
  712.         return $this->getSoftTranslation()->getName();
  713.     }
  714.     /**
  715.      * Get SKU
  716.      *
  717.      * @return string
  718.      */
  719.     public function getSku()
  720.     {
  721.         return $this->sku !== null ? (string) $this->sku null;
  722.     }
  723.     /**
  724.      * Get quantity of the product
  725.      *
  726.      * @return integer
  727.      */
  728.     public function getQty()
  729.     {
  730.         return $this->getPublicAmount();
  731.     }
  732.     /**
  733.      * Get image
  734.      *
  735.      * @return \XLite\Model\Image\Product\Image
  736.      */
  737.     public function getImage()
  738.     {
  739.         return $this->getImages()->get(0);
  740.     }
  741.     /**
  742.      * Get public images
  743.      *
  744.      * @return array
  745.      */
  746.     public function getPublicImages()
  747.     {
  748.         return $this->getImages()->toArray();
  749.     }
  750.     /**
  751.      * Get free shipping flag
  752.      *
  753.      * @return boolean
  754.      */
  755.     public function getFreeShipping()
  756.     {
  757.         return $this->free_shipping;
  758.     }
  759.     /**
  760.      * Get shippable flag
  761.      *
  762.      * @return boolean
  763.      */
  764.     public function getShippable()
  765.     {
  766.         return !$this->getFreeShipping();
  767.     }
  768.     /**
  769.      * Set shippable flag
  770.      *
  771.      * @param boolean $value Value
  772.      *
  773.      * @return void
  774.      */
  775.     public function setShippable($value)
  776.     {
  777.         $this->setFreeShipping(!$value);
  778.     }
  779.     /**
  780.      * Return true if product can be purchased
  781.      *
  782.      * @return boolean
  783.      */
  784.     public function isAvailable()
  785.     {
  786.         return \XLite::isAdminZone() || $this->isPublicAvailable();
  787.     }
  788.     /**
  789.      * Return true if product can be purchased in customer interface
  790.      *
  791.      * @return boolean
  792.      */
  793.     public function isPublicAvailable()
  794.     {
  795.         return $this->isVisible()
  796.             && $this->availableInDate()
  797.             && !$this->isOutOfStock();
  798.     }
  799.     /**
  800.      * Check product visibility
  801.      *
  802.      * @return boolean
  803.      */
  804.     public function isVisible()
  805.     {
  806.         return $this->getEnabled()
  807.             && $this->hasAvailableMembership();
  808.     }
  809.     /**
  810.      * Get membership Ids
  811.      *
  812.      * @return array
  813.      */
  814.     public function getMembershipIds()
  815.     {
  816.         $result = [];
  817.         foreach ($this->getMemberships() as $membership) {
  818.             $result[] = $membership->getMembershipId();
  819.         }
  820.         return $result;
  821.     }
  822.     /**
  823.      * Flag if the category and active profile have the same memberships. (when category is displayed or hidden)
  824.      *
  825.      * @return boolean
  826.      */
  827.     public function hasAvailableMembership()
  828.     {
  829.         return $this->getMemberships()->count() === 0
  830.             || in_array(\XLite\Core\Auth::getInstance()->getMembershipId(), $this->getMembershipIds());
  831.     }
  832.     /**
  833.      * Flag if the product is available according date/time
  834.      *
  835.      * @return boolean
  836.      */
  837.     public function availableInDate()
  838.     {
  839.         return !$this->getArrivalDate()
  840.             || \XLite\Core\Converter::getDayEnd(static::getUserTime()) > $this->getArrivalDate();
  841.     }
  842.     /**
  843.      * Check if product has image or not
  844.      *
  845.      * @return boolean
  846.      */
  847.     public function hasImage()
  848.     {
  849.         return $this->getImage() !== null && $this->getImage()->isPersistent();
  850.     }
  851.     /**
  852.      * Return image URL
  853.      *
  854.      * @return string|void
  855.      */
  856.     public function getImageURL()
  857.     {
  858.         return $this->getImage() ? $this->getImage()->getURL() : null;
  859.     }
  860.     /**
  861.      * Return random product category
  862.      *
  863.      * @param integer|null $categoryId Category ID OPTIONAL
  864.      *
  865.      * @return \XLite\Model\Category
  866.      */
  867.     public function getCategory($categoryId null)
  868.     {
  869.         $result $this->getLink($categoryId)->getCategory();
  870.         if (empty($result)) {
  871.             $result = new \XLite\Model\Category();
  872.         }
  873.         return $result;
  874.     }
  875.     /**
  876.      * Return random product category ID
  877.      *
  878.      * @param integer|null $categoryId Category ID OPTIONAL
  879.      *
  880.      * @return integer
  881.      */
  882.     public function getCategoryId($categoryId null)
  883.     {
  884.         return $this->getCategory($categoryId)->getCategoryId();
  885.     }
  886.     /**
  887.      * Return list of product categories
  888.      *
  889.      * @return array
  890.      */
  891.     public function getCategories()
  892.     {
  893.         $result = [];
  894.         foreach ($this->getCategoryProducts() as $cp) {
  895.             $result[] = $cp->getCategory();
  896.         }
  897.         return $result;
  898.     }
  899.     // {{{ Inventory methods
  900.     /**
  901.      * Setter
  902.      *
  903.      * @param int $value Value to setcookie(name)
  904.      *
  905.      * @return void
  906.      */
  907.     public function setAmount($value)
  908.     {
  909.         $this->amount $this->correctAmount($value);
  910.     }
  911.     /**
  912.      * Setter
  913.      *
  914.      * @param integer $amount Amount to set
  915.      *
  916.      * @return void
  917.      */
  918.     public function setLowLimitAmount($amount)
  919.     {
  920.         $this->lowLimitAmount $this->correctAmount($amount);
  921.     }
  922.     /**
  923.      * Increase / decrease product inventory amount
  924.      *
  925.      * @param integer $delta Amount delta
  926.      *
  927.      * @return void
  928.      */
  929.     public function changeAmount($delta)
  930.     {
  931.         if ($this->getInventoryEnabled()) {
  932.             if($delta<0)
  933.             {
  934.                 if($this->getquantity1()+$delta>-1)
  935.                 {
  936.                     $this->setquantity1($this->getquantity1() + $delta);
  937.                 }
  938.                 else
  939.                 {
  940.                    
  941.                    $rest=$this->getquantity1()+$delta;
  942.                     $this->setquantity1(0);
  943.                        $this->setAmount($this->getPublicAmount()+ $rest);
  944.                 }
  945.             }
  946.             else
  947.             {
  948.                 $this->setAmount($this->getPublicAmount()-$this->getquantity1() + $delta);
  949.             }
  950.             
  951.         }
  952.     }
  953.     /**
  954.      * Get public amount
  955.      *
  956.      * @return integer
  957.      */
  958.     public function getPublicAmount()
  959.     {
  960.         return $this->getAmount()+$this->getquantity1();
  961.     }
  962.     /**
  963.      * Get low available amount
  964.      *
  965.      * @return integer
  966.      */
  967.     public function getLowAvailableAmount()
  968.     {
  969.         return $this->getInventoryEnabled()
  970.             ? min($this->getLowDefaultAmount(), $this->getAvailableAmount())
  971.             : $this->getLowDefaultAmount();
  972.     }
  973.     /**
  974.      * Check if product amount is less than its low limit
  975.      *
  976.      * @return boolean
  977.      */
  978.     public function isLowLimitReached()
  979.     {
  980.         return $this->getInventoryEnabled()
  981.             && $this->getLowLimitEnabled()
  982.             && $this->getPublicAmount() <= $this->getLowLimitAmount();
  983.     }
  984.     /**
  985.      * List of controllers which should not send notifications
  986.      *
  987.      * @return array
  988.      */
  989.     protected function getForbiddenControllers()
  990.     {
  991.         return [
  992.             '\XLite\Controller\Admin\EventTask',
  993.             '\XLite\Controller\Admin\ProductList',
  994.             '\XLite\Controller\Admin\Product',
  995.         ];
  996.     }
  997.     /**
  998.      * Check if notifications should be sended in current situation
  999.      *
  1000.      * @return boolean
  1001.      */
  1002.     public function isShouldSend()
  1003.     {
  1004.         $result false;
  1005.         if (!defined('LC_CACHE_BUILDING')) {
  1006.             $currentController     \XLite::getInstance()->getController();
  1007.             $isControllerForbidden array_reduce(
  1008.                 $this->getForbiddenControllers(),
  1009.                 static function ($carry$controllerName) use ($currentController) {
  1010.                     return $carry ?: ($currentController instanceof $controllerName);
  1011.                 },
  1012.                 false
  1013.             );
  1014.             $result \XLite\Core\Request::getInstance()->event !== 'import'
  1015.                 && !$isControllerForbidden;
  1016.         }
  1017.         return $result;
  1018.     }
  1019.     /**
  1020.      * Check and (if needed) correct amount value
  1021.      *
  1022.      * @param integer $amount Value to check
  1023.      *
  1024.      * @return integer
  1025.      */
  1026.     protected function correctAmount($amount)
  1027.     {
  1028.         return Converter::toUnsigned32BitInt($amount);
  1029.     }
  1030.     /**
  1031.      * Get a low default amount
  1032.      *
  1033.      * @return integer
  1034.      */
  1035.     protected function getLowDefaultAmount()
  1036.     {
  1037.         return 1;
  1038.     }
  1039.     /**
  1040.      * Default qty value to show to customers
  1041.      *
  1042.      * @return integer
  1043.      */
  1044.     public function getDefaultAmount()
  1045.     {
  1046.         return static::AMOUNT_DEFAULT_INV_TRACK;
  1047.     }
  1048.     /**
  1049.      * Get ProductStockAvailabilityPolicy associated with this product
  1050.      *
  1051.      * @return ProductStockAvailabilityPolicy
  1052.      */
  1053.     public function getStockAvailabilityPolicy()
  1054.     {
  1055.         return new ProductStockAvailabilityPolicy($this);
  1056.     }
  1057.     /**
  1058.      * Return product amount available to add to cart
  1059.      *
  1060.      * @return integer
  1061.      */
  1062.     public function getAvailableAmount()
  1063.     {
  1064.         return $this->getStockAvailabilityPolicy()->getAvailableAmount(Cart::getInstance());
  1065.     }
  1066.     /**
  1067.      * Alias: is product in stock or not
  1068.      *
  1069.      * @return boolean
  1070.      */
  1071.     public function isOutOfStock()
  1072.     {
  1073.         return $this->getAmount() <= && $this->getInventoryEnabled();
  1074.     }
  1075.     /**
  1076.      * Alias: is all product items in cart
  1077.      *
  1078.      * @return boolean
  1079.      */
  1080.     public function isAllStockInCart()
  1081.     {
  1082.         return $this->getStockAvailabilityPolicy()->isAllStockInCart(Cart::getInstance());
  1083.     }
  1084.     /**
  1085.      * How many product items added to cart
  1086.      *
  1087.      * @return boolean
  1088.      */
  1089.     public function getItemsInCart()
  1090.     {
  1091.         return $this->getStockAvailabilityPolicy()->getInCartAmount(Cart::getInstance());
  1092.     }
  1093.     /**
  1094.      * How many product items added to cart
  1095.      *
  1096.      * @return boolean
  1097.      */
  1098.     public function getItemsInCartMessage()
  1099.     {
  1100.         $count $this->getStockAvailabilityPolicy()->getInCartAmount(Cart::getInstance());
  1101.         return \XLite\Core\Translation::getInstance()->translate(
  1102.             'Items in your cart: X',
  1103.             ['count' => $count]
  1104.         );
  1105.     }
  1106.     /**
  1107.      * Check if the product is out-of-stock
  1108.      *
  1109.      * @return boolean
  1110.      */
  1111.     public function isShowStockWarning()
  1112.     {
  1113.         return $this->getInventoryEnabled()
  1114.             && $this->getLowLimitEnabledCustomer()
  1115.             && $this->getPublicAmount() <= $this->getLowLimitAmount()
  1116.             && !$this->isOutOfStock();
  1117.     }
  1118.     /**
  1119.      * Check if the product is out-of-stock
  1120.      *
  1121.      * @return boolean
  1122.      */
  1123.     public function isShowOutOfStockWarning()
  1124.     {
  1125.         return $this->getInventoryEnabled()
  1126.             && $this->isOutOfStock();
  1127.     }
  1128.     /**
  1129.      * Send notification to admin about product low limit
  1130.      *
  1131.      * @return void
  1132.      */
  1133.     public function sendLowLimitNotification()
  1134.     {
  1135.         \XLite\Core\Mailer::sendLowLimitWarningAdmin($this->prepareDataForNotification());
  1136.     }
  1137.     /**
  1138.      * Prepare data for 'low limit warning' email notifications
  1139.      *
  1140.      * @return array
  1141.      */
  1142.     public function prepareDataForNotification()
  1143.     {
  1144.         $data = [];
  1145.         $data['product'] = $this;
  1146.         $data['name']    = $this->getName();
  1147.         $data['sku']     = $this->getSKU();
  1148.         $data['amount']  = $this->getAmount();
  1149.         $params           = [
  1150.             'product_id' => $this->getProductId(),
  1151.             'page'       => 'inventory',
  1152.         ];
  1153.         $data['adminURL'] = \XLite\Core\Converter::buildFullURL('product'''$params\XLite::getAdminScript(), false);
  1154.         return $data;
  1155.     }
  1156.     // }}}
  1157.     /**
  1158.      * Set sku and trim it to max length
  1159.      *
  1160.      * @param string $sku
  1161.      *
  1162.      * @return void
  1163.      */
  1164.     public function setSku($sku)
  1165.     {
  1166.         $this->sku substr(
  1167.             $sku,
  1168.             0,
  1169.             \XLite\Core\Database::getRepo('XLite\Model\Product')->getFieldInfo('sku''length')
  1170.         );
  1171.     }
  1172.     /**
  1173.      * Get product Url
  1174.      *
  1175.      * @return string
  1176.      */
  1177.     public function getURL()
  1178.     {
  1179.         return $this->getProductId()
  1180.             ? \XLite\Core\Converter::makeURLValid(
  1181.                 \XLite\Core\Converter::buildURL('product''', ['product_id' => $this->getProductId()])
  1182.             )
  1183.             : null;
  1184.     }
  1185.     /**
  1186.      * Get front URL
  1187.      *
  1188.      * @return string
  1189.      */
  1190.     public function getFrontURL($withAttributes false$buildCuInAdminZone false)
  1191.     {
  1192.         return $this->getProductId()
  1193.             ? \XLite\Core\Converter::makeURLValid(
  1194.                 \XLite::getInstance()->getShopURL(
  1195.                     \XLite\Core\Converter::buildURL(
  1196.                         'product',
  1197.                         '',
  1198.                         $this->getParamsForFrontURL($withAttributes),
  1199.                         \XLite::getCustomerScript(),
  1200.                         $buildCuInAdminZone
  1201.                     )
  1202.                 )
  1203.             )
  1204.             : null;
  1205.     }
  1206.     /**
  1207.      * @return array
  1208.      */
  1209.     protected function getParamsForFrontURL($withAttributes false)
  1210.     {
  1211.         $result = [
  1212.             'product_id' => $this->getProductId(),
  1213.         ];
  1214.         if ($withAttributes) {
  1215.             $result['attribute_values'] = $this->getAttributeValuesParams();
  1216.         }
  1217.         return $result;
  1218.     }
  1219.     /**
  1220.      * @return string
  1221.      */
  1222.     protected function getAttributeValuesParams()
  1223.     {
  1224.         $validAttributes array_filter(
  1225.             $this->getAttrValues(),
  1226.             static function ($attr) {
  1227.                 return $attr && $attr->getAttribute();
  1228.             }
  1229.         );
  1230.         $paramsStrings array_map(
  1231.             static function ($attr) {
  1232.                 return $attr->getAttribute()->getId() . '_' $attr->getId();
  1233.             },
  1234.             $validAttributes
  1235.         );
  1236.         return trim(join(','$paramsStrings), ',');
  1237.     }
  1238.     /**
  1239.      * Minimal available amount
  1240.      *
  1241.      * @return integer
  1242.      */
  1243.     public function getMinPurchaseLimit()
  1244.     {
  1245.         return 1;
  1246.     }
  1247.     /**
  1248.      * Maximal available amount
  1249.      *
  1250.      * @return integer
  1251.      */
  1252.     public function getMaxPurchaseLimit()
  1253.     {
  1254.         return (int) \XLite\Core\Config::getInstance()->General->default_purchase_limit;
  1255.     }
  1256.     /**
  1257.      * Return product position in category
  1258.      *
  1259.      * @param integer|null $categoryId Category ID OPTIONAL
  1260.      *
  1261.      * @return integer|void
  1262.      */
  1263.     public function getOrderBy($categoryId null)
  1264.     {
  1265.         $link $this->getLink($categoryId);
  1266.         return $link $link->getOrderBy() : null;
  1267.     }
  1268.     /**
  1269.      * Count product images
  1270.      *
  1271.      * @return integer
  1272.      */
  1273.     public function countImages()
  1274.     {
  1275.         return count($this->getPublicImages());
  1276.     }
  1277.     /**
  1278.      * Try to fetch product description
  1279.      *
  1280.      * @return string
  1281.      */
  1282.     public function getCommonDescription()
  1283.     {
  1284.         return $this->getBriefDescription() ?: $this->getDescription();
  1285.     }
  1286.     /**
  1287.      * Get processed product brief description
  1288.      *
  1289.      * @return string
  1290.      */
  1291.     public function getProcessedBriefDescription()
  1292.     {
  1293.         $value $this->getBriefDescription();
  1294.         return $value
  1295.             ? static::getPreprocessedValue($value)
  1296.             : $value;
  1297.     }
  1298.     /**
  1299.      * Get processed product description
  1300.      *
  1301.      * @return string
  1302.      */
  1303.     public function getProcessedDescription()
  1304.     {
  1305.         $value $this->getDescription();
  1306.         return $value
  1307.             ? static::getPreprocessedValue($value)
  1308.             : $value;
  1309.     }
  1310.     /**
  1311.      * Get taxable basis
  1312.      *
  1313.      * @return float
  1314.      */
  1315.     public function getTaxableBasis()
  1316.     {
  1317.         return $this->getNetPrice();
  1318.     }
  1319.     /**
  1320.      * Check if product inventory changed
  1321.      *
  1322.      * @return boolean
  1323.      */
  1324.     public function isInventoryChanged()
  1325.     {
  1326.         $changeset \XLite\Core\Database::getEM()
  1327.             ->getUnitOfWork()
  1328.             ->getEntityChangeSet($this);
  1329.         return isset($changeset['amount']);
  1330.     }
  1331.     /**
  1332.      * Set product class
  1333.      *
  1334.      * @param ProductClass|null $productClass     Product class OPTIONAL
  1335.      * @param boolean           $preprocessChange Flag if use preprocessChangeProductClass() OPTIONAL
  1336.      *
  1337.      * @return Product
  1338.      */
  1339.     public function setProductClass(\XLite\Model\ProductClass $productClass null$preprocessChange true)
  1340.     {
  1341.         if (
  1342.             $preprocessChange
  1343.             && $this->productClass
  1344.             && (
  1345.                 !$productClass
  1346.                 || $productClass->getId() !== $this->productClass->getId()
  1347.             )
  1348.         ) {
  1349.             $this->preprocessChangeProductClass();
  1350.         }
  1351.         $this->productClass $productClass;
  1352.         return $this;
  1353.     }
  1354.     /**
  1355.      * Get attr values
  1356.      *
  1357.      * @return array
  1358.      */
  1359.     public function getAttrValues()
  1360.     {
  1361.         return $this->attrValues;
  1362.     }
  1363.     /**
  1364.      * Set attr values
  1365.      *
  1366.      * @param array $value Value
  1367.      *
  1368.      * @return void
  1369.      */
  1370.     public function setAttrValues($value)
  1371.     {
  1372.         $this->attrValues $value;
  1373.     }
  1374.     /**
  1375.      * Sort editable attributes
  1376.      *
  1377.      * @param array $a Attribute A
  1378.      * @param array $b Attribute B
  1379.      *
  1380.      * @return int
  1381.      */
  1382.     protected function sortEditableAttributes($a$b)
  1383.     {
  1384.         return $a['position'] > $b['position'] ? : -1;
  1385.     }
  1386.     /**
  1387.      * Get editable attributes
  1388.      *
  1389.      * @return list<\XLite\Model\Attribute>
  1390.      */
  1391.     public function getEditableAttributes()
  1392.     {
  1393.         return $this->executeCachedRuntime(function () {
  1394.             return $this->defineEditableAttributes();
  1395.         }, ['getEditableAttributes'$this->getProductId()]);
  1396.     }
  1397.     /**
  1398.      * @return array
  1399.      */
  1400.     protected function defineEditableAttributes()
  1401.     {
  1402.         $result = [];
  1403.         foreach ((array) \XLite\Model\Attribute::getTypes() as $type => $name) {
  1404.             $class \XLite\Model\Attribute::getAttributeValueClass($type);
  1405.             if (is_subclass_of($class'XLite\Model\AttributeValue\Multiple')) {
  1406.                 $result[] = \XLite\Core\Database::getRepo($class)->findMultipleAttributes($this);
  1407.             } elseif ($class === '\XLite\Model\AttributeValue\AttributeValueText') {
  1408.                 $result[] = \XLite\Core\Database::getRepo($class)->findEditableAttributes($this);
  1409.             }
  1410.         }
  1411.         $result = (array) call_user_func_array('array_merge'$result);
  1412.         usort($result, [$this'sortEditableAttributes']);
  1413.         if ($result) {
  1414.             foreach ($result as $k => $v) {
  1415.                 $result[$k] = $v[0];
  1416.             }
  1417.         }
  1418.         return $result;
  1419.     }
  1420.     /**
  1421.      * Sort visible attributes
  1422.      *
  1423.      * @param \XLite\Model\Attribute $a Attribute A
  1424.      * @param \XLite\Model\Attribute $b Attribute B
  1425.      *
  1426.      * @return int
  1427.      */
  1428.     protected function sortVisibleAttributes($a$b)
  1429.     {
  1430.         return $a->getPosition($this) > $b->getPosition($this) ? : -1;
  1431.     }
  1432.     /**
  1433.      * Get all visible attributes with values
  1434.      *
  1435.      * @return list<\XLite\Model\Attribute>
  1436.      */
  1437.     public function getVisibleAttributes()
  1438.     {
  1439.         return $this->executeCachedRuntime(function () {
  1440.             return $this->defineVisibleAttributes();
  1441.         }, ['getVisibleAttributes'$this->getProductId()]);
  1442.     }
  1443.     /**
  1444.      * @return array
  1445.      */
  1446.     protected function defineVisibleAttributes()
  1447.     {
  1448.         $result = [];
  1449.         foreach ((array) \XLite\Model\Attribute::getTypes() as $type => $name) {
  1450.             if ($type === \XLite\Model\Attribute::TYPE_HIDDEN) {
  1451.                 continue;
  1452.             }
  1453.             $result array_merge(
  1454.                 $result,
  1455.                 \XLite\Core\Database::getRepo('XLite\Model\Attribute')
  1456.                     ->getAttributesWithValues($this$type)
  1457.             );
  1458.         }
  1459.         usort($result, [$this'sortVisibleAttributes']);
  1460.         return $result;
  1461.     }
  1462.     /**
  1463.      * Check - product has visible attrbiutes or not
  1464.      *
  1465.      * @return boolean
  1466.      */
  1467.     public function hasVisibleAttributes()
  1468.     {
  1469.         return count($this->getVisibleAttributes());
  1470.     }
  1471.     /**
  1472.      * Get editable attributes ids
  1473.      *
  1474.      * @return array
  1475.      */
  1476.     public function getEditableAttributesIds()
  1477.     {
  1478.         $result = [];
  1479.         foreach ($this->getEditableAttributes() as $a) {
  1480.             $result[] = $a->getId();
  1481.         }
  1482.         sort($result);
  1483.         return $result;
  1484.     }
  1485.     /**
  1486.      * Check - product has editable attrbiutes or not
  1487.      *
  1488.      * @return boolean
  1489.      */
  1490.     public function hasEditableAttributes()
  1491.     {
  1492.         return count($this->getEditableAttributes());
  1493.     }
  1494.     /**
  1495.      * Get multiple attributes
  1496.      *
  1497.      * @return array
  1498.      */
  1499.     public function getMultipleAttributes()
  1500.     {
  1501.         $result = [];
  1502.         foreach (\XLite\Model\Attribute::getTypes() as $type => $name) {
  1503.             $class \XLite\Model\Attribute::getAttributeValueClass($type);
  1504.             if (is_subclass_of($class'XLite\Model\AttributeValue\Multiple')) {
  1505.                 $result array_merge(
  1506.                     $result,
  1507.                     \XLite\Core\Database::getRepo($class)->findMultipleAttributes($this)
  1508.                 );
  1509.             }
  1510.         }
  1511.         if ($result) {
  1512.             foreach ($result as $k => $v) {
  1513.                 $result[$k] = $v[0];
  1514.             }
  1515.         }
  1516.         return $result;
  1517.     }
  1518.     /**
  1519.      * Get multiple attributes ids
  1520.      *
  1521.      * @return array
  1522.      */
  1523.     public function getMultipleAttributesIds()
  1524.     {
  1525.         $result = [];
  1526.         foreach ($this->getMultipleAttributes() as $a) {
  1527.             $result[] = $a->getId();
  1528.         }
  1529.         sort($result);
  1530.         return $result;
  1531.     }
  1532.     /**
  1533.      * Check - product has multiple attributes or not
  1534.      *
  1535.      * @return boolean
  1536.      */
  1537.     public function hasMultipleAttributes()
  1538.     {
  1539.         return count($this->getMultipleAttributes());
  1540.     }
  1541.     /**
  1542.      * Update quick data
  1543.      *
  1544.      * @return void
  1545.      */
  1546.     public function updateQuickData()
  1547.     {
  1548.         if ($this->isPersistent()) {
  1549.             \XLite\Core\QuickData::getInstance()->updateProductData($this);
  1550.         }
  1551.     }
  1552.     /**
  1553.      * @return boolean
  1554.      */
  1555.     protected function showPlaceholderOption()
  1556.     {
  1557.         if (\XLite::isAdminZone()) {
  1558.             return false;
  1559.         } elseif (\XLite\Core\Config::getInstance()->General->force_choose_product_options === 'quicklook') {
  1560.             return \XLite::getController()->getTarget() !== 'product';
  1561.         } elseif (\XLite\Core\Config::getInstance()->General->force_choose_product_options === 'product_page') {
  1562.             return true;
  1563.         }
  1564.         return false;
  1565.     }
  1566.     /**
  1567.      * Prepare attribute values
  1568.      *
  1569.      * @param array $ids Request-based selected attribute values OPTIONAL
  1570.      *
  1571.      * @return array
  1572.      */
  1573.     public function prepareAttributeValues($ids = [])
  1574.     {
  1575.         return $this->executeCachedRuntime(function () use ($ids) {
  1576.             $attributeValues = [];
  1577.             foreach ($this->getEditableAttributes() as $a) {
  1578.                 if ($a->getType() === \XLite\Model\Attribute::TYPE_TEXT) {
  1579.                     $value     $ids[$a->getId()] ?? $a->getAttributeValue($this)->getValue();
  1580.                     $attrValue $a->getAttributeValue($this);
  1581.                     $attributeValues[$a->getId()] = [
  1582.                         'attributeValue' => $attrValue,
  1583.                         'value'          => $value,
  1584.                     ];
  1585.                 } elseif ($a->getType() === \XLite\Model\Attribute::TYPE_CHECKBOX) {
  1586.                     $found null;
  1587.                     if (isset($ids[$a->getId()])) {
  1588.                         foreach ($a->getAttributeValue($this) as $av) {
  1589.                             if ($av->getId() === (int) $ids[$a->getId()]) {
  1590.                                 $found $av;
  1591.                                 break;
  1592.                             }
  1593.                         }
  1594.                     }
  1595.                     $value $found ?: $a->getDefaultAttributeValue($this);
  1596.                     $attributeValues[$a->getId()] = $value;
  1597.                 } else {
  1598.                     if (!$this->showPlaceholderOption()) {
  1599.                         $attributeValues[$a->getId()] = $a->getDefaultAttributeValue($this);
  1600.                     }
  1601.                     if (isset($ids[$a->getId()])) {
  1602.                         foreach ($a->getAttributeValue($this) as $av) {
  1603.                             if ($av->getId() === (int) $ids[$a->getId()]) {
  1604.                                 $attributeValues[$a->getId()] = $av;
  1605.                                 break;
  1606.                             }
  1607.                         }
  1608.                     }
  1609.                 }
  1610.             }
  1611.             return $attributeValues;
  1612.         }, ['prepareAttributeValues'$ids]);
  1613.     }
  1614.     /**
  1615.      * Define the specific clone name for the product
  1616.      *
  1617.      * @param string $name Product name
  1618.      *
  1619.      * @return string
  1620.      */
  1621.     protected function getCloneName($name)
  1622.     {
  1623.         return $name ' [ clone ]';
  1624.     }
  1625.     /**
  1626.      * Preprocess change product class
  1627.      *
  1628.      * @return void
  1629.      */
  1630.     protected function preprocessChangeProductClass()
  1631.     {
  1632.         if ($this->productClass) {
  1633.             foreach ($this->productClass->getAttributes() as $a) {
  1634.                 $class $a->getAttributeValueClass($a->getType());
  1635.                 $repo  \XLite\Core\Database::getRepo($class);
  1636.                 foreach ($repo->findBy(['product' => $this'attribute' => $a]) as $v) {
  1637.                     $repo->delete($v);
  1638.                 }
  1639.             }
  1640.         }
  1641.     }
  1642.     /**
  1643.      * Return certain Product <--> Category association
  1644.      *
  1645.      * @param integer|null $categoryId Category ID
  1646.      *
  1647.      * @return \XLite\Model\CategoryProducts|void
  1648.      */
  1649.     protected function findLinkByCategoryId($categoryId)
  1650.     {
  1651.         $result null;
  1652.         foreach ($this->getCategoryProducts() as $cp) {
  1653.             if ($cp->getCategory() && $cp->getCategory()->getCategoryId() == $categoryId) {
  1654.                 $result $cp;
  1655.             }
  1656.         }
  1657.         return $result;
  1658.     }
  1659.     /**
  1660.      * Return certain Product <--> Category association
  1661.      *
  1662.      * @param integer|null $categoryId Category ID OPTIONAL
  1663.      *
  1664.      * @return \XLite\Model\CategoryProducts
  1665.      */
  1666.     protected function getLink($categoryId null)
  1667.     {
  1668.         $result = empty($categoryId)
  1669.             ? $this->getCategoryProducts()->first()
  1670.             : $this->findLinkByCategoryId($categoryId);
  1671.         if (empty($result)) {
  1672.             $result = new \XLite\Model\CategoryProducts();
  1673.         }
  1674.         return $result;
  1675.     }
  1676.     /**
  1677.      * Returns position of the product in the given category
  1678.      *
  1679.      * @param integer $category
  1680.      *
  1681.      * @return integer
  1682.      */
  1683.     public function getPosition($category)
  1684.     {
  1685.         return $this->getOrderBy($category);
  1686.     }
  1687.     /**
  1688.      * Sets the position of the product in the given category
  1689.      *
  1690.      * @param array $value
  1691.      *
  1692.      * @return void
  1693.      */
  1694.     public function setPosition($value)
  1695.     {
  1696.         $link $this->getLink($value['category']);
  1697.         $link->setProduct($this);
  1698.         $link->setOrderby($value['position']);
  1699.         \XLite\Core\Database::getEM()->persist($link);
  1700.         \XLite\Core\Database::getEM()->flush($link);
  1701.     }
  1702.     /**
  1703.      * Returns meta description
  1704.      *
  1705.      * @return string
  1706.      */
  1707.     public function getMetaDesc()
  1708.     {
  1709.         return $this->getMetaDescType() === static::META_DESC_TYPE_AUTO
  1710.             ? static::generateMetaDescription($this->getCommonDescription())
  1711.             : $this->getSoftTranslation()->getMetaDesc();
  1712.     }
  1713.     /**
  1714.      * Generate meta description
  1715.      *
  1716.      * @param $description
  1717.      *
  1718.      * @return string
  1719.      */
  1720.     public static function generateMetaDescription($description)
  1721.     {
  1722.         return static::postprocessMetaDescription($description);
  1723.     }
  1724.     /**
  1725.      * Returns meta description type
  1726.      *
  1727.      * @return string
  1728.      */
  1729.     public function getMetaDescType()
  1730.     {
  1731.         $result $this->metaDescType;
  1732.         if (!$result) {
  1733.             $metaDescPresent array_reduce($this->getTranslations()->toArray(), static function ($carry$item) {
  1734.                 return $carry ?: (bool) $item->getMetaDesc();
  1735.             }, false);
  1736.             $result $metaDescPresent
  1737.                 ? static::META_DESC_TYPE_CUSTOM
  1738.                 : static::META_DESC_TYPE_AUTO;
  1739.         }
  1740.         return $result;
  1741.     }
  1742.     // {{{ Sales statistics
  1743.     /**
  1744.      * Set sales
  1745.      *
  1746.      * @param integer $sales Sales
  1747.      */
  1748.     public function setSales($sales)
  1749.     {
  1750.         $this->sales max(0$sales);
  1751.     }
  1752.     /**
  1753.      * Return sales
  1754.      *
  1755.      * @return integer
  1756.      */
  1757.     public function getSales()
  1758.     {
  1759.         return $this->sales;
  1760.     }
  1761.     /**
  1762.      * Update sales
  1763.      */
  1764.     public function updateSales()
  1765.     {
  1766.         $this->setSales(
  1767.             $this->getRepository()->findSalesByProduct($this)
  1768.         );
  1769.     }
  1770.     // }}}
  1771.     /**
  1772.      * Get product_id
  1773.      *
  1774.      * @return integer
  1775.      */
  1776.     public function getProductId()
  1777.     {
  1778.         return $this->product_id;
  1779.     }
  1780.     /**
  1781.      * Set price
  1782.      *
  1783.      * @param float $price
  1784.      *
  1785.      * @return Product
  1786.      */
  1787.     public function setPrice($price)
  1788.     {
  1789.         $this->price Converter::toUnsigned32BitFloat($price);
  1790.         return $this;
  1791.     }
  1792.     /**
  1793.      * Set enabled
  1794.      *
  1795.      * @param boolean $enabled
  1796.      *
  1797.      * @return Product
  1798.      */
  1799.     public function setEnabled($enabled)
  1800.     {
  1801.         $this->enabled = (bool) $enabled;
  1802.         return $this;
  1803.     }
  1804.     /**
  1805.      * Get enabled
  1806.      *
  1807.      * @return boolean
  1808.      */
  1809.     public function getEnabled()
  1810.     {
  1811.         return $this->enabled;
  1812.     }
  1813.     /**
  1814.      * Set weight
  1815.      *
  1816.      * @param float $weight
  1817.      *
  1818.      * @return Product
  1819.      */
  1820.     public function setWeight($weight)
  1821.     {
  1822.         $this->weight Converter::toUnsigned32BitFloat($weight);
  1823.         return $this;
  1824.     }
  1825.     /**
  1826.      * Set useSeparateBox
  1827.      *
  1828.      * @param boolean $useSeparateBox
  1829.      *
  1830.      * @return Product
  1831.      */
  1832.     public function setUseSeparateBox($useSeparateBox)
  1833.     {
  1834.         $this->useSeparateBox $useSeparateBox;
  1835.         return $this;
  1836.     }
  1837.     /**
  1838.      * Get useSeparateBox
  1839.      *
  1840.      * @return boolean
  1841.      */
  1842.     public function getUseSeparateBox()
  1843.     {
  1844.         return $this->useSeparateBox;
  1845.     }
  1846.     /**
  1847.      * Set boxWidth
  1848.      *
  1849.      * @param float $boxWidth
  1850.      *
  1851.      * @return Product
  1852.      */
  1853.     public function setBoxWidth($boxWidth)
  1854.     {
  1855.         $this->boxWidth Converter::toUnsigned32BitFloat($boxWidth);
  1856.         return $this;
  1857.     }
  1858.     /**
  1859.      * Get boxWidth
  1860.      *
  1861.      * @return float
  1862.      */
  1863.     public function getBoxWidth()
  1864.     {
  1865.         return $this->boxWidth;
  1866.     }
  1867.     /**
  1868.      * Set boxLength
  1869.      *
  1870.      * @param float $boxLength
  1871.      *
  1872.      * @return Product
  1873.      */
  1874.     public function setBoxLength($boxLength)
  1875.     {
  1876.         $this->boxLength Converter::toUnsigned32BitFloat($boxLength);
  1877.         return $this;
  1878.     }
  1879.     /**
  1880.      * Get boxLength
  1881.      *
  1882.      * @return float
  1883.      */
  1884.     public function getBoxLength()
  1885.     {
  1886.         return $this->boxLength;
  1887.     }
  1888.     /**
  1889.      * Set boxHeight
  1890.      *
  1891.      * @param float $boxHeight
  1892.      *
  1893.      * @return Product
  1894.      */
  1895.     public function setBoxHeight($boxHeight)
  1896.     {
  1897.         $this->boxHeight Converter::toUnsigned32BitFloat($boxHeight);
  1898.         return $this;
  1899.     }
  1900.     /**
  1901.      * Get boxHeight
  1902.      *
  1903.      * @return float
  1904.      */
  1905.     public function getBoxHeight()
  1906.     {
  1907.         return $this->boxHeight;
  1908.     }
  1909.     /**
  1910.      * Set itemsPerBox
  1911.      *
  1912.      * @param integer $itemsPerBox
  1913.      *
  1914.      * @return Product
  1915.      */
  1916.     public function setItemsPerBox($itemsPerBox)
  1917.     {
  1918.         $this->itemsPerBox Converter::toUnsigned32BitInt($itemsPerBox);
  1919.         return $this;
  1920.     }
  1921.     /**
  1922.      * Get itemsPerBox
  1923.      *
  1924.      * @return integer
  1925.      */
  1926.     public function getItemsPerBox()
  1927.     {
  1928.         return $this->itemsPerBox;
  1929.     }
  1930.     /**
  1931.      * Set free_shipping
  1932.      *
  1933.      * @param boolean $freeShipping
  1934.      *
  1935.      * @return Product
  1936.      */
  1937.     public function setFreeShipping($freeShipping)
  1938.     {
  1939.         $this->free_shipping = (bool) $freeShipping;
  1940.         return $this;
  1941.     }
  1942.     /**
  1943.      * Set taxable
  1944.      *
  1945.      * @param boolean $taxable
  1946.      *
  1947.      * @return Product
  1948.      */
  1949.     public function setTaxable($taxable)
  1950.     {
  1951.         $this->taxable $taxable;
  1952.         return $this;
  1953.     }
  1954.     /**
  1955.      * Get taxable
  1956.      *
  1957.      * @return boolean
  1958.      */
  1959.     public function getTaxable()
  1960.     {
  1961.         return $this->taxable;
  1962.     }
  1963.     /**
  1964.      * Set arrivalDate
  1965.      *
  1966.      * @param integer $arrivalDate
  1967.      *
  1968.      * @return Product
  1969.      */
  1970.     public function setArrivalDate($arrivalDate)
  1971.     {
  1972.         $this->arrivalDate min(MAX_TIMESTAMPmax(0$arrivalDate));
  1973.         return $this;
  1974.     }
  1975.     /**
  1976.      * Get arrivalDate
  1977.      *
  1978.      * @return integer
  1979.      */
  1980.     public function getArrivalDate()
  1981.     {
  1982.         return $this->arrivalDate;
  1983.     }
  1984.     /**
  1985.      * Returns true if product is classified as an upcoming product
  1986.      *
  1987.      * @return boolean
  1988.      */
  1989.     public function isUpcomingProduct()
  1990.     {
  1991.         return $this->getArrivalDate()
  1992.             && $this->getArrivalDate() > Converter::getDayEnd(static::getUserTime());
  1993.     }
  1994.     /**
  1995.      * Check if upcoming product is available
  1996.      *
  1997.      * @return boolean
  1998.      */
  1999.     public function isAllowedUpcomingProduct()
  2000.     {
  2001.         return false;
  2002.     }
  2003.     /**
  2004.      * Set date
  2005.      *
  2006.      * @param integer $date
  2007.      *
  2008.      * @return Product
  2009.      */
  2010.     public function setDate($date)
  2011.     {
  2012.         $this->date $date;
  2013.         return $this;
  2014.     }
  2015.     /**
  2016.      * Get date
  2017.      *
  2018.      * @return integer
  2019.      */
  2020.     public function getDate()
  2021.     {
  2022.         return $this->date;
  2023.     }
  2024.     /**
  2025.      * Set updateDate
  2026.      *
  2027.      * @param integer $updateDate
  2028.      *
  2029.      * @return Product
  2030.      */
  2031.     public function setUpdateDate($updateDate)
  2032.     {
  2033.         $this->updateDate $updateDate;
  2034.         return $this;
  2035.     }
  2036.     /**
  2037.      * Get updateDate
  2038.      *
  2039.      * @return integer
  2040.      */
  2041.     public function getUpdateDate()
  2042.     {
  2043.         return $this->updateDate;
  2044.     }
  2045.     /**
  2046.      * Set needProcess
  2047.      *
  2048.      * @param boolean $needProcess
  2049.      *
  2050.      * @return Product
  2051.      */
  2052.     public function setNeedProcess($needProcess)
  2053.     {
  2054.         $this->needProcess $needProcess;
  2055.         return $this;
  2056.     }
  2057.     /**
  2058.      * Get needProcess
  2059.      *
  2060.      * @return boolean
  2061.      */
  2062.     public function getNeedProcess()
  2063.     {
  2064.         return $this->needProcess;
  2065.     }
  2066.     /**
  2067.      * Set attrSepTab
  2068.      *
  2069.      * @param boolean $attrSepTab
  2070.      *
  2071.      * @return Product
  2072.      */
  2073.     public function setAttrSepTab($attrSepTab)
  2074.     {
  2075.         $this->attrSepTab $attrSepTab;
  2076.         return $this;
  2077.     }
  2078.     /**
  2079.      * Get attrSepTab
  2080.      *
  2081.      * @return boolean
  2082.      */
  2083.     public function getAttrSepTab()
  2084.     {
  2085.         return $this->attrSepTab;
  2086.     }
  2087.     /**
  2088.      * Set metaDescType
  2089.      *
  2090.      * @param string $metaDescType
  2091.      *
  2092.      * @return Product
  2093.      */
  2094.     public function setMetaDescType($metaDescType)
  2095.     {
  2096.         $this->metaDescType $metaDescType;
  2097.         return $this;
  2098.     }
  2099.     /**
  2100.      * Set xcPendingExport
  2101.      *
  2102.      * @param boolean $xcPendingExport
  2103.      *
  2104.      * @return Product
  2105.      */
  2106.     public function setXcPendingExport($xcPendingExport)
  2107.     {
  2108.         $this->xcPendingExport $xcPendingExport;
  2109.         return $this;
  2110.     }
  2111.     /**
  2112.      * Get xcPendingExport
  2113.      *
  2114.      * @return boolean
  2115.      */
  2116.     public function getXcPendingExport()
  2117.     {
  2118.         return $this->xcPendingExport;
  2119.     }
  2120.     /**
  2121.      * Add categoryProducts
  2122.      *
  2123.      * @param \XLite\Model\CategoryProducts $categoryProducts
  2124.      *
  2125.      * @return Product
  2126.      */
  2127.     public function addCategoryProducts(\XLite\Model\CategoryProducts $categoryProducts)
  2128.     {
  2129.         $this->categoryProducts[] = $categoryProducts;
  2130.         return $this;
  2131.     }
  2132.     /**
  2133.      * Get categoryProducts
  2134.      *
  2135.      * @return \Doctrine\Common\Collections\Collection
  2136.      */
  2137.     public function getCategoryProducts()
  2138.     {
  2139.         return $this->categoryProducts;
  2140.     }
  2141.     /**
  2142.      * @param \XLite\Model\Category[] $categories
  2143.      */
  2144.     public function addCategoryProductsLinksByCategories($categories)
  2145.     {
  2146.         foreach ($categories as $category) {
  2147.             if (!$this->hasCategoryProductsLinkByCategory($category)) {
  2148.                 $categoryProduct = new \XLite\Model\CategoryProducts();
  2149.                 $categoryProduct->setProduct($this);
  2150.                 $categoryProduct->setCategory($category);
  2151.                 $this->addCategoryProducts($categoryProduct);
  2152.             }
  2153.         }
  2154.     }
  2155.     /**
  2156.      * @param \XLite\Model\Category $category
  2157.      */
  2158.     public function addCategory($category)
  2159.     {
  2160.         $categoryProduct = new \XLite\Model\CategoryProducts();
  2161.         $categoryProduct->setProduct($this);
  2162.         $categoryProduct->setCategory($category);
  2163.         $this->addCategoryProducts($categoryProduct);
  2164.     }
  2165.     /**
  2166.      * @param \XLite\Model\Category[] $categories
  2167.      */
  2168.     public function removeCategoryProductsLinksByCategories($categories)
  2169.     {
  2170.         $categoryProductsLinks = [];
  2171.         foreach ($categories as $category) {
  2172.             $categoryProductsLink $this->findCategoryProductsLinkByCategory($category);
  2173.             if ($categoryProductsLink) {
  2174.                 $categoryProductsLinks[] = $categoryProductsLink;
  2175.             }
  2176.         }
  2177.         if ($categoryProductsLinks) {
  2178.             \XLite\Core\Database::getRepo('XLite\Model\CategoryProducts')->deleteInBatch(
  2179.                 $categoryProductsLinks
  2180.             );
  2181.         }
  2182.     }
  2183.     /**
  2184.      * @param \XLite\Model\Category[] $categories
  2185.      */
  2186.     public function replaceCategoryProductsLinksByCategories($categories)
  2187.     {
  2188.         $categoriesToAdd = [];
  2189.         foreach ($categories as $category) {
  2190.             if (!$this->hasCategoryProductsLinkByCategory($category)) {
  2191.                 $categoriesToAdd[] = $category;
  2192.             }
  2193.         }
  2194.         $categoriesIds array_map(static function ($item) {
  2195.             /** @var \XLite\Model\Category $item */
  2196.             return (int) $item->getCategoryId();
  2197.         }, $categories);
  2198.         $categoryProductsLinksToDelete = [];
  2199.         foreach ($this->getCategoryProducts() as $categoryProduct) {
  2200.             if (!in_array((int) $categoryProduct->getCategory()->getCategoryId(), $categoriesIdstrue)) {
  2201.                 $categoryProductsLinksToDelete[] = $categoryProduct;
  2202.             }
  2203.         }
  2204.         if ($categoryProductsLinksToDelete) {
  2205.             \XLite\Core\Database::getRepo('XLite\Model\CategoryProducts')->deleteInBatch(
  2206.                 $categoryProductsLinksToDelete
  2207.             );
  2208.         }
  2209.         if ($categoriesToAdd) {
  2210.             $this->addCategoryProductsLinksByCategories($categoriesToAdd);
  2211.         }
  2212.     }
  2213.     /**
  2214.      * @param \XLite\Model\Category $category
  2215.      *
  2216.      * @return bool
  2217.      */
  2218.     public function hasCategoryProductsLinkByCategory($category)
  2219.     {
  2220.         return (bool) $this->findCategoryProductsLinkByCategory($category);
  2221.     }
  2222.     /**
  2223.      * @param \XLite\Model\Category $category
  2224.      *
  2225.      * @return \XLite\Model\CategoryProducts
  2226.      */
  2227.     public function findCategoryProductsLinkByCategory($category)
  2228.     {
  2229.         /** @var \XLite\Model\CategoryProducts $categoryProduct */
  2230.         foreach ($this->getCategoryProducts() as $categoryProduct) {
  2231.             if ((int) $categoryProduct->getCategory()->getCategoryId() === (int) $category->getCategoryId()) {
  2232.                 return $categoryProduct;
  2233.             }
  2234.         }
  2235.         return null;
  2236.     }
  2237.     /**
  2238.      * Add order_items
  2239.      *
  2240.      * @param \XLite\Model\OrderItem $orderItems
  2241.      *
  2242.      * @return Product
  2243.      */
  2244.     public function addOrderItems(\XLite\Model\OrderItem $orderItems)
  2245.     {
  2246.         $this->order_items[] = $orderItems;
  2247.         return $this;
  2248.     }
  2249.     /**
  2250.      * Get order_items
  2251.      *
  2252.      * @return \Doctrine\Common\Collections\Collection
  2253.      */
  2254.     public function getOrderItems()
  2255.     {
  2256.         return $this->order_items;
  2257.     }
  2258.     /**
  2259.      * Add images
  2260.      *
  2261.      * @param \XLite\Model\Image\Product\Image $images
  2262.      *
  2263.      * @return Product
  2264.      */
  2265.     public function addImages(\XLite\Model\Image\Product\Image $images)
  2266.     {
  2267.         $this->images[] = $images;
  2268.         return $this;
  2269.     }
  2270.     /**
  2271.      * Get images
  2272.      *
  2273.      * @return \Doctrine\Common\Collections\Collection
  2274.      */
  2275.     public function getImages()
  2276.     {
  2277.         return $this->images;
  2278.     }
  2279.     /**
  2280.      * Get productClass
  2281.      *
  2282.      * @return \XLite\Model\ProductClass
  2283.      */
  2284.     public function getProductClass()
  2285.     {
  2286.         return $this->productClass;
  2287.     }
  2288.     /**
  2289.      * Set taxClass
  2290.      *
  2291.      * @param \XLite\Model\TaxClass $taxClass
  2292.      *
  2293.      * @return Product
  2294.      */
  2295.     public function setTaxClass(\XLite\Model\TaxClass $taxClass null)
  2296.     {
  2297.         $this->taxClass $taxClass;
  2298.         return $this;
  2299.     }
  2300.     /**
  2301.      * Get taxClass
  2302.      *
  2303.      * @return \XLite\Model\TaxClass
  2304.      */
  2305.     public function getTaxClass()
  2306.     {
  2307.         return $this->taxClass;
  2308.     }
  2309.     /**
  2310.      * Add attributes
  2311.      *
  2312.      * @param \XLite\Model\Attribute $attributes
  2313.      *
  2314.      * @return Product
  2315.      */
  2316.     public function addAttributes(\XLite\Model\Attribute $attributes)
  2317.     {
  2318.         $this->attributes[] = $attributes;
  2319.         return $this;
  2320.     }
  2321.     /**
  2322.      * Get attributes
  2323.      *
  2324.      * @return \Doctrine\Common\Collections\Collection<int, \XLite\Model\Attribute>
  2325.      */
  2326.     public function getAttributes()
  2327.     {
  2328.         return $this->attributes;
  2329.     }
  2330.     /**
  2331.      * Add attributeValueC
  2332.      *
  2333.      * @param \XLite\Model\AttributeValue\AttributeValueCheckbox $attributeValueC
  2334.      *
  2335.      * @return Product
  2336.      */
  2337.     public function addAttributeValueC(\XLite\Model\AttributeValue\AttributeValueCheckbox $attributeValueC)
  2338.     {
  2339.         $this->attributeValueC[] = $attributeValueC;
  2340.         return $this;
  2341.     }
  2342.     /**
  2343.      * Get attributeValueC
  2344.      *
  2345.      * @return \Doctrine\Common\Collections\Collection
  2346.      */
  2347.     public function getAttributeValueC()
  2348.     {
  2349.         return $this->attributeValueC;
  2350.     }
  2351.     /**
  2352.      * Add attributeValueT
  2353.      *
  2354.      * @param \XLite\Model\AttributeValue\AttributeValueText $attributeValueT
  2355.      *
  2356.      * @return Product
  2357.      */
  2358.     public function addAttributeValueT(\XLite\Model\AttributeValue\AttributeValueText $attributeValueT)
  2359.     {
  2360.         $this->attributeValueT[] = $attributeValueT;
  2361.         return $this;
  2362.     }
  2363.     /**
  2364.      * Get attributeValueT
  2365.      *
  2366.      * @return \Doctrine\Common\Collections\Collection
  2367.      */
  2368.     public function getAttributeValueT()
  2369.     {
  2370.         return $this->attributeValueT;
  2371.     }
  2372.     /**
  2373.      * Add attributeValueS
  2374.      *
  2375.      * @param \XLite\Model\AttributeValue\AttributeValueSelect $attributeValueS
  2376.      *
  2377.      * @return Product
  2378.      */
  2379.     public function addAttributeValueS(\XLite\Model\AttributeValue\AttributeValueSelect $attributeValueS)
  2380.     {
  2381.         $this->attributeValueS[] = $attributeValueS;
  2382.         return $this;
  2383.     }
  2384.     /**
  2385.      * Get attributeValueS
  2386.      *
  2387.      * @return \Doctrine\Common\Collections\Collection
  2388.      */
  2389.     public function getAttributeValueS()
  2390.     {
  2391.         return $this->attributeValueS;
  2392.     }
  2393.     /**
  2394.      * Add attributeValueH
  2395.      *
  2396.      * @param \XLite\Model\AttributeValue\AttributeValueHidden $attributeValueH
  2397.      *
  2398.      * @return Product
  2399.      */
  2400.     public function addAttributeValueH(\XLite\Model\AttributeValue\AttributeValueHidden $attributeValueH)
  2401.     {
  2402.         $this->attributeValueH[] = $attributeValueH;
  2403.         return $this;
  2404.     }
  2405.     /**
  2406.      * Get attributeValueH
  2407.      *
  2408.      * @return \Doctrine\Common\Collections\Collection
  2409.      */
  2410.     public function getAttributeValueH()
  2411.     {
  2412.         return $this->attributeValueH;
  2413.     }
  2414.     /**
  2415.      * Add quickData
  2416.      *
  2417.      * @param \XLite\Model\QuickData $quickData
  2418.      *
  2419.      * @return Product
  2420.      */
  2421.     public function addQuickData(\XLite\Model\QuickData $quickData)
  2422.     {
  2423.         $this->quickData[] = $quickData;
  2424.         return $this;
  2425.     }
  2426.     /**
  2427.      * Get quickData
  2428.      *
  2429.      * @return \Doctrine\Common\Collections\Collection
  2430.      */
  2431.     public function getQuickData()
  2432.     {
  2433.         return $this->quickData;
  2434.     }
  2435.     /**
  2436.      * Add memberships
  2437.      *
  2438.      * @param \XLite\Model\Membership $memberships
  2439.      *
  2440.      * @return Product
  2441.      */
  2442.     public function addMemberships(\XLite\Model\Membership $memberships)
  2443.     {
  2444.         $this->memberships[] = $memberships;
  2445.         return $this;
  2446.     }
  2447.     /**
  2448.      * Get memberships
  2449.      *
  2450.      * @return \Doctrine\Common\Collections\Collection
  2451.      */
  2452.     public function getMemberships()
  2453.     {
  2454.         return $this->memberships;
  2455.     }
  2456.     /**
  2457.      * @param \XLite\Model\Membership[] $memberships
  2458.      */
  2459.     public function addMembershipsByMemberships($memberships)
  2460.     {
  2461.         foreach ($memberships as $membership) {
  2462.             if (!$this->hasMembershipByMembership($membership)) {
  2463.                 $this->addMemberships($membership);
  2464.             }
  2465.         }
  2466.     }
  2467.     /**
  2468.      * @param \XLite\Model\Membership[] $memberships
  2469.      */
  2470.     public function removeMembershipsByMemberships($memberships)
  2471.     {
  2472.         foreach ($memberships as $membership) {
  2473.             if ($this->hasMembershipByMembership($membership)) {
  2474.                 $this->getMemberships()->removeElement($membership);
  2475.             }
  2476.         }
  2477.     }
  2478.     /**
  2479.      * @param \XLite\Model\Membership[] $memberships
  2480.      */
  2481.     public function replaceMembershipsByMemberships($memberships)
  2482.     {
  2483.         $ids array_map(static function ($item) {
  2484.             /** @var \XLite\Model\Membership $item */
  2485.             return (int) $item->getMembershipId();
  2486.         }, $memberships);
  2487.         $toRemove = [];
  2488.         foreach ($this->getMemberships() as $membership) {
  2489.             if (!in_array((int) $membership->getMembershipId(), $idstrue)) {
  2490.                 $toRemove[] = $membership;
  2491.             }
  2492.         }
  2493.         $this->addMembershipsByMemberships($memberships);
  2494.         $this->removeMembershipsByMemberships($toRemove);
  2495.     }
  2496.     /**
  2497.      * @param \XLite\Model\Membership $membership
  2498.      *
  2499.      * @return boolean
  2500.      */
  2501.     public function hasMembershipByMembership($membership)
  2502.     {
  2503.         return (bool) $this->getMembershipByMembership($membership);
  2504.     }
  2505.     /**
  2506.      * @param \XLite\Model\Membership $membership
  2507.      *
  2508.      * @return mixed|null
  2509.      */
  2510.     public function getMembershipByMembership($membership)
  2511.     {
  2512.         foreach ($this->getMemberships() as $membershipObject) {
  2513.             if (
  2514.                 (
  2515.                     $membership->isPersistent()
  2516.                     && (int) $membership->getMembershipId() === (int) $membershipObject->getMembershipId()
  2517.                 )
  2518.                 || $membership === $membershipObject
  2519.             ) {
  2520.                 return $membershipObject;
  2521.             }
  2522.         }
  2523.         return null;
  2524.     }
  2525.     /**
  2526.      * Add cleanURLs
  2527.      *
  2528.      * @param \XLite\Model\CleanURL $cleanURLs
  2529.      *
  2530.      * @return Product
  2531.      */
  2532.     public function addCleanURLs(\XLite\Model\CleanURL $cleanURLs)
  2533.     {
  2534.         $this->cleanURLs[] = $cleanURLs;
  2535.         return $this;
  2536.     }
  2537.     /**
  2538.      * Get cleanURLs
  2539.      *
  2540.      * @return \Doctrine\Common\Collections\Collection
  2541.      */
  2542.     public function getCleanURLs()
  2543.     {
  2544.         return $this->cleanURLs;
  2545.     }
  2546.     /**
  2547.      * Set inventoryEnabled
  2548.      *
  2549.      * @param boolean $inventoryEnabled
  2550.      *
  2551.      * @return Product
  2552.      */
  2553.     public function setInventoryEnabled($inventoryEnabled)
  2554.     {
  2555.         $this->inventoryEnabled $inventoryEnabled;
  2556.         return $this;
  2557.     }
  2558.     /**
  2559.      * Get inventoryEnabled
  2560.      *
  2561.      * @return boolean
  2562.      */
  2563.     public function getInventoryEnabled()
  2564.     {
  2565.         return $this->inventoryEnabled;
  2566.     }
  2567.     /**
  2568.      * Get amount
  2569.      *
  2570.      * @return integer
  2571.      */
  2572.     public function getAmount()
  2573.     {
  2574.         return $this->amount;
  2575.     }
  2576.     /**
  2577.      * Set lowLimitEnabledCustomer
  2578.      *
  2579.      * @param boolean $lowLimitEnabledCustomer
  2580.      *
  2581.      * @return Product
  2582.      */
  2583.     public function setLowLimitEnabledCustomer($lowLimitEnabledCustomer)
  2584.     {
  2585.         $this->lowLimitEnabledCustomer $lowLimitEnabledCustomer;
  2586.         return $this;
  2587.     }
  2588.     /**
  2589.      * Get lowLimitEnabledCustomer
  2590.      *
  2591.      * @return boolean
  2592.      */
  2593.     public function getLowLimitEnabledCustomer()
  2594.     {
  2595.         return $this->lowLimitEnabledCustomer;
  2596.     }
  2597.     /**
  2598.      * Set lowLimitEnabled
  2599.      *
  2600.      * @param boolean $lowLimitEnabled
  2601.      *
  2602.      * @return Product
  2603.      */
  2604.     public function setLowLimitEnabled($lowLimitEnabled)
  2605.     {
  2606.         $this->lowLimitEnabled $lowLimitEnabled;
  2607.         return $this;
  2608.     }
  2609.     /**
  2610.      * Get lowLimitEnabled
  2611.      *
  2612.      * @return boolean
  2613.      */
  2614.     public function getLowLimitEnabled()
  2615.     {
  2616.         return $this->lowLimitEnabled;
  2617.     }
  2618.     /**
  2619.      * Get lowLimitAmount
  2620.      *
  2621.      * @return integer
  2622.      */
  2623.     public function getLowLimitAmount()
  2624.     {
  2625.         return $this->lowLimitAmount;
  2626.     }
  2627.     /**
  2628.      * @return integer
  2629.      */
  2630.     protected function getAvailableAmountForTooltipText()
  2631.     {
  2632.         return $this->getAvailableAmount();
  2633.     }
  2634.     /**
  2635.      * Get help text for "all stock in cart" product
  2636.      *
  2637.      * @return string
  2638.      */
  2639.     public function getAllStockInCartTooltipText()
  2640.     {
  2641.         $text '';
  2642.         if ($this->getInventoryEnabled()) {
  2643.             $text = static::t(
  2644.                 'Sorry, we only have {{qty}} of that item available',
  2645.                 ['qty' => $this->getAvailableAmountForTooltipText()]
  2646.             );
  2647.         }
  2648.         return $text;
  2649.     }
  2650.     // {{{ Translation Getters / setters
  2651.     /**
  2652.      * @return string
  2653.      */
  2654.     public function getDescription()
  2655.     {
  2656.         return $this->getTranslationField(__FUNCTION__);
  2657.     }
  2658.     /**
  2659.      * @param string $description
  2660.      *
  2661.      * @return \XLite\Model\Base\Translation
  2662.      */
  2663.     public function setDescription($description)
  2664.     {
  2665.         return $this->setTranslationField(__FUNCTION__$description);
  2666.     }
  2667.     /**
  2668.      * @return string
  2669.      */
  2670.     public function getBriefDescription()
  2671.     {
  2672.         return $this->getTranslationField(__FUNCTION__);
  2673.     }
  2674.     /**
  2675.      * @param string $briefDescription
  2676.      *
  2677.      * @return \XLite\Model\Base\Translation
  2678.      */
  2679.     public function setBriefDescription($briefDescription)
  2680.     {
  2681.         return $this->setTranslationField(__FUNCTION__$briefDescription);
  2682.     }
  2683.     /**
  2684.      * @return string
  2685.      */
  2686.     public function getMetaTags()
  2687.     {
  2688.         return $this->getTranslationField(__FUNCTION__);
  2689.     }
  2690.     /**
  2691.      * @param string $metaTags
  2692.      *
  2693.      * @return \XLite\Model\Base\Translation
  2694.      */
  2695.     public function setMetaTags($metaTags)
  2696.     {
  2697.         return $this->setTranslationField(__FUNCTION__$metaTags);
  2698.     }
  2699.     /**
  2700.      * @param string $metaDesc
  2701.      *
  2702.      * @return \XLite\Model\Base\Translation
  2703.      */
  2704.     public function setMetaDesc($metaDesc)
  2705.     {
  2706.         return $this->setTranslationField(__FUNCTION__$metaDesc);
  2707.     }
  2708.     /**
  2709.      * @return string
  2710.      */
  2711.     public function getMetaTitle()
  2712.     {
  2713.         return $this->getTranslationField(__FUNCTION__);
  2714.     }
  2715.     /**
  2716.      * @param string $metaTitle
  2717.      *
  2718.      * @return \XLite\Model\Base\Translation
  2719.      */
  2720.     public function setMetaTitle($metaTitle)
  2721.     {
  2722.         return $this->setTranslationField(__FUNCTION__$metaTitle);
  2723.     }
  2724.     // }}}
  2725.     public function isDemoProduct(): bool
  2726.     {
  2727.         return $this->isDemoProduct;
  2728.     }
  2729.     public function setIsDemoProduct(bool $isDemoProduct): self
  2730.     {
  2731.         $this->isDemoProduct $isDemoProduct;
  2732.         return $this;
  2733.     }
  2734. }