Data.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Catalog\Helper;
  7. use Magento\Catalog\Api\CategoryRepositoryInterface;
  8. use Magento\Catalog\Api\ProductRepositoryInterface;
  9. use Magento\Store\Model\ScopeInterface;
  10. use Magento\Customer\Model\Session as CustomerSession;
  11. use Magento\Framework\Exception\NoSuchEntityException;
  12. use Magento\Framework\Pricing\PriceCurrencyInterface;
  13. use Magento\Tax\Api\Data\TaxClassKeyInterface;
  14. use Magento\Tax\Model\Config;
  15. /**
  16. * Catalog data helper
  17. *
  18. * @api
  19. *
  20. * @SuppressWarnings(PHPMD.TooManyFields)
  21. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  22. * @since 100.0.2
  23. */
  24. class Data extends \Magento\Framework\App\Helper\AbstractHelper
  25. {
  26. const PRICE_SCOPE_GLOBAL = 0;
  27. const PRICE_SCOPE_WEBSITE = 1;
  28. const XML_PATH_PRICE_SCOPE = 'catalog/price/scope';
  29. const CONFIG_USE_STATIC_URLS = 'cms/wysiwyg/use_static_urls_in_catalog';
  30. /**
  31. * @deprecated
  32. * @see \Magento\Catalog\Helper\Output::isDirectivesExists
  33. */
  34. const CONFIG_PARSE_URL_DIRECTIVES = 'catalog/frontend/parse_url_directives';
  35. const XML_PATH_DISPLAY_PRODUCT_COUNT = 'catalog/layered_navigation/display_product_count';
  36. /**
  37. * Cache context
  38. */
  39. const CONTEXT_CATALOG_SORT_DIRECTION = 'catalog_sort_direction';
  40. const CONTEXT_CATALOG_SORT_ORDER = 'catalog_sort_order';
  41. const CONTEXT_CATALOG_DISPLAY_MODE = 'catalog_mode';
  42. const CONTEXT_CATALOG_LIMIT = 'catalog_limit';
  43. /**
  44. * Breadcrumb Path cache
  45. *
  46. * @var array
  47. */
  48. protected $_categoryPath;
  49. /**
  50. * Currently selected store ID if applicable
  51. *
  52. * @var int
  53. */
  54. protected $_storeId;
  55. /**
  56. * Core registry
  57. *
  58. * @var \Magento\Framework\Registry
  59. */
  60. protected $_coreRegistry;
  61. /**
  62. * Catalog product
  63. *
  64. * @var Product
  65. */
  66. protected $_catalogProduct;
  67. /**
  68. * Catalog category
  69. *
  70. * @var Category
  71. */
  72. protected $_catalogCategory;
  73. /**
  74. * @var \Magento\Framework\Stdlib\StringUtils
  75. */
  76. protected $string;
  77. /**
  78. * @var string
  79. */
  80. protected $_templateFilterModel;
  81. /**
  82. * Catalog session
  83. *
  84. * @var \Magento\Catalog\Model\Session
  85. */
  86. protected $_catalogSession;
  87. /**
  88. * Store manager
  89. *
  90. * @var \Magento\Store\Model\StoreManagerInterface
  91. */
  92. protected $_storeManager;
  93. /**
  94. * Template filter factory
  95. *
  96. * @var \Magento\Catalog\Model\Template\Filter\Factory
  97. */
  98. protected $_templateFilterFactory;
  99. /**
  100. * Tax class key factory
  101. *
  102. * @var \Magento\Tax\Api\Data\TaxClassKeyInterfaceFactory
  103. */
  104. protected $_taxClassKeyFactory;
  105. /**
  106. * Tax helper
  107. *
  108. * @var \Magento\Tax\Model\Config
  109. */
  110. protected $_taxConfig;
  111. /**
  112. * Quote details factory
  113. *
  114. * @var \Magento\Tax\Api\Data\QuoteDetailsInterfaceFactory
  115. */
  116. protected $_quoteDetailsFactory;
  117. /**
  118. * Quote details item factory
  119. *
  120. * @var \Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory
  121. */
  122. protected $_quoteDetailsItemFactory;
  123. /**
  124. * @var CustomerSession
  125. */
  126. protected $_customerSession;
  127. /**
  128. * Tax calculation service interface
  129. *
  130. * @var \Magento\Tax\Api\TaxCalculationInterface
  131. */
  132. protected $_taxCalculationService;
  133. /**
  134. * Price currency
  135. *
  136. * @var PriceCurrencyInterface
  137. */
  138. protected $priceCurrency;
  139. /**
  140. * @var ProductRepositoryInterface
  141. */
  142. protected $productRepository;
  143. /**
  144. * @var CategoryRepositoryInterface
  145. */
  146. protected $categoryRepository;
  147. /**
  148. * @var \Magento\Customer\Api\GroupRepositoryInterface
  149. */
  150. protected $customerGroupRepository;
  151. /**
  152. * @var \Magento\Customer\Api\Data\AddressInterfaceFactory
  153. */
  154. protected $addressFactory;
  155. /**
  156. * @var \Magento\Customer\Api\Data\RegionInterfaceFactory
  157. */
  158. protected $regionFactory;
  159. /**
  160. * @param \Magento\Framework\App\Helper\Context $context
  161. * @param \Magento\Store\Model\StoreManagerInterface $storeManager
  162. * @param \Magento\Catalog\Model\Session $catalogSession
  163. * @param \Magento\Framework\Stdlib\StringUtils $string
  164. * @param Category $catalogCategory
  165. * @param Product $catalogProduct
  166. * @param \Magento\Framework\Registry $coreRegistry
  167. * @param \Magento\Catalog\Model\Template\Filter\Factory $templateFilterFactory
  168. * @param string $templateFilterModel
  169. * @param \Magento\Tax\Api\Data\TaxClassKeyInterfaceFactory $taxClassKeyFactory
  170. * @param Config $taxConfig
  171. * @param \Magento\Tax\Api\Data\QuoteDetailsInterfaceFactory $quoteDetailsFactory
  172. * @param \Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory $quoteDetailsItemFactory
  173. * @param \Magento\Tax\Api\TaxCalculationInterface $taxCalculationService
  174. * @param CustomerSession $customerSession
  175. * @param PriceCurrencyInterface $priceCurrency
  176. * @param ProductRepositoryInterface $productRepository
  177. * @param CategoryRepositoryInterface $categoryRepository
  178. * @param \Magento\Customer\Api\GroupRepositoryInterface $customerGroupRepository
  179. * @param \Magento\Customer\Api\Data\AddressInterfaceFactory $addressFactory
  180. * @param \Magento\Customer\Api\Data\RegionInterfaceFactory $regionFactory
  181. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  182. */
  183. public function __construct(
  184. \Magento\Framework\App\Helper\Context $context,
  185. \Magento\Store\Model\StoreManagerInterface $storeManager,
  186. \Magento\Catalog\Model\Session $catalogSession,
  187. \Magento\Framework\Stdlib\StringUtils $string,
  188. Category $catalogCategory,
  189. Product $catalogProduct,
  190. \Magento\Framework\Registry $coreRegistry,
  191. \Magento\Catalog\Model\Template\Filter\Factory $templateFilterFactory,
  192. $templateFilterModel,
  193. \Magento\Tax\Api\Data\TaxClassKeyInterfaceFactory $taxClassKeyFactory,
  194. \Magento\Tax\Model\Config $taxConfig,
  195. \Magento\Tax\Api\Data\QuoteDetailsInterfaceFactory $quoteDetailsFactory,
  196. \Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory $quoteDetailsItemFactory,
  197. \Magento\Tax\Api\TaxCalculationInterface $taxCalculationService,
  198. CustomerSession $customerSession,
  199. PriceCurrencyInterface $priceCurrency,
  200. ProductRepositoryInterface $productRepository,
  201. CategoryRepositoryInterface $categoryRepository,
  202. \Magento\Customer\Api\GroupRepositoryInterface $customerGroupRepository,
  203. \Magento\Customer\Api\Data\AddressInterfaceFactory $addressFactory,
  204. \Magento\Customer\Api\Data\RegionInterfaceFactory $regionFactory
  205. ) {
  206. $this->_storeManager = $storeManager;
  207. $this->_catalogSession = $catalogSession;
  208. $this->_templateFilterFactory = $templateFilterFactory;
  209. $this->string = $string;
  210. $this->_catalogCategory = $catalogCategory;
  211. $this->_catalogProduct = $catalogProduct;
  212. $this->_coreRegistry = $coreRegistry;
  213. $this->_templateFilterModel = $templateFilterModel;
  214. $this->_taxClassKeyFactory = $taxClassKeyFactory;
  215. $this->_taxConfig = $taxConfig;
  216. $this->_quoteDetailsFactory = $quoteDetailsFactory;
  217. $this->_quoteDetailsItemFactory = $quoteDetailsItemFactory;
  218. $this->_taxCalculationService = $taxCalculationService;
  219. $this->_customerSession = $customerSession;
  220. $this->priceCurrency = $priceCurrency;
  221. $this->productRepository = $productRepository;
  222. $this->categoryRepository = $categoryRepository;
  223. $this->customerGroupRepository = $customerGroupRepository;
  224. $this->addressFactory = $addressFactory;
  225. $this->regionFactory = $regionFactory;
  226. parent::__construct($context);
  227. }
  228. /**
  229. * Set a specified store ID value
  230. *
  231. * @param int $store
  232. * @return $this
  233. */
  234. public function setStoreId($store)
  235. {
  236. $this->_storeId = $store;
  237. return $this;
  238. }
  239. /**
  240. * Return current category path or get it from current category
  241. *
  242. * Creating array of categories|product paths for breadcrumbs
  243. *
  244. * @return array
  245. */
  246. public function getBreadcrumbPath()
  247. {
  248. if (!$this->_categoryPath) {
  249. $path = [];
  250. $category = $this->getCategory();
  251. if ($category) {
  252. $pathInStore = $category->getPathInStore();
  253. $pathIds = array_reverse(explode(',', $pathInStore));
  254. $categories = $category->getParentCategories();
  255. // add category path breadcrumb
  256. foreach ($pathIds as $categoryId) {
  257. if (isset($categories[$categoryId]) && $categories[$categoryId]->getName()) {
  258. $path['category' . $categoryId] = [
  259. 'label' => $categories[$categoryId]->getName(),
  260. 'link' => $this->_isCategoryLink($categoryId) ? $categories[$categoryId]->getUrl() : ''
  261. ];
  262. }
  263. }
  264. }
  265. if ($this->getProduct()) {
  266. $path['product'] = ['label' => $this->getProduct()->getName()];
  267. }
  268. $this->_categoryPath = $path;
  269. }
  270. return $this->_categoryPath;
  271. }
  272. /**
  273. * Check is category link
  274. *
  275. * @param int $categoryId
  276. * @return bool
  277. */
  278. protected function _isCategoryLink($categoryId)
  279. {
  280. if ($this->getProduct()) {
  281. return true;
  282. }
  283. if ($categoryId != $this->getCategory()->getId()) {
  284. return true;
  285. }
  286. return false;
  287. }
  288. /**
  289. * Return current category object
  290. *
  291. * @return \Magento\Catalog\Model\Category|null
  292. */
  293. public function getCategory()
  294. {
  295. return $this->_coreRegistry->registry('current_category');
  296. }
  297. /**
  298. * Retrieve current Product object
  299. *
  300. * @return \Magento\Catalog\Model\Product|null
  301. */
  302. public function getProduct()
  303. {
  304. return $this->_coreRegistry->registry('current_product');
  305. }
  306. /**
  307. * Retrieve Visitor/Customer Last Viewed URL
  308. *
  309. * @return string
  310. */
  311. public function getLastViewedUrl()
  312. {
  313. $productId = $this->_catalogSession->getLastViewedProductId();
  314. if ($productId) {
  315. try {
  316. $product = $this->productRepository->getById($productId);
  317. } catch (NoSuchEntityException $e) {
  318. return '';
  319. }
  320. /* @var $product \Magento\Catalog\Model\Product */
  321. if ($this->_catalogProduct->canShow($product, 'catalog')) {
  322. return $product->getProductUrl();
  323. }
  324. }
  325. $categoryId = $this->_catalogSession->getLastViewedCategoryId();
  326. if ($categoryId) {
  327. try {
  328. $category = $this->categoryRepository->get($categoryId);
  329. } catch (NoSuchEntityException $e) {
  330. return '';
  331. }
  332. /* @var $category \Magento\Catalog\Model\Category */
  333. if (!$this->_catalogCategory->canShow($category)) {
  334. return '';
  335. }
  336. return $category->getCategoryUrl();
  337. }
  338. return '';
  339. }
  340. /**
  341. * Split SKU of an item by dashes and spaces
  342. *
  343. * Words will not be broken, unless this length is greater than $length
  344. *
  345. * @param string $sku
  346. * @param int $length
  347. * @return string[]
  348. */
  349. public function splitSku($sku, $length = 30)
  350. {
  351. return $this->string->split($sku, $length, true, false, '[\-\s]');
  352. }
  353. /**
  354. * Retrieve attribute hidden fields
  355. *
  356. * @return array
  357. */
  358. public function getAttributeHiddenFields()
  359. {
  360. if ($this->_coreRegistry->registry('attribute_type_hidden_fields')) {
  361. return $this->_coreRegistry->registry('attribute_type_hidden_fields');
  362. } else {
  363. return [];
  364. }
  365. }
  366. /**
  367. * Retrieve Catalog Price Scope
  368. *
  369. * @return int|null
  370. */
  371. public function getPriceScope(): ?int
  372. {
  373. $priceScope = $this->scopeConfig->getValue(
  374. self::XML_PATH_PRICE_SCOPE,
  375. ScopeInterface::SCOPE_STORE
  376. );
  377. return isset($priceScope) ? (int)$priceScope : null;
  378. }
  379. /**
  380. * Is Global Price
  381. *
  382. * @return bool
  383. */
  384. public function isPriceGlobal()
  385. {
  386. return $this->getPriceScope() == self::PRICE_SCOPE_GLOBAL;
  387. }
  388. /**
  389. * Check if the store is configured to use static URLs for media
  390. *
  391. * @return bool
  392. */
  393. public function isUsingStaticUrlsAllowed()
  394. {
  395. return $this->scopeConfig->isSetFlag(
  396. self::CONFIG_USE_STATIC_URLS,
  397. ScopeInterface::SCOPE_STORE
  398. );
  399. }
  400. /**
  401. * Check if the parsing of URL directives is allowed for the catalog
  402. *
  403. * @return bool
  404. * @deprecated 103.0.0
  405. * @see \Magento\Catalog\Helper\Output::isDirectivesExists
  406. */
  407. public function isUrlDirectivesParsingAllowed()
  408. {
  409. return $this->scopeConfig->isSetFlag(
  410. self::CONFIG_PARSE_URL_DIRECTIVES,
  411. ScopeInterface::SCOPE_STORE,
  412. $this->_storeId
  413. );
  414. }
  415. /**
  416. * Retrieve template processor for catalog content
  417. *
  418. * @return \Magento\Framework\Filter\Template
  419. * @throws \Magento\Framework\Exception\LocalizedException
  420. */
  421. public function getPageTemplateProcessor()
  422. {
  423. return $this->_templateFilterFactory->create($this->_templateFilterModel);
  424. }
  425. /**
  426. * Whether to display items count for each filter option
  427. *
  428. * @param int $storeId Store view ID
  429. * @return bool
  430. */
  431. public function shouldDisplayProductCountOnLayer($storeId = null)
  432. {
  433. return $this->scopeConfig->isSetFlag(
  434. self::XML_PATH_DISPLAY_PRODUCT_COUNT,
  435. ScopeInterface::SCOPE_STORE,
  436. $storeId
  437. );
  438. }
  439. /**
  440. * Convert tax address array to address data object with country id and postcode
  441. *
  442. * @param array $taxAddress
  443. * @return \Magento\Customer\Api\Data\AddressInterface|null
  444. */
  445. private function convertDefaultTaxAddress(array $taxAddress = null)
  446. {
  447. if (empty($taxAddress)) {
  448. return null;
  449. }
  450. /** @var \Magento\Customer\Api\Data\AddressInterface $addressDataObject */
  451. $addressDataObject = $this->addressFactory->create()
  452. ->setCountryId($taxAddress['country_id'])
  453. ->setPostcode($taxAddress['postcode']);
  454. if (isset($taxAddress['region_id'])) {
  455. $addressDataObject->setRegion($this->regionFactory->create()->setRegionId($taxAddress['region_id']));
  456. }
  457. return $addressDataObject;
  458. }
  459. /**
  460. * Get product price with all tax settings processing
  461. *
  462. * @param \Magento\Catalog\Model\Product $product
  463. * @param float $price inputted product price
  464. * @param bool $includingTax return price include tax flag
  465. * @param null|\Magento\Customer\Model\Address\AbstractAddress $shippingAddress
  466. * @param null|\Magento\Customer\Model\Address\AbstractAddress $billingAddress
  467. * @param null|int $ctc customer tax class
  468. * @param null|string|bool|int|\Magento\Store\Model\Store $store
  469. * @param bool $priceIncludesTax flag what price parameter contain tax
  470. * @param bool $roundPrice
  471. * @return float
  472. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  473. * @SuppressWarnings(PHPMD.NPathComplexity)
  474. * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
  475. */
  476. public function getTaxPrice(
  477. $product,
  478. $price,
  479. $includingTax = null,
  480. $shippingAddress = null,
  481. $billingAddress = null,
  482. $ctc = null,
  483. $store = null,
  484. $priceIncludesTax = null,
  485. $roundPrice = true
  486. ) {
  487. if (!$price) {
  488. return $price;
  489. }
  490. $store = $this->_storeManager->getStore($store);
  491. if ($this->_taxConfig->needPriceConversion($store)) {
  492. if ($priceIncludesTax === null) {
  493. $priceIncludesTax = $this->_taxConfig->priceIncludesTax($store);
  494. }
  495. $shippingAddressDataObject = null;
  496. if ($shippingAddress === null) {
  497. $shippingAddressDataObject =
  498. $this->convertDefaultTaxAddress($this->_customerSession->getDefaultTaxShippingAddress());
  499. } elseif ($shippingAddress instanceof \Magento\Customer\Model\Address\AbstractAddress) {
  500. $shippingAddressDataObject = $shippingAddress->getDataModel();
  501. }
  502. $billingAddressDataObject = null;
  503. if ($billingAddress === null) {
  504. $billingAddressDataObject =
  505. $this->convertDefaultTaxAddress($this->_customerSession->getDefaultTaxBillingAddress());
  506. } elseif ($billingAddress instanceof \Magento\Customer\Model\Address\AbstractAddress) {
  507. $billingAddressDataObject = $billingAddress->getDataModel();
  508. }
  509. $taxClassKey = $this->_taxClassKeyFactory->create();
  510. $taxClassKey->setType(TaxClassKeyInterface::TYPE_ID)
  511. ->setValue($product->getTaxClassId());
  512. if ($ctc === null && $this->_customerSession->getCustomerGroupId() != null) {
  513. $ctc = $this->customerGroupRepository->getById($this->_customerSession->getCustomerGroupId())
  514. ->getTaxClassId();
  515. }
  516. $customerTaxClassKey = $this->_taxClassKeyFactory->create();
  517. $customerTaxClassKey->setType(TaxClassKeyInterface::TYPE_ID)
  518. ->setValue($ctc);
  519. $item = $this->_quoteDetailsItemFactory->create();
  520. $item->setQuantity(1)
  521. ->setCode($product->getSku())
  522. ->setShortDescription($product->getShortDescription())
  523. ->setTaxClassKey($taxClassKey)
  524. ->setIsTaxIncluded($priceIncludesTax)
  525. ->setType('product')
  526. ->setUnitPrice($price);
  527. $quoteDetails = $this->_quoteDetailsFactory->create();
  528. $quoteDetails->setShippingAddress($shippingAddressDataObject)
  529. ->setBillingAddress($billingAddressDataObject)
  530. ->setCustomerTaxClassKey($customerTaxClassKey)
  531. ->setItems([$item])
  532. ->setCustomerId($this->_customerSession->getCustomerId());
  533. $storeId = null;
  534. if ($store) {
  535. $storeId = $store->getId();
  536. }
  537. $taxDetails = $this->_taxCalculationService->calculateTax($quoteDetails, $storeId, $roundPrice);
  538. $items = $taxDetails->getItems();
  539. $taxDetailsItem = array_shift($items);
  540. if ($includingTax !== null) {
  541. if ($includingTax) {
  542. $price = $taxDetailsItem->getPriceInclTax();
  543. } else {
  544. $price = $taxDetailsItem->getPrice();
  545. }
  546. } else {
  547. switch ($this->_taxConfig->getPriceDisplayType($store)) {
  548. case Config::DISPLAY_TYPE_EXCLUDING_TAX:
  549. case Config::DISPLAY_TYPE_BOTH:
  550. $price = $taxDetailsItem->getPrice();
  551. break;
  552. case Config::DISPLAY_TYPE_INCLUDING_TAX:
  553. $price = $taxDetailsItem->getPriceInclTax();
  554. break;
  555. default:
  556. break;
  557. }
  558. }
  559. }
  560. if ($roundPrice) {
  561. return $this->priceCurrency->round($price);
  562. } else {
  563. return $price;
  564. }
  565. }
  566. }