classes/XLite/Model/Product.php line 67

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