classes/XLite/Model/Category.php line 131

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