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