PageRenderTime 51ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Indexer/Price.php

https://bitbucket.org/andrewjleavitt/magestudy
PHP | 549 lines | 336 code | 65 blank | 148 comment | 31 complexity | 8b6b7f3f9934b6dc10a3e0c6aa42aec5 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, GPL-2.0, WTFPL
  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) 2010 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. * Catalog Product Price Indexer Resource Model
  28. *
  29. * @category Mage
  30. * @package Mage_Catalog
  31. */
  32. class Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price extends Mage_Index_Model_Mysql4_Abstract
  33. {
  34. /**
  35. * Default Product Type Price indexer resource model
  36. *
  37. * @var string
  38. */
  39. protected $_defaultPriceIndexer = 'catalog/product_indexer_price_default';
  40. /**
  41. * Product Type Price indexer resource models
  42. *
  43. * @var array
  44. */
  45. protected $_indexers;
  46. /**
  47. * Define main index table
  48. *
  49. */
  50. protected function _construct()
  51. {
  52. $this->_init('catalog/product_index_price', 'entity_id');
  53. }
  54. /**
  55. * Retrieve parent ids and types by child id
  56. *
  57. * Return array with key product_id and value as product type id
  58. *
  59. * @param int $childId
  60. * @return array
  61. */
  62. public function getProductParentsByChild($childId)
  63. {
  64. $write = $this->_getWriteAdapter();
  65. $select = $write->select()
  66. ->from(array('l' => $this->getTable('catalog/product_relation')), array('parent_id'))
  67. ->join(
  68. array('e' => $this->getTable('catalog/product')),
  69. 'l.parent_id=e.entity_id',
  70. array('e.type_id'))
  71. ->where('l.child_id=?', $childId);
  72. return $write->fetchPairs($select);
  73. }
  74. /**
  75. * Process produce delete
  76. *
  77. * If the deleted product was found in a composite product(s) update it
  78. *
  79. * @param Mage_Index_Model_Event $event
  80. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price
  81. */
  82. public function catalogProductDelete(Mage_Index_Model_Event $event)
  83. {
  84. $data = $event->getNewData();
  85. if (empty($data['reindex_price_parent_ids'])) {
  86. return $this;
  87. }
  88. $this->clearTemporaryIndexTable();
  89. $processIds = array_keys($data['reindex_price_parent_ids']);
  90. $parentIds = array();
  91. foreach ($data['reindex_price_parent_ids'] as $parentId => $parentType) {
  92. $parentIds[$parentType][$parentId] = $parentId;
  93. }
  94. $this->_copyRelationIndexData($processIds);
  95. foreach ($parentIds as $parentType => $entityIds) {
  96. $this->_getIndexer($parentType)->reindexEntity($entityIds);
  97. }
  98. $this->_copyIndexDataToMainTable($parentIds);
  99. return $this;
  100. }
  101. /**
  102. * Copy data from temporary index table to main table by defined ids
  103. *
  104. * @param array $processIds
  105. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price
  106. */
  107. protected function _copyIndexDataToMainTable($processIds)
  108. {
  109. $write = $this->_getWriteAdapter();
  110. $write->beginTransaction();
  111. try {
  112. // remove old index
  113. $where = $write->quoteInto('entity_id IN(?)', $processIds);
  114. $write->delete($this->getMainTable(), $where);
  115. // remove additional data from index
  116. $where = $write->quoteInto('entity_id NOT IN(?)', $processIds);
  117. $write->delete($this->getIdxTable(), $where);
  118. // insert new index
  119. $this->useDisableKeys(false);
  120. $this->insertFromTable($this->getIdxTable(), $this->getMainTable());
  121. $this->useDisableKeys(true);
  122. $this->commit();
  123. } catch (Exception $e) {
  124. $this->rollBack();
  125. throw $e;
  126. }
  127. return $this;
  128. }
  129. /**
  130. * Process product save.
  131. * Method is responsible for index support
  132. * when product was saved and changed attribute(s) has an effect on price.
  133. *
  134. * @param Mage_Index_Model_Event $event
  135. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price
  136. */
  137. public function catalogProductSave(Mage_Index_Model_Event $event)
  138. {
  139. $productId = $event->getEntityPk();
  140. $data = $event->getNewData();
  141. /**
  142. * Check if price attribute values were updated
  143. */
  144. if (!isset($data['reindex_price'])) {
  145. return $this;
  146. }
  147. $this->clearTemporaryIndexTable();
  148. $this->_prepareWebsiteDateTable();
  149. $indexer = $this->_getIndexer($data['product_type_id']);
  150. $processIds = array($productId);
  151. if ($indexer->getIsComposite()) {
  152. $this->_copyRelationIndexData($productId);
  153. $this->_prepareTierPriceIndex($productId);
  154. $indexer->reindexEntity($productId);
  155. } else {
  156. $parentIds = $this->getProductParentsByChild($productId);
  157. if ($parentIds) {
  158. $processIds = array_merge($processIds, array_keys($parentIds));
  159. $this->_copyRelationIndexData(array_keys($parentIds), $productId);
  160. $this->_prepareTierPriceIndex($processIds);
  161. $indexer->reindexEntity($productId);
  162. $parentByType = array();
  163. foreach ($parentIds as $parentId => $parentType) {
  164. $parentByType[$parentType][$parentId] = $parentId;
  165. }
  166. foreach ($parentByType as $parentType => $entityIds) {
  167. $this->_getIndexer($parentType)->reindexEntity($entityIds);
  168. }
  169. } else {
  170. $this->_prepareTierPriceIndex($productId);
  171. $indexer->reindexEntity($productId);
  172. }
  173. }
  174. $this->_copyIndexDataToMainTable($processIds);
  175. return $this;
  176. }
  177. /**
  178. * Process product mass update action
  179. *
  180. * @param Mage_Index_Model_Event $event
  181. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price
  182. */
  183. public function catalogProductMassAction(Mage_Index_Model_Event $event)
  184. {
  185. $data = $event->getNewData();
  186. if (empty($data['reindex_price_product_ids'])) {
  187. return $this;
  188. }
  189. $processIds = $data['reindex_price_product_ids'];
  190. $write = $this->_getWriteAdapter();
  191. $select = $write->select()
  192. ->from($this->getTable('catalog/product'), 'COUNT(*)');
  193. $pCount = $write->fetchOne($select);
  194. // if affected more 30% of all products - run reindex all products
  195. if ($pCount * 0.3 < count($processIds)) {
  196. return $this->reindexAll();
  197. }
  198. // calculate relations
  199. $select = $write->select()
  200. ->from($this->getTable('catalog/product_relation'), 'COUNT(DISTINCT parent_id)')
  201. ->where('child_id IN(?)', $processIds);
  202. $aCount = $write->fetchOne($select);
  203. $select = $write->select()
  204. ->from($this->getTable('catalog/product_relation'), 'COUNT(DISTINCT child_id)')
  205. ->where('parent_id IN(?)', $processIds);
  206. $bCount = $write->fetchOne($select);
  207. // if affected with relations more 30% of all products - run reindex all products
  208. if ($pCount * 0.3 < count($processIds) + $aCount + $bCount) {
  209. return $this->reindexAll();
  210. }
  211. $this->reindexProductIds($processIds);
  212. return $this;
  213. }
  214. /**
  215. * Reindex product prices for specified product ids
  216. *
  217. * @param array | int $ids
  218. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price
  219. */
  220. public function reindexProductIds($ids)
  221. {
  222. if (empty($ids)) {
  223. return $this;
  224. }
  225. if (!is_array($ids)) {
  226. $ids = array($ids);
  227. }
  228. $this->clearTemporaryIndexTable();
  229. $write = $this->_getWriteAdapter();
  230. // retrieve products types
  231. $select = $write->select()
  232. ->from($this->getTable('catalog/product'), array('entity_id', 'type_id'))
  233. ->where('entity_id IN(?)', $ids);
  234. $pairs = $write->fetchPairs($select);
  235. $byType = array();
  236. foreach ($pairs as $productId => $productType) {
  237. $byType[$productType][$productId] = $productId;
  238. }
  239. $compositeIds = array();
  240. $notCompositeIds = array();
  241. foreach ($byType as $productType => $entityIds) {
  242. $indexer = $this->_getIndexer($productType);
  243. if ($indexer->getIsComposite()) {
  244. $compositeIds += $entityIds;
  245. } else {
  246. $notCompositeIds += $entityIds;
  247. }
  248. }
  249. if (!empty($notCompositeIds)) {
  250. $select = $write->select()
  251. ->from(
  252. array('l' => $this->getTable('catalog/product_relation')),
  253. 'parent_id')
  254. ->join(
  255. array('e' => $this->getTable('catalog/product')),
  256. 'e.entity_id = l.parent_id',
  257. array('type_id'))
  258. ->where('l.child_id IN(?)', $notCompositeIds);
  259. $pairs = $write->fetchPairs($select);
  260. foreach ($pairs as $productId => $productType) {
  261. if (!in_array($productId, $ids)) {
  262. $ids[] = $productId;
  263. $byType[$productType][$productId] = $productId;
  264. $compositeIds[$productId] = $productId;
  265. }
  266. }
  267. }
  268. if (!empty($compositeIds)) {
  269. $this->_copyRelationIndexData($compositeIds, $notCompositeIds);
  270. }
  271. $indexers = $this->getTypeIndexers();
  272. foreach ($indexers as $indexer) {
  273. if (!empty($byType[$indexer->getTypeId()])) {
  274. $indexer->reindexEntity($byType[$indexer->getTypeId()]);
  275. }
  276. }
  277. $this->_copyIndexDataToMainTable($ids);
  278. return $this;
  279. }
  280. /**
  281. * Retrieve Price indexer by Product Type
  282. *
  283. * @param string $productTypeId
  284. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Interface
  285. */
  286. protected function _getIndexer($productTypeId)
  287. {
  288. $types = $this->getTypeIndexers();
  289. if (!isset($types[$productTypeId])) {
  290. Mage::throwException(Mage::helper('catalog')->__('Unsupported product type "%s".', $productTypeId));
  291. }
  292. return $types[$productTypeId];
  293. }
  294. /**
  295. * Retrieve price indexers per product type
  296. *
  297. * @return array
  298. */
  299. public function getTypeIndexers()
  300. {
  301. if (is_null($this->_indexers)) {
  302. $this->_indexers = array();
  303. $types = Mage::getSingleton('catalog/product_type')->getTypesByPriority();
  304. foreach ($types as $typeId => $typeInfo) {
  305. if (isset($typeInfo['price_indexer'])) {
  306. $modelName = $typeInfo['price_indexer'];
  307. } else {
  308. $modelName = $this->_defaultPriceIndexer;
  309. }
  310. $isComposite = !empty($typeInfo['composite']);
  311. $indexer = Mage::getResourceModel($modelName)
  312. ->setTypeId($typeId)
  313. ->setIsComposite($isComposite);
  314. $this->_indexers[$typeId] = $indexer;
  315. }
  316. }
  317. return $this->_indexers;
  318. }
  319. /**
  320. * Rebuild all index data
  321. *
  322. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Eav
  323. */
  324. public function reindexAll()
  325. {
  326. $this->useIdxTable(true);
  327. $this->clearTemporaryIndexTable();
  328. $this->_prepareWebsiteDateTable();
  329. $this->_prepareTierPriceIndex();
  330. $indexers = $this->getTypeIndexers();
  331. foreach ($indexers as $indexer) {
  332. /* @var $indexer Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Interface */
  333. $indexer->reindexAll();
  334. }
  335. $this->syncData();
  336. return $this;
  337. }
  338. /**
  339. * Retrieve table name for product tier price index
  340. *
  341. * @return string
  342. */
  343. protected function _getTierPriceIndexTable()
  344. {
  345. return $this->getTable('catalog/product_index_tier_price');
  346. }
  347. /**
  348. * Prepare tier price index table
  349. *
  350. * @param int|array $entityIds the entity ids limitation
  351. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price
  352. */
  353. protected function _prepareTierPriceIndex($entityIds = null)
  354. {
  355. $write = $this->_getWriteAdapter();
  356. $table = $this->_getTierPriceIndexTable();
  357. $write->delete($table);
  358. $select = $write->select()
  359. ->from(
  360. array('tp' => $this->getValueTable('catalog/product', 'tier_price')),
  361. array('entity_id'))
  362. ->join(
  363. array('cg' => $this->getTable('customer/customer_group')),
  364. 'tp.all_groups = 1 OR (tp.all_groups = 0 AND tp.customer_group_id = cg.customer_group_id)',
  365. array('customer_group_id'))
  366. ->join(
  367. array('cw' => $this->getTable('core/website')),
  368. 'tp.website_id = 0 OR tp.website_id = cw.website_id',
  369. array('website_id'))
  370. ->join(
  371. array('cwd' => $this->_getWebsiteDateTable()),
  372. 'cw.website_id = cwd.website_id',
  373. array())
  374. ->where('cw.website_id != 0')
  375. ->columns(new Zend_Db_Expr('MIN(IF(tp.website_id=0, ROUND(tp.value * cwd.rate, 4), tp.value))'))
  376. ->group(array('tp.entity_id', 'cg.customer_group_id', 'cw.website_id'));
  377. if (!empty($entityIds)) {
  378. $select->where('tp.entity_id IN(?)', $entityIds);
  379. }
  380. $query = $select->insertFromSelect($table);
  381. $write->query($query);
  382. return $this;
  383. }
  384. /**
  385. * Copy relations product index from primary index to temporary index table by parent entity
  386. *
  387. * @param array|int $parentIds
  388. * @package array|int $excludeIds
  389. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price
  390. */
  391. protected function _copyRelationIndexData($parentIds, $excludeIds = null)
  392. {
  393. $write = $this->_getWriteAdapter();
  394. $select = $write->select()
  395. ->from($this->getTable('catalog/product_relation'), array('child_id'))
  396. ->where('parent_id IN(?)', $parentIds);
  397. if (!is_null($excludeIds)) {
  398. $select->where('child_id NOT IN(?)', $excludeIds);
  399. }
  400. $children = $write->fetchCol($select);
  401. if ($children) {
  402. $select = $write->select()
  403. ->from($this->getMainTable())
  404. ->where('entity_id IN(?)', $children);
  405. $query = $select->insertFromSelect($this->getIdxTable());
  406. $write->query($query);
  407. }
  408. return $this;
  409. }
  410. /**
  411. * Retrieve website current dates table name
  412. *
  413. * @return string
  414. */
  415. protected function _getWebsiteDateTable()
  416. {
  417. return $this->getTable('catalog/product_index_website');
  418. }
  419. /**
  420. * Prepare website current dates table
  421. *
  422. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Indexer_Price_Default
  423. */
  424. protected function _prepareWebsiteDateTable()
  425. {
  426. $write = $this->_getWriteAdapter();
  427. $baseCurrency = Mage::app()->getBaseCurrencyCode();
  428. $select = $write->select()
  429. ->from(
  430. array('cw' => $this->getTable('core/website')),
  431. array('website_id'))
  432. ->join(
  433. array('csg' => $this->getTable('core/store_group')),
  434. 'cw.default_group_id = csg.group_id',
  435. array('store_id' => 'default_store_id'))
  436. ->where('cw.website_id != 0')
  437. ->group('cw.website_id');
  438. $data = array();
  439. foreach ($write->fetchAll($select) as $item) {
  440. /* @var $website Mage_Core_Model_Website */
  441. $website = Mage::app()->getWebsite($item['website_id']);
  442. if ($website->getBaseCurrencyCode() != $baseCurrency) {
  443. $rate = Mage::getModel('directory/currency')
  444. ->load($baseCurrency)
  445. ->getRate($website->getBaseCurrencyCode());
  446. if (!$rate) {
  447. $rate = 1;
  448. }
  449. } else {
  450. $rate = 1;
  451. }
  452. /* @var $store Mage_Core_Model_Store */
  453. $store = Mage::app()->getStore($item['store_id']);
  454. if ($store) {
  455. $timestamp = Mage::app()->getLocale()->storeTimeStamp($store);
  456. $data[] = array(
  457. 'website_id' => $website->getId(),
  458. 'date' => $this->formatDate($timestamp, false),
  459. 'rate' => $rate
  460. );
  461. }
  462. }
  463. $write->beginTransaction();
  464. $table = $this->_getWebsiteDateTable();
  465. $write->delete($table);
  466. if ($data) {
  467. $write->insertMultiple($table, $data);
  468. }
  469. $write->commit();
  470. return $this;
  471. }
  472. /**
  473. * Retrieve temporary index table name
  474. *
  475. * @return string
  476. */
  477. public function getIdxTable($table = null)
  478. {
  479. if ($this->useIdxTable()) {
  480. return $this->getTable('catalog/product_price_indexer_idx');
  481. }
  482. return $this->getTable('catalog/product_price_indexer_tmp');
  483. }
  484. }