classes/XLite/Model/Product.php line 70

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