classes/XLite/Model/Category.php line 130

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 XLite\API\Endpoint\Category\DTO\CategoryMoveInput;
  10. use XLite\API\Endpoint\Category\DTO\CategoryProductInput;
  11. use XLite\API\Endpoint\Category\DTO\CategoryProductOutput;
  12. use XLite\API\Endpoint\Category\DTO\CategoryStatsOutput;
  13. use XLite\API\Endpoint\Category\DTO\CategoryInput;
  14. use XLite\API\Endpoint\Category\DTO\CategoryOutput;
  15. use XLite\API\Endpoint\Category\Filter\ParentFilter;
  16. use XLite\API\Filter\TranslationAwareOrderFilter;
  17. use XLite\Controller\API\Category\DeleteCategoryProduct;
  18. use XLite\Core\Cache\ExecuteCached;
  19. use XLite\Core\Database;
  20. use XLite\Model\Image\Category\Image;
  21. /**
  22.  * Category
  23.  *
  24.  * @ORM\Entity
  25.  * @ORM\Table  (name="categories",
  26.  *      indexes={
  27.  *          @ORM\Index (name="lpos", columns={"lpos"}),
  28.  *          @ORM\Index (name="rpos", columns={"rpos"}),
  29.  *          @ORM\Index (name="enabled", columns={"enabled"})
  30.  *      }
  31.  * )
  32.  * @ApiPlatform\ApiResource(
  33.  *     input=CategoryInput::class,
  34.  *     output=CategoryOutput::class,
  35.  *     itemOperations={
  36.  *         "get"={
  37.  *             "method"="GET",
  38.  *             "path"="/categories/{category_id}",
  39.  *             "identifiers"={"category_id"},
  40.  *         },
  41.  *         "put"={
  42.  *             "method"="PUT",
  43.  *             "path"="/categories/{category_id}",
  44.  *             "identifiers"={"category_id"},
  45.  *         },
  46.  *         "delete"={
  47.  *             "method"="DELETE",
  48.  *             "path"="/categories/{category_id}",
  49.  *             "identifiers"={"category_id"},
  50.  *         },
  51.  *         "move"={
  52.  *             "method"="PUT",
  53.  *             "input"=CategoryMoveInput::class,
  54.  *             "path"="/categories/{category_id}/move",
  55.  *             "identifiers"={"category_id"},
  56.  *             "openapi_context"={
  57.  *                  "summary"="Update a category position in the categories tree",
  58.  *                  "parameters"={
  59.  *                      {"name"="category_id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  60.  *                  }
  61.  *             },
  62.  *         },
  63.  *         "stats"={
  64.  *             "method"="GET",
  65.  *             "output"=CategoryStatsOutput::class,
  66.  *             "path"="/categories/{category_id}/stats",
  67.  *             "identifiers"={"category_id"},
  68.  *             "openapi_context"={
  69.  *                  "summary"="Retrieve category statistics",
  70.  *                  "parameters"={
  71.  *                      {"name"="category_id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  72.  *                  }
  73.  *             },
  74.  *         },
  75.  *         "delete_category_product"={
  76.  *             "method"="DELETE",
  77.  *             "path"="/categories/{category_id}/products/{product_id}",
  78.  *             "identifiers"={"category_id"},
  79.  *             "requirements"={"category_id"="\d+", "product_id"="\d+"},
  80.  *             "controller"=DeleteCategoryProduct::class,
  81.  *             "read"=false,
  82.  *             "openapi_context"={
  83.  *                  "summary"="Delete a product from a category",
  84.  *                  "parameters"={
  85.  *                      {"name"="category_id", "in"="path", "required"=true, "schema"={"type"="integer"}},
  86.  *                      {"name"="product_id", "in"="path", "required"=true, "schema"={"type"="integer"}},
  87.  *                  }
  88.  *             },
  89.  *         },
  90.  *     },
  91.  *     collectionOperations={
  92.  *         "get"={
  93.  *             "method"="GET",
  94.  *             "path"="/categories",
  95.  *             "identifiers"={"category_id"},
  96.  *         },
  97.  *         "post"={
  98.  *             "method"="POST",
  99.  *             "path"="/categories",
  100.  *             "identifiers"={"category_id"},
  101.  *         },
  102.  *         "add_category_product"={
  103.  *             "method"="POST",
  104.  *             "input"=CategoryProductInput::class,
  105.  *             "path"="/categories/{category_id}/products",
  106.  *             "identifiers"={"category_id"},
  107.  *             "requirements"={"category_id"="\d+"},
  108.  *             "openapi_context"={
  109.  *                 "summary"="Add a product to a category",
  110.  *                 "parameters"={
  111.  *                     {"name"="category_id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  112.  *                 }
  113.  *             },
  114.  *         },
  115.  *         "get_category_products"={
  116.  *             "method"="GET",
  117.  *             "path"="/categories/{category_id}/products",
  118.  *             "identifiers"={"category_id"},
  119.  *             "output"=CategoryProductOutput::class,
  120.  *             "requirements"={"category_id"="\d+"},
  121.  *             "openapi_context"={
  122.  *                 "summary"="Retrieve a list of products from a category",
  123.  *                 "parameters"={
  124.  *                     {"name"="category_id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  125.  *                 }
  126.  *             },
  127.  *         },
  128.  *     }
  129.  * )
  130.  * @ApiPlatform\ApiFilter(ParentFilter::class, properties={"parent"})
  131.  * @ApiPlatform\ApiFilter(TranslationAwareOrderFilter::class, properties={"position"="ASC"})
  132.  */
  133. class Category extends \XLite\Model\Base\Catalog
  134. {
  135.     /**
  136.      * Node unique ID
  137.      *
  138.      * @var integer
  139.      *
  140.      * @ORM\Id
  141.      * @ORM\GeneratedValue (strategy="AUTO")
  142.      * @ORM\Column         (type="integer", options={ "unsigned": true })
  143.      */
  144.     protected $category_id;
  145.     /**
  146.      * Node left value
  147.      *
  148.      * @var integer
  149.      *
  150.      * @ORM\Column (type="integer")
  151.      */
  152.     protected $lpos;
  153.     /**
  154.      * Node right value
  155.      *
  156.      * @var integer
  157.      *
  158.      * @ORM\Column (type="integer")
  159.      */
  160.     protected $rpos;
  161.     /**
  162.      * Node status
  163.      *
  164.      * @var boolean
  165.      *
  166.      * @ORM\Column (type="boolean")
  167.      */
  168.     protected $enabled true;
  169.     /**
  170.      * Whether to display the category title, or not
  171.      *
  172.      * @var boolean
  173.      *
  174.      * @ORM\Column (type="boolean")
  175.      */
  176.     protected $show_title true;
  177.     /**
  178.      * Category "depth" in the tree
  179.      *
  180.      * @var integer
  181.      *
  182.      * @ORM\Column (type="integer")
  183.      */
  184.     protected $depth = -1;
  185.     /**
  186.      * Category position parameter. Sort inside the parent category
  187.      *
  188.      * @var integer
  189.      *
  190.      * @ORM\Column (type="integer")
  191.      */
  192.     protected $pos 0;
  193.     /**
  194.      * Whether to display the category title, or not
  195.      *
  196.      * @var string
  197.      *
  198.      * @ORM\Column (type="string", length=32, nullable=true)
  199.      */
  200.     protected $root_category_look;
  201.     /**
  202.      * Some cached flags
  203.      *
  204.      * @var \XLite\Model\Category\QuickFlags
  205.      *
  206.      * @ORM\OneToOne (targetEntity="XLite\Model\Category\QuickFlags", mappedBy="category", cascade={"all"})
  207.      */
  208.     protected $quickFlags;
  209.     /**
  210.      * Memberships
  211.      *
  212.      * @var \Doctrine\Common\Collections\ArrayCollection
  213.      *
  214.      * @ORM\ManyToMany (targetEntity="XLite\Model\Membership", inversedBy="categories")
  215.      * @ORM\JoinTable (name="category_membership_links",
  216.      *      joinColumns={@ORM\JoinColumn (name="category_id", referencedColumnName="category_id", onDelete="CASCADE")},
  217.      *      inverseJoinColumns={@ORM\JoinColumn (name="membership_id", referencedColumnName="membership_id", onDelete="CASCADE")}
  218.      * )
  219.      */
  220.     protected $memberships;
  221.     /**
  222.      * One-to-one relation with category_images table
  223.      *
  224.      * @var Image
  225.      *
  226.      * @ORM\OneToOne  (targetEntity="XLite\Model\Image\Category\Image", mappedBy="category", cascade={"all"})
  227.      */
  228.     protected $image;
  229.     /**
  230.      * One-to-one relation with category_images table
  231.      *
  232.      * @var \XLite\Model\Image\Category\Banner
  233.      *
  234.      * @ORM\OneToOne  (targetEntity="XLite\Model\Image\Category\Banner", mappedBy="category", cascade={"all"})
  235.      */
  236.     protected $banner;
  237.     /**
  238.      * Relation to a CategoryProducts entities
  239.      *
  240.      * @var \Doctrine\Common\Collections\ArrayCollection
  241.      *
  242.      * @ORM\OneToMany (targetEntity="XLite\Model\CategoryProducts", mappedBy="category", cascade={"all"})
  243.      * @ORM\OrderBy   ({"orderby" = "ASC"})
  244.      */
  245.     protected $categoryProducts;
  246.     /**
  247.      * Child categories
  248.      *
  249.      * @var \Doctrine\Common\Collections\ArrayCollection
  250.      *
  251.      * @ORM\OneToMany (targetEntity="XLite\Model\Category", mappedBy="parent", cascade={"all"})
  252.      * @ORM\OrderBy({"pos" = "ASC","category_id"="ASC","lpos" = "ASC"})
  253.      */
  254.     protected $children;
  255.     /**
  256.      * Parent category
  257.      *
  258.      * @var \XLite\Model\Category
  259.      *
  260.      * @ORM\ManyToOne  (targetEntity="XLite\Model\Category", inversedBy="children")
  261.      * @ORM\JoinColumn (name="parent_id", referencedColumnName="category_id", onDelete="SET NULL")
  262.      */
  263.     protected $parent;
  264.     /**
  265.      * Caching flag to check if the category is visible in the parents branch.
  266.      *
  267.      * @var boolean
  268.      */
  269.     protected $flagVisible;
  270.     /**
  271.      * Clean URLs
  272.      *
  273.      * @var \Doctrine\Common\Collections\Collection
  274.      *
  275.      * @ORM\OneToMany (targetEntity="XLite\Model\CleanURL", mappedBy="category", cascade={"all"})
  276.      * @ORM\OrderBy   ({"id" = "ASC"})
  277.      */
  278.     protected $cleanURLs;
  279.     /**
  280.      * Meta description type
  281.      *
  282.      * @var string
  283.      *
  284.      * @ORM\Column (type="string", length=1)
  285.      */
  286.     protected $metaDescType 'A';
  287.     /**
  288.      * Flag to exporting entities
  289.      *
  290.      * @var boolean
  291.      *
  292.      * @ORM\Column (type="boolean")
  293.      */
  294.     protected $xcPendingExport false;
  295.     /**
  296.      * @var \Doctrine\Common\Collections\Collection
  297.      *
  298.      * @ORM\OneToMany (targetEntity="XLite\Model\CategoryTranslation", mappedBy="owner", cascade={"all"})
  299.      */
  300.     protected $translations;
  301.     /**
  302.      * Get object unique id
  303.      *
  304.      * @return integer
  305.      */
  306.     public function getId()
  307.     {
  308.         return $this->getCategoryId();
  309.     }
  310.     /**
  311.      * Set parent
  312.      *
  313.      * @param \XLite\Model\Category $parent Parent category OPTIONAL
  314.      *
  315.      * @return void
  316.      */
  317.     public function setParent(\XLite\Model\Category $parent null)
  318.     {
  319.         $this->parent $parent;
  320.     }
  321.     /**
  322.      * Set image
  323.      *
  324.      * @param Image $image Image OPTIONAL
  325.      *
  326.      * @return void
  327.      */
  328.     public function setImage(Image $image null)
  329.     {
  330.         $this->image $image;
  331.     }
  332.     /**
  333.      * Check if category has image
  334.      *
  335.      * @return boolean
  336.      */
  337.     public function hasImage()
  338.     {
  339.         return $this->getImage() !== null;
  340.     }
  341.     /**
  342.      * Set banner image
  343.      *
  344.      * @param \XLite\Model\Image\Category\Banner $image Image OPTIONAL
  345.      *
  346.      * @return void
  347.      */
  348.     public function setBanner(\XLite\Model\Image\Category\Banner $image null)
  349.     {
  350.         $this->banner $image;
  351.     }
  352.     /**
  353.      * Check if category has image
  354.      *
  355.      * @return boolean
  356.      */
  357.     public function hasBanner()
  358.     {
  359.         return $this->getBanner() !== null;
  360.     }
  361.     /**
  362.      * Check every parent of category to be enabled.
  363.      *
  364.      * @return boolean
  365.      */
  366.     public function isVisible()
  367.     {
  368.         if ($this->flagVisible === null) {
  369.             $current $this;
  370.             $hidden false;
  371.             $rootCategoryId Database::getRepo('XLite\Model\Category')->getRootCategoryId();
  372.             while ($rootCategoryId != $current->getCategoryId()) {
  373.                 if (!$this->checkStorefrontVisibility($current)) {
  374.                     $hidden true;
  375.                     break;
  376.                 }
  377.                 $current $current->getParent();
  378.             }
  379.             $this->flagVisible = !$hidden;
  380.         }
  381.         return $this->flagVisible;
  382.     }
  383.     /**
  384.      * @return bool
  385.      */
  386.     public function isRootCategory()
  387.     {
  388.         return $this->getCategoryId() == Database::getRepo('XLite\Model\Category')->getRootCategoryId();
  389.     }
  390.     /**
  391.      * Check if the category is visible on the storefront for the current customer
  392.      *
  393.      * @param \XLite\Model\Category $current Current category
  394.      *
  395.      * @return boolean
  396.      */
  397.     protected function checkStorefrontVisibility($current)
  398.     {
  399.         return $current->getEnabled()
  400.             && (
  401.                 $current->getMemberships()->count() === 0
  402.                 || in_array(\XLite\Core\Auth::getInstance()->getMembershipId(), $current->getMembershipIds())
  403.             );
  404.     }
  405.     /**
  406.      * Get the number of subcategories
  407.      *
  408.      * @return integer
  409.      */
  410.     public function getSubcategoriesCount()
  411.     {
  412.         $result 0;
  413.         $enabledCondition $this->getRepository()->getEnabledCondition();
  414.         $quickFlags $this->getQuickFlags();
  415.         if ($quickFlags) {
  416.             $result $enabledCondition
  417.                 $quickFlags->getSubcategoriesCountEnabled()
  418.                 : $quickFlags->getSubcategoriesCountAll();
  419.         }
  420.         return $result;
  421.     }
  422.     /**
  423.      * Check if category has subcategories
  424.      *
  425.      * @return boolean
  426.      */
  427.     public function hasSubcategories()
  428.     {
  429.         return $this->getSubcategoriesCount();
  430.     }
  431.     /**
  432.      * Return subcategories list
  433.      *
  434.      * @return \Doctrine\Common\Collections\Collection
  435.      */
  436.     public function getSubcategories()
  437.     {
  438.         return $this->getChildren()->filter(
  439.             static function (\XLite\Model\Category $category) {
  440.                 return $category->getEnabled();
  441.             }
  442.         );
  443.     }
  444.     /**
  445.      * Return siblings list.
  446.      * You are able to include itself into this list. (Customer area)
  447.      *
  448.      * @param boolean $hasSelf Flag to include itself
  449.      *
  450.      * @return array
  451.      */
  452.     public function getSiblings($hasSelf false)
  453.     {
  454.         return $this->getRepository()->getSiblings($this$hasSelf);
  455.     }
  456.     /**
  457.      * Return siblings list.
  458.      * You are able to include itself into this list. (Customer area)
  459.      *
  460.      * @param integer $maxResults   Max results
  461.      * @param boolean $hasSelf      Flag to include itself
  462.      *
  463.      * @return array
  464.      */
  465.     public function getSiblingsFramed($maxResults$hasSelf false)
  466.     {
  467.         return $this->getRepository()->getSiblingsFramed($this$maxResults$hasSelf);
  468.     }
  469.     /**
  470.      * Get category path
  471.      *
  472.      * @return Category[]
  473.      */
  474.     public function getPath()
  475.     {
  476.         return $this->getRepository()->getCategoryPath($this->getCategoryId());
  477.     }
  478.     /**
  479.      * Gets full path to the category as a string: <parent category>/.../<category name>
  480.      *
  481.      * @return string
  482.      */
  483.     public function getStringPath()
  484.     {
  485.         $path = [];
  486.         foreach ($this->getPath() as $category) {
  487.             $path[] = $category->getName();
  488.         }
  489.         return implode('/'$path);
  490.     }
  491.     /**
  492.      * Return parent category ID
  493.      *
  494.      * @return integer
  495.      */
  496.     public function getParentId()
  497.     {
  498.         return $this->getParent() ? $this->getParent()->getCategoryId() : 0;
  499.     }
  500.     /**
  501.      * Set parent category ID
  502.      *
  503.      * @param integer $parentID Value to set
  504.      *
  505.      * @return void
  506.      */
  507.     public function setParentId($parentID)
  508.     {
  509.         $this->parent $this->getRepository()->find($parentID);
  510.     }
  511.     /**
  512.      * Get membership Ids
  513.      *
  514.      * @return array
  515.      */
  516.     public function getMembershipIds()
  517.     {
  518.         $result = [];
  519.         foreach ($this->getMemberships() as $membership) {
  520.             $result[] = $membership->getMembershipId();
  521.         }
  522.         return $result;
  523.     }
  524.     /**
  525.      * Flag if the category and active profile have the same memberships. (when category is displayed or hidden)
  526.      *
  527.      * @return boolean
  528.      */
  529.     public function hasAvailableMembership()
  530.     {
  531.         return $this->getMemberships()->count() === 0
  532.             || in_array(\XLite\Core\Auth::getInstance()->getMembershipId(), $this->getMembershipIds());
  533.     }
  534.     /**
  535.      * Return number of products associated with the category
  536.      *
  537.      * @return integer
  538.      */
  539.     public function getProductsCount()
  540.     {
  541.         return $this->getProducts(nulltrue);
  542.     }
  543.     /**
  544.      * Return products list
  545.      *
  546.      * @param \XLite\Core\CommonCell $cnd       Search condition OPTIONAL
  547.      * @param boolean                $countOnly Return items list or only its size OPTIONAL
  548.      *
  549.      * @return array|integer
  550.      */
  551.     public function getProducts(\XLite\Core\CommonCell $cnd null$countOnly false)
  552.     {
  553.         if ($cnd === null) {
  554.             $cnd = new \XLite\Core\CommonCell();
  555.         }
  556.         // Main condition for this search
  557.         $cnd->{\XLite\Model\Repo\Product::P_CATEGORY_ID} = $this->getCategoryId();
  558.         if (
  559.             \XLite\Core\Config::getInstance()->General->show_out_of_stock_products !== 'directLink'
  560.             && !'searchOnly' !== \XLite\Core\Config::getInstance()->General->show_out_of_stock_products
  561.             && !\XLite::isAdminZone()
  562.         ) {
  563.             $cnd->{\XLite\Model\Repo\Product::P_INVENTORY} = false;
  564.         }
  565.         return Database::getRepo('XLite\Model\Product')->search($cnd$countOnly);
  566.     }
  567.     /**
  568.      * Check if product present in category
  569.      *
  570.      * @param Product|integer $product Product
  571.      *
  572.      * @return boolean
  573.      */
  574.     public function hasProduct($product)
  575.     {
  576.         return $this->getRepository()->hasProduct($this$product);
  577.     }
  578.     /**
  579.      * Return category description
  580.      *
  581.      * @return string
  582.      */
  583.     public function getViewDescription()
  584.     {
  585.         return static::getPreprocessedValue($this->getDescription())
  586.             ?: $this->getDescription();
  587.     }
  588.     /**
  589.      * Get position
  590.      *
  591.      * @return integer
  592.      */
  593.     public function getPosition()
  594.     {
  595.         return $this->getPos();
  596.     }
  597.     /**
  598.      * Set position
  599.      *
  600.      * @param integer $position Product position
  601.      *
  602.      * @return self
  603.      */
  604.     public function setPosition($position)
  605.     {
  606.         return $this->setPos($position);
  607.     }
  608.     /**
  609.      * Returns meta description
  610.      *
  611.      * @return string
  612.      */
  613.     public function getMetaDesc()
  614.     {
  615.         return $this->getMetaDescType() === 'A'
  616.             ? static::postprocessMetaDescription($this->getDescription())
  617.             : $this->getSoftTranslation()->getMetaDesc();
  618.     }
  619.     /**
  620.      * Returns meta description type
  621.      *
  622.      * @return string
  623.      */
  624.     public function getMetaDescType()
  625.     {
  626.         $result $this->metaDescType;
  627.         if (!$result) {
  628.             $metaDescPresent array_reduce($this->getTranslations()->toArray(), static function ($carry$item) {
  629.                 return $carry ?: (bool) $item->getMetaDesc();
  630.             }, false);
  631.             $result $metaDescPresent 'C' 'A';
  632.         }
  633.         return $result;
  634.     }
  635.     /**
  636.      * Constructor
  637.      *
  638.      * @param array $data Entity properties OPTIONAL
  639.      */
  640.     public function __construct(array $data = [])
  641.     {
  642.         $this->categoryProducts = new \Doctrine\Common\Collections\ArrayCollection();
  643.         $this->children         = new \Doctrine\Common\Collections\ArrayCollection();
  644.         $this->memberships      = new \Doctrine\Common\Collections\ArrayCollection();
  645.         parent::__construct($data);
  646.     }
  647.     /**
  648.      * Get category_id
  649.      *
  650.      * @return integer
  651.      */
  652.     public function getCategoryId()
  653.     {
  654.         return (int) $this->category_id;
  655.     }
  656.     /**
  657.      * Set lpos
  658.      *
  659.      * @param integer $lpos
  660.      * @return Category
  661.      */
  662.     public function setLpos($lpos)
  663.     {
  664.         $this->lpos $lpos;
  665.         return $this;
  666.     }
  667.     /**
  668.      * Get lpos
  669.      *
  670.      * @return integer
  671.      */
  672.     public function getLpos()
  673.     {
  674.         return $this->lpos;
  675.     }
  676.     /**
  677.      * Set rpos
  678.      *
  679.      * @param integer $rpos
  680.      * @return Category
  681.      */
  682.     public function setRpos($rpos)
  683.     {
  684.         $this->rpos $rpos;
  685.         return $this;
  686.     }
  687.     /**
  688.      * Get rpos
  689.      *
  690.      * @return integer
  691.      */
  692.     public function getRpos()
  693.     {
  694.         return $this->rpos;
  695.     }
  696.     /**
  697.      * Set enabled
  698.      *
  699.      * @param boolean $enabled
  700.      * @return Category
  701.      */
  702.     public function setEnabled($enabled)
  703.     {
  704.         $this->getPreviousState()->enabled $this->enabled;
  705.         $this->enabled                     = (bool)$enabled;
  706.         return $this;
  707.     }
  708.     /**
  709.      * Get enabled
  710.      *
  711.      * @return boolean
  712.      */
  713.     public function getEnabled()
  714.     {
  715.         return $this->enabled;
  716.     }
  717.     /**
  718.      * Set show_title
  719.      *
  720.      * @param boolean $showTitle
  721.      * @return Category
  722.      */
  723.     public function setShowTitle($showTitle)
  724.     {
  725.         $this->show_title $showTitle;
  726.         return $this;
  727.     }
  728.     /**
  729.      * Get show_title
  730.      *
  731.      * @return boolean
  732.      */
  733.     public function getShowTitle()
  734.     {
  735.         return $this->show_title;
  736.     }
  737.     /**
  738.      * Set depth
  739.      *
  740.      * @param integer $depth
  741.      * @return Category
  742.      */
  743.     public function setDepth($depth)
  744.     {
  745.         $this->depth $depth;
  746.         return $this;
  747.     }
  748.     /**
  749.      * Get depth
  750.      *
  751.      * @return integer
  752.      */
  753.     public function getDepth()
  754.     {
  755.         return $this->depth;
  756.     }
  757.     /**
  758.      * Set pos
  759.      *
  760.      * @param integer $pos
  761.      * @return Category
  762.      */
  763.     public function setPos($pos)
  764.     {
  765.         $this->pos $pos;
  766.         return $this;
  767.     }
  768.     /**
  769.      * Get pos
  770.      *
  771.      * @return integer
  772.      */
  773.     public function getPos()
  774.     {
  775.         return $this->pos;
  776.     }
  777.     /**
  778.      * Set root_category_look
  779.      *
  780.      * @param string $rootCategoryLook
  781.      * @return Category
  782.      */
  783.     public function setRootCategoryLook($rootCategoryLook)
  784.     {
  785.         $this->root_category_look $rootCategoryLook;
  786.         return $this;
  787.     }
  788.     /**
  789.      * Get root_category_look
  790.      *
  791.      * @return string
  792.      */
  793.     public function getRootCategoryLook()
  794.     {
  795.         return $this->root_category_look;
  796.     }
  797.     /**
  798.      * Set metaDescType
  799.      *
  800.      * @param string $metaDescType
  801.      * @return Category
  802.      */
  803.     public function setMetaDescType($metaDescType)
  804.     {
  805.         $this->metaDescType $metaDescType;
  806.         return $this;
  807.     }
  808.     /**
  809.      * Set xcPendingExport
  810.      *
  811.      * @param boolean $xcPendingExport
  812.      * @return Category
  813.      */
  814.     public function setXcPendingExport($xcPendingExport)
  815.     {
  816.         $this->xcPendingExport $xcPendingExport;
  817.         return $this;
  818.     }
  819.     /**
  820.      * Get xcPendingExport
  821.      *
  822.      * @return boolean
  823.      */
  824.     public function getXcPendingExport()
  825.     {
  826.         return $this->xcPendingExport;
  827.     }
  828.     /**
  829.      * Set quickFlags
  830.      *
  831.      * @param \XLite\Model\Category\QuickFlags $quickFlags
  832.      * @return Category
  833.      */
  834.     public function setQuickFlags(\XLite\Model\Category\QuickFlags $quickFlags null)
  835.     {
  836.         $this->quickFlags $quickFlags;
  837.         return $this;
  838.     }
  839.     /**
  840.      * Get quickFlags
  841.      *
  842.      * @return \XLite\Model\Category\QuickFlags
  843.      */
  844.     public function getQuickFlags()
  845.     {
  846.         return $this->quickFlags;
  847.     }
  848.     /**
  849.      * Add memberships
  850.      *
  851.      * @param \XLite\Model\Membership $memberships
  852.      * @return Category
  853.      */
  854.     public function addMemberships(\XLite\Model\Membership $memberships)
  855.     {
  856.         $this->memberships[] = $memberships;
  857.         return $this;
  858.     }
  859.     /**
  860.      * Get memberships
  861.      *
  862.      * @return \Doctrine\Common\Collections\Collection
  863.      */
  864.     public function getMemberships()
  865.     {
  866.         return $this->memberships;
  867.     }
  868.     /**
  869.      * Get image
  870.      *
  871.      * @return Image
  872.      */
  873.     public function getImage()
  874.     {
  875.         return $this->image;
  876.     }
  877.     public function getImageWithFallback(): ?Base\Image
  878.     {
  879.         return $this->image ?: $this->getFallbackImage();
  880.     }
  881.     public function getFallbackImage(): ?Base\Image
  882.     {
  883.         /** @var Base\Image $image */
  884.         $image $this->getCachedFallbackImage();
  885.         if ($image && !$image->isFileExists()) {
  886.             // If image will become unavailable - force updating the image
  887.             $image $this->getCachedFallbackImage(true);
  888.         }
  889.         return $image;
  890.     }
  891.     public function getCachedFallbackImage(bool $force false): ?Base\Image
  892.     {
  893.         // Just caching fallback image for a while.
  894.         // If admin want to change the icon for the Category - getImageWithFallback will return it instead of cached fallback
  895.         return ExecuteCached::executeCached(
  896.             fn () => $this->findFallbackImage(),
  897.             [__METHOD__$this->getCategoryId()],
  898.             86400,
  899.             $force
  900.         );
  901.     }
  902.     public function findFallbackImage(): ?Base\Image
  903.     {
  904.         /** @var Repo\Product $productRepo */
  905.         $productRepo Database::getRepo(Product::class);
  906.         return $productRepo->getFirstImageForTheCategoryProducts($this->getCategoryId());
  907.     }
  908.     /**
  909.      * Get banner image
  910.      *
  911.      * @return \XLite\Model\Image\Category\Banner
  912.      */
  913.     public function getBanner()
  914.     {
  915.         return $this->banner;
  916.     }
  917.     /**
  918.      * Add categoryProducts
  919.      *
  920.      * @param \XLite\Model\CategoryProducts $categoryProducts
  921.      * @return Category
  922.      */
  923.     public function addCategoryProducts(\XLite\Model\CategoryProducts $categoryProducts)
  924.     {
  925.         $this->categoryProducts[] = $categoryProducts;
  926.         return $this;
  927.     }
  928.     /**
  929.      * Get categoryProducts
  930.      *
  931.      * @return \Doctrine\Common\Collections\Collection
  932.      */
  933.     public function getCategoryProducts()
  934.     {
  935.         return $this->categoryProducts;
  936.     }
  937.     /**
  938.      * Add children
  939.      *
  940.      * @param \XLite\Model\Category $children
  941.      * @return Category
  942.      */
  943.     public function addChildren(\XLite\Model\Category $children)
  944.     {
  945.         $this->children[] = $children;
  946.         return $this;
  947.     }
  948.     /**
  949.      * Get children
  950.      *
  951.      * @return \Doctrine\Common\Collections\Collection
  952.      */
  953.     public function getChildren()
  954.     {
  955.         return $this->children;
  956.     }
  957.     /**
  958.      * Get parent
  959.      *
  960.      * @return \XLite\Model\Category
  961.      */
  962.     public function getParent()
  963.     {
  964.         return $this->parent;
  965.     }
  966.     /**
  967.      * Add cleanURLs
  968.      *
  969.      * @param \XLite\Model\CleanURL $cleanURLs
  970.      * @return Category
  971.      */
  972.     public function addCleanURLs(\XLite\Model\CleanURL $cleanURLs)
  973.     {
  974.         $this->cleanURLs[] = $cleanURLs;
  975.         return $this;
  976.     }
  977.     /**
  978.      * Get cleanURLs
  979.      *
  980.      * @return \Doctrine\Common\Collections\Collection
  981.      */
  982.     public function getCleanURLs()
  983.     {
  984.         return $this->cleanURLs;
  985.     }
  986.     /**
  987.      * Get front URL
  988.      *
  989.      * @return string
  990.      */
  991.     public function getFrontURL($buildCuInAdminZone false)
  992.     {
  993.         return $this->getCategoryId()
  994.             ? \XLite\Core\Converter::makeURLValid(
  995.                 \XLite::getInstance()->getShopURL(
  996.                     \XLite\Core\Converter::buildURL(
  997.                         'category',
  998.                         '',
  999.                         ['category_id' => $this->getCategoryId()],
  1000.                         \XLite::getCustomerScript(),
  1001.                         $buildCuInAdminZone
  1002.                     )
  1003.                 )
  1004.             )
  1005.             : null;
  1006.     }
  1007.     // {{{ Translation Getters / setters
  1008.     /**
  1009.      * @return string
  1010.      */
  1011.     public function getDescription()
  1012.     {
  1013.         return $this->getTranslationField(__FUNCTION__);
  1014.     }
  1015.     /**
  1016.      * @param string $description
  1017.      *
  1018.      * @return \XLite\Model\Base\Translation
  1019.      */
  1020.     public function setDescription($description)
  1021.     {
  1022.         return $this->setTranslationField(__FUNCTION__$description);
  1023.     }
  1024.     /**
  1025.      * @return string
  1026.      */
  1027.     public function getMetaTags()
  1028.     {
  1029.         return $this->getTranslationField(__FUNCTION__);
  1030.     }
  1031.     /**
  1032.      * @param string $metaTags
  1033.      *
  1034.      * @return \XLite\Model\Base\Translation
  1035.      */
  1036.     public function setMetaTags($metaTags)
  1037.     {
  1038.         return $this->setTranslationField(__FUNCTION__$metaTags);
  1039.     }
  1040.     /**
  1041.      * @param string $metaDesc
  1042.      *
  1043.      * @return \XLite\Model\Base\Translation
  1044.      */
  1045.     public function setMetaDesc($metaDesc)
  1046.     {
  1047.         return $this->setTranslationField(__FUNCTION__$metaDesc);
  1048.     }
  1049.     /**
  1050.      * @return string
  1051.      */
  1052.     public function getMetaTitle()
  1053.     {
  1054.         return $this->getTranslationField(__FUNCTION__);
  1055.     }
  1056.     /**
  1057.      * @param string $metaTitle
  1058.      *
  1059.      * @return \XLite\Model\Base\Translation
  1060.      */
  1061.     public function setMetaTitle($metaTitle)
  1062.     {
  1063.         return $this->setTranslationField(__FUNCTION__$metaTitle);
  1064.     }
  1065.     // }}}
  1066. }