PageRenderTime 93ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/app/code/core/Mage/Catalog/Model/Layer/Filter/Price.php

https://github.com/FiveDigital/magento2
PHP | 578 lines | 317 code | 56 blank | 205 comment | 58 complexity | a581f85804cde51bc2652b1632534d2c MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. <?php
  2. /**
  3. * Magento
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@magentocommerce.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade Magento to newer
  18. * versions in the future. If you wish to customize Magento for your
  19. * needs please refer to http://www.magentocommerce.com for more information.
  20. *
  21. * @category Mage
  22. * @package Mage_Catalog
  23. * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com)
  24. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  25. */
  26. /**
  27. * Layer price filter
  28. *
  29. * @category Mage
  30. * @package Mage_Catalog
  31. * @author Magento Core Team <core@magentocommerce.com>
  32. */
  33. /**
  34. * @method Mage_Catalog_Model_Layer_Filter_Price setInterval(array)
  35. * @method array getInterval()
  36. */
  37. class Mage_Catalog_Model_Layer_Filter_Price extends Mage_Catalog_Model_Layer_Filter_Abstract
  38. {
  39. /**
  40. * XML configuration paths for Price Layered Navigation
  41. */
  42. const XML_PATH_RANGE_CALCULATION = 'catalog/layered_navigation/price_range_calculation';
  43. const XML_PATH_RANGE_STEP = 'catalog/layered_navigation/price_range_step';
  44. const XML_PATH_RANGE_MAX_INTERVALS = 'catalog/layered_navigation/price_range_max_intervals';
  45. const XML_PATH_ONE_PRICE_INTERVAL = 'catalog/layered_navigation/one_price_interval';
  46. const XML_PATH_INTERVAL_DIVISION_LIMIT = 'catalog/layered_navigation/interval_division_limit';
  47. /**
  48. * Price layered navigation modes: Automatic (equalize price ranges), Automatic (equalize product counts), Manual
  49. */
  50. const RANGE_CALCULATION_AUTO = 'auto'; // equalize price ranges
  51. const RANGE_CALCULATION_IMPROVED = 'improved'; // equalize product counts
  52. const RANGE_CALCULATION_MANUAL = 'manual';
  53. /**
  54. * Minimal size of the range
  55. */
  56. const MIN_RANGE_POWER = 10;
  57. /**
  58. * Resource instance
  59. *
  60. * @var Mage_Catalog_Model_Resource_Layer_Filter_Price
  61. */
  62. protected $_resource;
  63. /**
  64. * Class constructor
  65. *
  66. */
  67. public function __construct()
  68. {
  69. parent::__construct();
  70. $this->_requestVar = 'price';
  71. }
  72. /**
  73. * Retrieve resource instance
  74. *
  75. * @return Mage_Catalog_Model_Resource_Layer_Filter_Price
  76. */
  77. protected function _getResource()
  78. {
  79. if (is_null($this->_resource)) {
  80. $this->_resource = Mage::getResourceModel('Mage_Catalog_Model_Resource_Layer_Filter_Price');
  81. }
  82. return $this->_resource;
  83. }
  84. /**
  85. * Get price range for building filter steps
  86. *
  87. * @return int
  88. */
  89. public function getPriceRange()
  90. {
  91. $range = $this->getData('price_range');
  92. if (!$range) {
  93. $currentCategory = Mage::registry('current_category_filter');
  94. if ($currentCategory) {
  95. $range = $currentCategory->getFilterPriceRange();
  96. } else {
  97. $range = $this->getLayer()->getCurrentCategory()->getFilterPriceRange();
  98. }
  99. $maxPrice = $this->getMaxPriceInt();
  100. if (!$range) {
  101. $calculation = Mage::app()->getStore()->getConfig(self::XML_PATH_RANGE_CALCULATION);
  102. if ($calculation == self::RANGE_CALCULATION_AUTO) {
  103. $index = 1;
  104. do {
  105. $range = pow(10, (strlen(floor($maxPrice)) - $index));
  106. $items = $this->getRangeItemCounts($range);
  107. $index++;
  108. }
  109. while($range > self::MIN_RANGE_POWER && count($items) < 2);
  110. } else {
  111. $range = (float)Mage::app()->getStore()->getConfig(self::XML_PATH_RANGE_STEP);
  112. }
  113. }
  114. $this->setData('price_range', $range);
  115. }
  116. return $range;
  117. }
  118. /**
  119. * Get maximum price from layer products set
  120. *
  121. * @return float
  122. */
  123. public function getMaxPriceInt()
  124. {
  125. $maxPrice = $this->getData('max_price_int');
  126. if (is_null($maxPrice)) {
  127. $maxPrice = $this->getLayer()->getProductCollection()->getMaxPrice();
  128. $maxPrice = floor($maxPrice);
  129. $this->setData('max_price_int', $maxPrice);
  130. }
  131. return $maxPrice;
  132. }
  133. /**
  134. * Get information about products count in range
  135. *
  136. * @param int $range
  137. * @return int
  138. */
  139. public function getRangeItemCounts($range)
  140. {
  141. $rangeKey = 'range_item_counts_' . $range;
  142. $items = $this->getData($rangeKey);
  143. if (is_null($items)) {
  144. $items = $this->_getResource()->getCount($this, $range);
  145. // checking max number of intervals
  146. $i = 0;
  147. $lastIndex = null;
  148. $maxIntervalsNumber = $this->getMaxIntervalsNumber();
  149. $calculation = Mage::app()->getStore()->getConfig(self::XML_PATH_RANGE_CALCULATION);
  150. foreach ($items as $k => $v) {
  151. ++$i;
  152. if ($calculation == self::RANGE_CALCULATION_MANUAL && $i > 1 && $i > $maxIntervalsNumber) {
  153. $items[$lastIndex] += $v;
  154. unset($items[$k]);
  155. } else {
  156. $lastIndex = $k;
  157. }
  158. }
  159. $this->setData($rangeKey, $items);
  160. }
  161. return $items;
  162. }
  163. /**
  164. * Prepare text of item label
  165. *
  166. * @deprecated since 1.7.0.0
  167. * @param int $range
  168. * @param float $value
  169. * @return string
  170. */
  171. protected function _renderItemLabel($range, $value)
  172. {
  173. $store = Mage::app()->getStore();
  174. $fromPrice = $store->formatPrice(($value - 1) * $range);
  175. $toPrice = $store->formatPrice($value*$range);
  176. return Mage::helper('Mage_Catalog_Helper_Data')->__('%s - %s', $fromPrice, $toPrice);
  177. }
  178. /**
  179. * Prepare text of range label
  180. *
  181. * @param float|string $fromPrice
  182. * @param float|string $toPrice
  183. * @return string
  184. */
  185. protected function _renderRangeLabel($fromPrice, $toPrice)
  186. {
  187. $store = Mage::app()->getStore();
  188. $formattedFromPrice = $store->formatPrice($fromPrice);
  189. if ($toPrice === '') {
  190. return Mage::helper('Mage_Catalog_Helper_Data')->__('%s and above', $formattedFromPrice);
  191. } elseif ($fromPrice == $toPrice && Mage::app()->getStore()->getConfig(self::XML_PATH_ONE_PRICE_INTERVAL)) {
  192. return $formattedFromPrice;
  193. } else {
  194. if ($fromPrice != $toPrice) {
  195. $toPrice -= .01;
  196. }
  197. return Mage::helper('Mage_Catalog_Helper_Data')->__('%s - %s', $formattedFromPrice, $store->formatPrice($toPrice));
  198. }
  199. }
  200. /**
  201. * Get additional request param data
  202. *
  203. * @return string
  204. */
  205. protected function _getAdditionalRequestData()
  206. {
  207. $result = '';
  208. $appliedInterval = $this->getInterval();
  209. if ($appliedInterval) {
  210. $result = ',' . $appliedInterval[0] . '-' . $appliedInterval[1];
  211. $priorIntervals = $this->getResetValue();
  212. if ($priorIntervals) {
  213. $result .= ',' . $priorIntervals;
  214. }
  215. }
  216. return $result;
  217. }
  218. /**
  219. * Get data generated by algorithm for build price filter items
  220. *
  221. * @return array
  222. */
  223. protected function _getCalculatedItemsData()
  224. {
  225. /** @var $algorithmModel Mage_Catalog_Model_Layer_Filter_Price_Algorithm */
  226. $algorithmModel = Mage::getSingleton('Mage_Catalog_Model_Layer_Filter_Price_Algorithm');
  227. $collection = $this->getLayer()->getProductCollection();
  228. $appliedInterval = $this->getInterval();
  229. if ($appliedInterval
  230. && $collection->getPricesCount() <= $this->getIntervalDivisionLimit()
  231. ) {
  232. return array();
  233. }
  234. $algorithmModel->setPricesModel($this)->setStatistics(
  235. $collection->getMinPrice(),
  236. $collection->getMaxPrice(),
  237. $collection->getPriceStandardDeviation(),
  238. $collection->getPricesCount()
  239. );
  240. if ($appliedInterval) {
  241. if ($appliedInterval[0] == $appliedInterval[1] || $appliedInterval[1] === '0') {
  242. return array();
  243. }
  244. $algorithmModel->setLimits($appliedInterval[0], $appliedInterval[1]);
  245. }
  246. $items = array();
  247. foreach ($algorithmModel->calculateSeparators() as $separator) {
  248. $items[] = array(
  249. 'label' => $this->_renderRangeLabel($separator['from'], $separator['to']),
  250. 'value' => (($separator['from'] == 0) ? '' : $separator['from'])
  251. . '-' . $separator['to'] . $this->_getAdditionalRequestData(),
  252. 'count' => $separator['count'],
  253. );
  254. }
  255. return $items;
  256. }
  257. /**
  258. * Get data for build price filter items
  259. *
  260. * @return array
  261. */
  262. protected function _getItemsData()
  263. {
  264. if (Mage::app()->getStore()->getConfig(self::XML_PATH_RANGE_CALCULATION) == self::RANGE_CALCULATION_IMPROVED) {
  265. return $this->_getCalculatedItemsData();
  266. } elseif ($this->getInterval()) {
  267. return array();
  268. }
  269. $range = $this->getPriceRange();
  270. $dbRanges = $this->getRangeItemCounts($range);
  271. $data = array();
  272. if (!empty($dbRanges)) {
  273. $lastIndex = array_keys($dbRanges);
  274. $lastIndex = $lastIndex[count($lastIndex) - 1];
  275. foreach ($dbRanges as $index => $count) {
  276. $fromPrice = ($index == 1) ? '' : (($index - 1) * $range);
  277. $toPrice = ($index == $lastIndex) ? '' : ($index * $range);
  278. $data[] = array(
  279. 'label' => $this->_renderRangeLabel($fromPrice, $toPrice),
  280. 'value' => $fromPrice . '-' . $toPrice,
  281. 'count' => $count,
  282. );
  283. }
  284. }
  285. return $data;
  286. }
  287. /**
  288. * Apply price range filter to collection
  289. *
  290. * @return Mage_Catalog_Model_Layer_Filter_Price
  291. */
  292. protected function _applyPriceRange()
  293. {
  294. $this->_getResource()->applyPriceRange($this);
  295. return $this;
  296. }
  297. /**
  298. * Validate and parse filter request param
  299. *
  300. * @param string $filter
  301. * @return array|bool
  302. */
  303. protected function _validateFilter($filter)
  304. {
  305. $filter = explode('-', $filter);
  306. if (count($filter) != 2) {
  307. return false;
  308. }
  309. foreach ($filter as $v) {
  310. if (($v !== '' && $v !== '0' && (float)$v <= 0) || is_infinite((float)$v)) {
  311. return false;
  312. }
  313. }
  314. return $filter;
  315. }
  316. /**
  317. * Apply price range filter
  318. *
  319. * @param Zend_Controller_Request_Abstract $request
  320. * @param $filterBlock
  321. *
  322. * @return Mage_Catalog_Model_Layer_Filter_Price
  323. */
  324. public function apply(Zend_Controller_Request_Abstract $request, $filterBlock)
  325. {
  326. /**
  327. * Filter must be string: $fromPrice-$toPrice
  328. */
  329. $filter = $request->getParam($this->getRequestVar());
  330. if (!$filter) {
  331. return $this;
  332. }
  333. //validate filter
  334. $filterParams = explode(',', $filter);
  335. $filter = $this->_validateFilter($filterParams[0]);
  336. if (!$filter) {
  337. return $this;
  338. }
  339. list($from, $to) = $filter;
  340. $this->setInterval(array($from, $to));
  341. $priorFilters = array();
  342. for ($i = 1; $i < count($filterParams); ++$i) {
  343. $priorFilter = $this->_validateFilter($filterParams[$i]);
  344. if ($priorFilter) {
  345. $priorFilters[] = $priorFilter;
  346. } else {
  347. //not valid data
  348. $priorFilters = array();
  349. break;
  350. }
  351. }
  352. if ($priorFilters) {
  353. $this->setPriorIntervals($priorFilters);
  354. }
  355. $this->_applyPriceRange();
  356. $this->getLayer()->getState()->addFilter($this->_createItem(
  357. $this->_renderRangeLabel(empty($from) ? 0 : $from, $to),
  358. $filter
  359. ));
  360. return $this;
  361. }
  362. /**
  363. * Apply filter value to product collection based on filter range and selected value
  364. *
  365. * @deprecated since 1.7.0.0
  366. * @param int $range
  367. * @param int $index
  368. * @return Mage_Catalog_Model_Layer_Filter_Price
  369. */
  370. protected function _applyToCollection($range, $index)
  371. {
  372. $this->_getResource()->applyFilterToCollection($this, $range, $index);
  373. return $this;
  374. }
  375. /**
  376. * Retrieve active customer group id
  377. *
  378. * @return int
  379. */
  380. public function getCustomerGroupId()
  381. {
  382. $customerGroupId = $this->_getData('customer_group_id');
  383. if (is_null($customerGroupId)) {
  384. $customerGroupId = Mage::getSingleton('Mage_Customer_Model_Session')->getCustomerGroupId();
  385. }
  386. return $customerGroupId;
  387. }
  388. /**
  389. * Set active customer group id for filter
  390. *
  391. * @param int $customerGroupId
  392. * @return Mage_Catalog_Model_Layer_Filter_Price
  393. */
  394. public function setCustomerGroupId($customerGroupId)
  395. {
  396. return $this->setData('customer_group_id', $customerGroupId);
  397. }
  398. /**
  399. * Retrieve active currency rate for filter
  400. *
  401. * @return float
  402. */
  403. public function getCurrencyRate()
  404. {
  405. $rate = $this->_getData('currency_rate');
  406. if (is_null($rate)) {
  407. $rate = Mage::app()->getStore($this->getStoreId())->getCurrentCurrencyRate();
  408. }
  409. if (!$rate) {
  410. $rate = 1;
  411. }
  412. return $rate;
  413. }
  414. /**
  415. * Set active currency rate for filter
  416. *
  417. * @param float $rate
  418. * @return Mage_Catalog_Model_Layer_Filter_Price
  419. */
  420. public function setCurrencyRate($rate)
  421. {
  422. return $this->setData('currency_rate', $rate);
  423. }
  424. /**
  425. * Get maximum number of intervals
  426. *
  427. * @return int
  428. */
  429. public function getMaxIntervalsNumber()
  430. {
  431. return (int)Mage::app()->getStore()->getConfig(self::XML_PATH_RANGE_MAX_INTERVALS);
  432. }
  433. /**
  434. * Get interval division limit
  435. *
  436. * @return int
  437. */
  438. public function getIntervalDivisionLimit()
  439. {
  440. return (int)Mage::app()->getStore()->getConfig(self::XML_PATH_INTERVAL_DIVISION_LIMIT);
  441. }
  442. /**
  443. * Get filter value for reset current filter state
  444. *
  445. * @return null|string
  446. */
  447. public function getResetValue()
  448. {
  449. $priorIntervals = $this->getPriorIntervals();
  450. $value = array();
  451. if ($priorIntervals) {
  452. foreach ($priorIntervals as $priorInterval) {
  453. $value[] = implode('-', $priorInterval);
  454. }
  455. return implode(',', $value);
  456. }
  457. return parent::getResetValue();
  458. }
  459. /**
  460. * Get 'clear price' link text
  461. *
  462. * @return false|string
  463. */
  464. public function getClearLinkText()
  465. {
  466. if (Mage::app()->getStore()->getConfig(self::XML_PATH_RANGE_CALCULATION) == self::RANGE_CALCULATION_IMPROVED
  467. && $this->getPriorIntervals()
  468. ) {
  469. return Mage::helper('Mage_Catalog_Helper_Data')->__('Clear Price');
  470. }
  471. return parent::getClearLinkText();
  472. }
  473. /**
  474. * Load range of product prices
  475. *
  476. * @param int $limit
  477. * @param null|int $offset
  478. * @param null|int $lowerPrice
  479. * @param null|int $upperPrice
  480. * @return array|false
  481. */
  482. public function loadPrices($limit, $offset = null, $lowerPrice = null, $upperPrice = null)
  483. {
  484. $prices = $this->_getResource()->loadPrices($this, $limit, $offset, $lowerPrice, $upperPrice);
  485. if ($prices) {
  486. $prices = array_map('floatval', $prices);
  487. }
  488. return $prices;
  489. }
  490. /**
  491. * Load range of product prices, preceding the price
  492. *
  493. * @param float $price
  494. * @param int $index
  495. * @param null|int $lowerPrice
  496. * @return array|false
  497. */
  498. public function loadPreviousPrices($price, $index, $lowerPrice = null)
  499. {
  500. $prices = $this->_getResource()->loadPreviousPrices($this, $price, $index, $lowerPrice);
  501. if ($prices) {
  502. $prices = array_map('floatval', $prices);
  503. }
  504. return $prices;
  505. }
  506. /**
  507. * Load range of product prices, next to the price
  508. *
  509. * @param float $price
  510. * @param int $rightIndex
  511. * @param null|int $upperPrice
  512. * @return array|false
  513. */
  514. public function loadNextPrices($price, $rightIndex, $upperPrice = null)
  515. {
  516. $prices = $this->_getResource()->loadNextPrices($this, $price, $rightIndex, $upperPrice);
  517. if ($prices) {
  518. $prices = array_map('floatval', $prices);
  519. }
  520. return $prices;
  521. }
  522. }