PageRenderTime 55ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Url.php

https://bitbucket.org/claudiu_marginean/magento-hg-mirror
PHP | 1262 lines | 797 code | 125 blank | 340 comment | 120 complexity | 82e072c049d4a8cda9fc1a698d8d30f9 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 url rewrite resource model
  28. *
  29. * @category Mage
  30. * @package Mage_Catalog
  31. * @author Magento Core Team <core@magentocommerce.com>
  32. */
  33. class Mage_Catalog_Model_Resource_Eav_Mysql4_Url extends Mage_Core_Model_Mysql4_Abstract
  34. {
  35. /**
  36. * Stores configuration array
  37. *
  38. * @var array
  39. */
  40. protected $_stores;
  41. /**
  42. * Category attribute properties cache
  43. *
  44. * @var array
  45. */
  46. protected $_categoryAttributes = array();
  47. /**
  48. * Product attribute properties cache
  49. *
  50. * @var array
  51. */
  52. protected $_productAttributes = array();
  53. /**
  54. * Limit products for select
  55. *
  56. * @var int
  57. */
  58. protected $_productLimit = 250;
  59. /**
  60. * Cache of root category children ids
  61. *
  62. * @var array
  63. */
  64. protected $_rootChildrenIds = array();
  65. /**
  66. * Load core Url rewrite model
  67. *
  68. */
  69. protected function _construct()
  70. {
  71. $this->_init('core/url_rewrite', 'url_rewrite_id');
  72. }
  73. /**
  74. * Retrieve stores array or store model
  75. *
  76. * @param int $storeId
  77. * @return Mage_Core_Model_Store|array
  78. */
  79. public function getStores($storeId = null)
  80. {
  81. if (is_null($this->_stores)) {
  82. $this->_stores = $this->_prepareStoreRootCategories(Mage::app()->getStores());
  83. }
  84. if ($storeId && isset($this->_stores[$storeId])) {
  85. return $this->_stores[$storeId];
  86. }
  87. return $this->_stores;
  88. }
  89. /**
  90. * Retrieve Category model singleton
  91. *
  92. * @return Mage_Catalog_Model_Category
  93. */
  94. public function getCategoryModel()
  95. {
  96. return Mage::getSingleton('catalog/category');
  97. }
  98. /**
  99. * Retrieve product model singleton
  100. *
  101. * @return Mage_Catalog_Model_Product
  102. */
  103. public function getProductModel()
  104. {
  105. return Mage::getSingleton('catalog/product');
  106. }
  107. /**
  108. * Retrieve rewrite by idPath
  109. *
  110. * @param string $idPath
  111. * @return Varien_Object
  112. */
  113. public function getRewriteByIdPath($idPath, $storeId)
  114. {
  115. $select = $this->_getWriteAdapter()->select()
  116. ->from($this->getMainTable())
  117. ->where('store_id=?', $storeId)
  118. ->where('id_path=?', $idPath);
  119. $row = $this->_getWriteAdapter()->fetchRow($select);
  120. if (!$row) {
  121. return false;
  122. }
  123. $rewrite = new Varien_Object($row);
  124. $rewrite->setIdFieldName($this->getIdFieldName());
  125. return $rewrite;
  126. }
  127. /**
  128. * Retrieve rewrite by requestPath
  129. *
  130. * @param string $requestPath
  131. * @return Varien_Object
  132. */
  133. public function getRewriteByRequestPath($requestPath, $storeId)
  134. {
  135. $select = $this->_getWriteAdapter()->select()
  136. ->from($this->getMainTable())
  137. ->where('store_id=?', $storeId)
  138. ->where('request_path=?', $requestPath);
  139. $row = $this->_getWriteAdapter()->fetchRow($select);
  140. if (!$row) {
  141. return false;
  142. }
  143. $rewrite = new Varien_Object($row);
  144. $rewrite->setIdFieldName($this->getIdFieldName());
  145. return $rewrite;
  146. }
  147. /**
  148. * Validate array of request paths. Return first not used path in case if validations passed
  149. *
  150. * @param array $paths
  151. * @param int $storeId
  152. * @return false | string
  153. */
  154. public function checkRequestPaths($paths, $storeId)
  155. {
  156. $select = $this->_getWriteAdapter()->select()
  157. ->from($this->getMainTable(), 'request_path')
  158. ->where('store_id=?', $storeId)
  159. ->where('request_path IN (?)', $paths);
  160. $data = $this->_getWriteAdapter()->fetchCol($select);
  161. $paths = array_diff($paths, $data);
  162. if (empty($paths)) {
  163. return false;
  164. } else {
  165. reset($paths);
  166. return current($paths);
  167. }
  168. }
  169. /**
  170. * Prepare rewrites for condition
  171. *
  172. * @param int $storeId
  173. * @param int|array $categoryIds
  174. * @param int|array $productIds
  175. * @return array
  176. */
  177. public function prepareRewrites($storeId, $categoryIds = null, $productIds = null)
  178. {
  179. $rewrites = array();
  180. $select = $this->_getWriteAdapter()->select()
  181. ->from($this->getMainTable())
  182. ->where('store_id=?', $storeId)
  183. ->where('is_system=?', 1);
  184. if (is_null($categoryIds)) {
  185. $select->where('category_id IS NULL');
  186. }
  187. elseif ($categoryIds) {
  188. $catIds = is_array($categoryIds) ? $categoryIds : array($categoryIds);
  189. // Check maybe we request products and root category id is within categoryIds,
  190. // it's a separate case because root category products are stored with NULL categoryId
  191. if ($productIds) {
  192. $addNullCategory = in_array($this->getStores($storeId)->getRootCategoryId(), $catIds);
  193. } else {
  194. $addNullCategory = false;
  195. }
  196. // Compose optimal condition
  197. if ($addNullCategory) {
  198. $select->where('category_id IN(?) OR category_id IS NULL', $catIds);
  199. } else {
  200. $select->where('category_id IN(?)', $catIds);
  201. }
  202. }
  203. if (is_null($productIds)) {
  204. $select->where('product_id IS NULL');
  205. }
  206. elseif ($productIds) {
  207. $select->where('product_id IN(?)', $productIds);
  208. }
  209. $query = $this->_getWriteAdapter()->query((string)$select);
  210. while ($row = $query->fetch()) {
  211. $rewrite = new Varien_Object($row);
  212. $rewrite->setIdFieldName($this->getIdFieldName());
  213. $rewrites[$rewrite->getIdPath()] = $rewrite;
  214. }
  215. unset($query);
  216. return $rewrites;
  217. }
  218. /**
  219. * Save rewrite URL
  220. *
  221. * @param array $rewriteData
  222. * @param Varien_Object $rewriteObject
  223. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Url
  224. */
  225. public function saveRewrite($rewriteData, $rewrite)
  226. {
  227. if ($rewrite && $rewrite->getId()) {
  228. if ($rewriteData['request_path'] != $rewrite->getRequestPath()) {
  229. $where = $this->_getWriteAdapter()->quoteInto($this->getIdFieldName() . '=?', $rewrite->getId());
  230. $this->_getWriteAdapter()->update(
  231. $this->getMainTable(),
  232. $rewriteData,
  233. $where
  234. );
  235. // Update existing rewrites history and avoid chain redirects
  236. $where = $this->_getWriteAdapter()->quoteInto('target_path=?', $rewrite->getRequestPath());
  237. if ($rewrite->getStoreId()) {
  238. $where .= $this->_getWriteAdapter()->quoteInto(' AND store_id=?', $rewrite->getStoreId());
  239. }
  240. $this->_getWriteAdapter()->update(
  241. $this->getMainTable(),
  242. array('target_path' => $rewriteData['request_path']),
  243. $where
  244. );
  245. }
  246. }
  247. else {
  248. try {
  249. $this->_getWriteAdapter()->insert($this->getMainTable(), $rewriteData);
  250. }
  251. catch (Exception $e) {
  252. Mage::logException($e);
  253. Mage::throwException(Mage::helper('catalog')->__('An error occurred while saving the URL rewrite.'));
  254. }
  255. }
  256. unset($rewriteData);
  257. return $this;
  258. }
  259. public function saveRewriteHistory($rewriteData)
  260. {
  261. $rewriteData = new Varien_Object($rewriteData);
  262. // check if rewrite exists with save request_path
  263. $rewrite = $this->getRewriteByRequestPath($rewriteData->getRequestPath(), $rewriteData->getStoreId());
  264. if ($rewrite === false) {
  265. // create permanent redirect
  266. $this->_getWriteAdapter()->insert($this->getMainTable(), $rewriteData->getData());
  267. }
  268. return $this;
  269. }
  270. /**
  271. * Save category attribute
  272. *
  273. * @param Varien_Object $category
  274. * @param string $attributeCode
  275. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Url
  276. */
  277. public function saveCategoryAttribute(Varien_Object $category, $attributeCode)
  278. {
  279. if (!isset($this->_categoryAttributes[$attributeCode])) {
  280. $attribute = $this->getCategoryModel()->getResource()->getAttribute($attributeCode);
  281. $this->_categoryAttributes[$attributeCode] = array(
  282. 'entity_type_id' => $attribute->getEntityTypeId(),
  283. 'attribute_id' => $attribute->getId(),
  284. 'table' => $attribute->getBackend()->getTable(),
  285. 'is_global' => $attribute->getIsGlobal()
  286. );
  287. unset($attribute);
  288. }
  289. $attributeTable = $this->_categoryAttributes[$attributeCode]['table'];
  290. $attributeData = array(
  291. 'entity_type_id' => $this->_categoryAttributes[$attributeCode]['entity_type_id'],
  292. 'attribute_id' => $this->_categoryAttributes[$attributeCode]['attribute_id'],
  293. 'store_id' => $category->getStoreId(),
  294. 'entity_id' => $category->getId(),
  295. 'value' => $category->getData($attributeCode)
  296. );
  297. if ($this->_categoryAttributes[$attributeCode]['is_global'] || $category->getStoreId() == 0) {
  298. $attributeData['store_id'] = 0;
  299. }
  300. $select = $this->_getWriteAdapter()->select()
  301. ->from($attributeTable)
  302. ->where('entity_type_id=?', $attributeData['entity_type_id'])
  303. ->where('attribute_id=?', $attributeData['attribute_id'])
  304. ->where('store_id=?', $attributeData['store_id'])
  305. ->where('entity_id=?', $attributeData['entity_id']);
  306. if ($row = $this->_getWriteAdapter()->fetchRow($select)) {
  307. $whereCond = $this->_getWriteAdapter()->quoteInto('value_id=?', $row['value_id']);
  308. $this->_getWriteAdapter()->update($attributeTable, $attributeData, $whereCond);
  309. }
  310. else {
  311. $this->_getWriteAdapter()->insert($attributeTable, $attributeData);
  312. }
  313. if ($attributeData['store_id'] != 0) {
  314. $attributeData['store_id'] = 0;
  315. $select = $this->_getWriteAdapter()->select()
  316. ->from($attributeTable)
  317. ->where('entity_type_id=?', $attributeData['entity_type_id'])
  318. ->where('attribute_id=?', $attributeData['attribute_id'])
  319. ->where('store_id=?', $attributeData['store_id'])
  320. ->where('entity_id=?', $attributeData['entity_id']);
  321. if ($row = $this->_getWriteAdapter()->fetchRow($select)) {
  322. $whereCond = $this->_getWriteAdapter()->quoteInto('value_id=?', $row['value_id']);
  323. $this->_getWriteAdapter()->update($attributeTable, $attributeData, $whereCond);
  324. }
  325. else {
  326. $this->_getWriteAdapter()->insert($attributeTable, $attributeData);
  327. }
  328. }
  329. unset($attributeData);
  330. return $this;
  331. }
  332. /**
  333. * Retrieve category attributes
  334. *
  335. * @param string $attributeCode
  336. * @param int|array $categoryIds
  337. * @param int $storeId
  338. * @return array
  339. */
  340. protected function _getCategoryAttribute($attributeCode, $categoryIds, $storeId)
  341. {
  342. if (!isset($this->_categoryAttributes[$attributeCode])) {
  343. $attribute = $this->getCategoryModel()->getResource()->getAttribute($attributeCode);
  344. $this->_categoryAttributes[$attributeCode] = array(
  345. 'entity_type_id' => $attribute->getEntityTypeId(),
  346. 'attribute_id' => $attribute->getId(),
  347. 'table' => $attribute->getBackend()->getTable(),
  348. 'is_global' => $attribute->getIsGlobal(),
  349. 'is_static' => $attribute->isStatic()
  350. );
  351. unset($attribute);
  352. }
  353. if (!is_array($categoryIds)) {
  354. $categoryIds = array($categoryIds);
  355. }
  356. $attributeTable = $this->_categoryAttributes[$attributeCode]['table'];
  357. if ($this->_categoryAttributes[$attributeCode]['is_static']) {
  358. $select = $this->_getWriteAdapter()->select()
  359. ->from(
  360. $this->getTable('catalog/category'),
  361. array('value'=>$attributeCode, 'entity_id'=>'entity_id')
  362. )
  363. ->where('entity_id IN(?)', $categoryIds);
  364. } elseif ($this->_categoryAttributes[$attributeCode]['is_global'] || $storeId == 0) {
  365. $select = $this->_getWriteAdapter()->select()
  366. ->from($attributeTable, array('entity_id', 'value'))
  367. ->where('attribute_id = ?', $this->_categoryAttributes[$attributeCode]['attribute_id'])
  368. ->where('store_id=?', 0)
  369. ->where('entity_id IN(?)', $categoryIds);
  370. } else {
  371. $select = $this->_getWriteAdapter()->select()
  372. ->from(array('t1'=>$attributeTable), array('entity_id', 'IF(t2.value_id>0, t2.value, t1.value) as value'))
  373. ->joinLeft(
  374. array('t2'=>$attributeTable),
  375. $this->_getWriteAdapter()->quoteInto('t1.entity_id = t2.entity_id AND t1.attribute_id = t2.attribute_id AND t2.store_id=?', $storeId),
  376. array()
  377. )
  378. ->where('t1.store_id = ?', 0)
  379. ->where('t1.attribute_id = ?', $this->_categoryAttributes[$attributeCode]['attribute_id'])
  380. ->where('t1.entity_id IN(?)', $categoryIds);
  381. }
  382. $rowSet = $this->_getWriteAdapter()->fetchAll($select);
  383. $attributes = array();
  384. foreach ($rowSet as $row) {
  385. $attributes[$row['entity_id']] = $row['value'];
  386. }
  387. unset($rowSet);
  388. foreach ($categoryIds as $categoryId) {
  389. if (!isset($attributes[$categoryId])) {
  390. $attributes[$categoryId] = null;
  391. }
  392. }
  393. return $attributes;
  394. }
  395. /**
  396. * Save product attribute
  397. *
  398. * @param Varien_Object $product
  399. * @param string $attributeCode
  400. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Url
  401. */
  402. public function saveProductAttribute(Varien_Object $product, $attributeCode)
  403. {
  404. if (!isset($this->_productAttributes[$attributeCode])) {
  405. $attribute = $this->getProductModel()->getResource()->getAttribute($attributeCode);
  406. $this->_productAttributes[$attributeCode] = array(
  407. 'entity_type_id' => $attribute->getEntityTypeId(),
  408. 'attribute_id' => $attribute->getId(),
  409. 'table' => $attribute->getBackend()->getTable(),
  410. 'is_global' => $attribute->getIsGlobal()
  411. );
  412. unset($attribute);
  413. }
  414. $attributeTable = $this->_productAttributes[$attributeCode]['table'];
  415. $attributeData = array(
  416. 'entity_type_id' => $this->_productAttributes[$attributeCode]['entity_type_id'],
  417. 'attribute_id' => $this->_productAttributes[$attributeCode]['attribute_id'],
  418. 'store_id' => $product->getStoreId(),
  419. 'entity_id' => $product->getId(),
  420. 'value' => $product->getData($attributeCode)
  421. );
  422. if ($this->_productAttributes[$attributeCode]['is_global'] || $product->getStoreId() == 0) {
  423. $attributeData['store_id'] = 0;
  424. }
  425. $select = $this->_getWriteAdapter()->select()
  426. ->from($attributeTable)
  427. ->where('entity_type_id=?', $attributeData['entity_type_id'])
  428. ->where('attribute_id=?', $attributeData['attribute_id'])
  429. ->where('store_id=?', $attributeData['store_id'])
  430. ->where('entity_id=?', $attributeData['entity_id']);
  431. if ($row = $this->_getWriteAdapter()->fetchRow($select)) {
  432. $whereCond = $this->_getWriteAdapter()->quoteInto('value_id=?', $row['value_id']);
  433. $this->_getWriteAdapter()->update($attributeTable, $attributeData, $whereCond);
  434. }
  435. else {
  436. $this->_getWriteAdapter()->insert($attributeTable, $attributeData);
  437. }
  438. if ($attributeData['store_id'] != 0) {
  439. $attributeData['store_id'] = 0;
  440. $select = $this->_getWriteAdapter()->select()
  441. ->from($attributeTable)
  442. ->where('entity_type_id=?', $attributeData['entity_type_id'])
  443. ->where('attribute_id=?', $attributeData['attribute_id'])
  444. ->where('store_id=?', $attributeData['store_id'])
  445. ->where('entity_id=?', $attributeData['entity_id']);
  446. if ($row = $this->_getWriteAdapter()->fetchRow($select)) {
  447. $whereCond = $this->_getWriteAdapter()->quoteInto('value_id=?', $row['value_id']);
  448. $this->_getWriteAdapter()->update($attributeTable, $attributeData, $whereCond);
  449. }
  450. else {
  451. $this->_getWriteAdapter()->insert($attributeTable, $attributeData);
  452. }
  453. }
  454. unset($attributeData);
  455. return $this;
  456. }
  457. /**
  458. * Retrieve product attribute
  459. *
  460. * @param string $attributeCode
  461. * @param int|array $productIds
  462. * @param string $storeId
  463. * @return array
  464. */
  465. public function _getProductAttribute($attributeCode, $productIds, $storeId)
  466. {
  467. if (!isset($this->_productAttributes[$attributeCode])) {
  468. $attribute = $this->getProductModel()->getResource()->getAttribute($attributeCode);
  469. $this->_productAttributes[$attributeCode] = array(
  470. 'entity_type_id' => $attribute->getEntityTypeId(),
  471. 'attribute_id' => $attribute->getId(),
  472. 'table' => $attribute->getBackend()->getTable(),
  473. 'is_global' => $attribute->getIsGlobal()
  474. );
  475. unset($attribute);
  476. }
  477. if (!is_array($productIds)) {
  478. $productIds = array($productIds);
  479. }
  480. $attributeTable = $this->_productAttributes[$attributeCode]['table'];
  481. if ($this->_productAttributes[$attributeCode]['is_global'] || $storeId == 0) {
  482. $select = $this->_getWriteAdapter()->select()
  483. ->from($attributeTable, array('entity_id', 'value'))
  484. ->where('attribute_id = ?', $this->_productAttributes[$attributeCode]['attribute_id'])
  485. ->where('store_id=?', 0)
  486. ->where('entity_id IN(?)', $productIds);
  487. }
  488. else {
  489. $select = $this->_getWriteAdapter()->select()
  490. ->from(array('t1'=>$attributeTable), array('entity_id', 'IF(t2.value_id>0, t2.value, t1.value) as value'))
  491. ->joinLeft(
  492. array('t2'=>$attributeTable),
  493. $this->_getWriteAdapter()->quoteInto('t1.entity_id = t2.entity_id AND t1.attribute_id = t2.attribute_id AND t2.store_id=?', $storeId),
  494. array()
  495. )
  496. ->where('t1.store_id = ?', 0)
  497. ->where('t1.attribute_id = ?', $this->_productAttributes[$attributeCode]['attribute_id'])
  498. ->where('t1.entity_id IN(?)', $productIds);
  499. }
  500. $rowSet = $this->_getWriteAdapter()->fetchAll($select);
  501. $attributes = array();
  502. foreach ($rowSet as $row) {
  503. $attributes[$row['entity_id']] = $row['value'];
  504. }
  505. unset($rowSet);
  506. foreach ($productIds as $productIds) {
  507. if (!isset($attributes[$productIds])) {
  508. $attributes[$productIds] = null;
  509. }
  510. }
  511. return $attributes;
  512. }
  513. /**
  514. * Prepare category parentId
  515. *
  516. * @param Varien_Object $category
  517. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Url
  518. */
  519. protected function _prepareCategoryParentId(Varien_Object $category)
  520. {
  521. if ($category->getPath() != $category->getId()) {
  522. $split = explode('/', $category->getPath());
  523. $category->setParentId($split[(count($split) - 2)]);
  524. }
  525. else {
  526. $category->setParentId(0);
  527. }
  528. return $this;
  529. }
  530. /**
  531. * Prepare stores root categories
  532. *
  533. * @param array $stores
  534. * @return array
  535. */
  536. protected function _prepareStoreRootCategories($stores)
  537. {
  538. $rootCategoryIds = array();
  539. foreach ($stores as $store) {
  540. /* @var $store Mage_Core_Model_Store */
  541. $rootCategoryIds[$store->getRootCategoryId()] = $store->getRootCategoryId();
  542. }
  543. if ($rootCategoryIds) {
  544. $categories = $this->_getCategories($rootCategoryIds);
  545. }
  546. foreach ($stores as $store) {
  547. /* @var $store Mage_Core_Model_Store */
  548. if (isset($categories[$store->getRootCategoryId()])) {
  549. $store->setRootCategoryPath($categories[$store->getRootCategoryId()]->getPath());
  550. $store->setRootCategory($categories[$store->getRootCategoryId()]);
  551. }
  552. else {
  553. unset($stores[$store->getId()]);
  554. }
  555. }
  556. return $stores;
  557. }
  558. /**
  559. * Retrieve categories objects
  560. * Either $categoryIds or $path (with ending slash) must be specified
  561. *
  562. * @param int|array $categoryIds
  563. * @param int $storeId
  564. * @param string $path
  565. * @return array
  566. */
  567. protected function _getCategories($categoryIds, $storeId = null, $path = null)
  568. {
  569. $isActiveAttribute = Mage::getSingleton('eav/config')->getAttribute('catalog_category', 'is_active');
  570. $categories = array();
  571. if (!is_array($categoryIds)) {
  572. $categoryIds = array($categoryIds);
  573. }
  574. $select = $this->_getWriteAdapter()->select()
  575. ->from(array('main_table' => $this->getTable('catalog/category')), array(
  576. 'main_table.entity_id',
  577. 'main_table.parent_id',
  578. 'main_table.level',
  579. 'is_active' => 'IF(c.value_id>0, c.value, d.value)',
  580. 'main_table.path'));
  581. if (is_null($path)) {
  582. $select->where('main_table.entity_id IN(?)', $categoryIds);
  583. } else {
  584. // Ensure that path ends with '/', otherwise we can get wrong results - e.g. $path = '1/2' will get '1/20'
  585. if (substr($path, -1) != '/') {
  586. $path .= '/';
  587. }
  588. $select
  589. ->where('main_table.path LIKE ?', $path . '%')
  590. ->order('main_table.path');
  591. }
  592. $table = $this->getTable('catalog/category') . '_int';
  593. $select->joinLeft(array('d'=>$table), "d.attribute_id = '{$isActiveAttribute->getId()}' AND d.store_id = 0 AND d.entity_id = main_table.entity_id", array())
  594. ->joinLeft(array('c'=>$table), "c.attribute_id = '{$isActiveAttribute->getId()}' AND c.store_id = '{$storeId}' AND c.entity_id = main_table.entity_id", array());
  595. // Prepare variables for checking whether categories belong to store
  596. if (!is_null($storeId)) {
  597. $rootCategoryPath = $this->getStores($storeId)->getRootCategoryPath();
  598. $rootCategoryPathLength = strlen($rootCategoryPath);
  599. }
  600. $rowSet = $this->_getWriteAdapter()->fetchAll($select);
  601. foreach ($rowSet as $row) {
  602. if (!is_null($storeId)) {
  603. // Check the category to be either store's root or its descendant
  604. // First - check that category's start is the same as root category
  605. if (substr($row['path'], 0, $rootCategoryPathLength) != $rootCategoryPath) {
  606. continue;
  607. }
  608. // Second - check non-root category - that it's really a descendant, not a simple string match
  609. if ((strlen($row['path']) > $rootCategoryPathLength) && ($row['path'][$rootCategoryPathLength] != '/')) {
  610. continue;
  611. }
  612. }
  613. $category = new Varien_Object($row);
  614. $category->setIdFieldName('entity_id');
  615. $category->setStoreId($storeId);
  616. $this->_prepareCategoryParentId($category);
  617. $categories[$category->getId()] = $category;
  618. }
  619. unset($rowSet);
  620. if (!is_null($storeId) && $categories) {
  621. foreach (array('name', 'url_key', 'url_path') as $attributeCode) {
  622. $attributes = $this->_getCategoryAttribute($attributeCode, array_keys($categories), $category->getStoreId());
  623. foreach ($attributes as $categoryId => $attributeValue) {
  624. $categories[$categoryId]->setData($attributeCode, $attributeValue);
  625. }
  626. }
  627. }
  628. return $categories;
  629. }
  630. /**
  631. * Retrieve category data object
  632. *
  633. * @param int $categoryId
  634. * @param int $storeId
  635. * @return Varien_Object
  636. */
  637. public function getCategory($categoryId, $storeId)
  638. {
  639. if (!$categoryId || !$storeId) {
  640. return false;
  641. }
  642. $categories = $this->_getCategories($categoryId, $storeId);
  643. if (isset($categories[$categoryId])) {
  644. return $categories[$categoryId];
  645. }
  646. return false;
  647. }
  648. /**
  649. * Retrieve categories data objects by their ids. Return only categories that belong to specified store.
  650. *
  651. * @param int|array $categoryIds
  652. * @param int $storeId
  653. * @return array
  654. */
  655. public function getCategories($categoryIds, $storeId)
  656. {
  657. if (!$categoryIds || !$storeId) {
  658. return false;
  659. }
  660. return $this->_getCategories($categoryIds, $storeId);
  661. }
  662. /**
  663. * Retrieve category childs data objects
  664. *
  665. * @param Varien_Object $category
  666. * @return Varien_Object
  667. */
  668. public function loadCategoryChilds(Varien_Object $category)
  669. {
  670. if (is_null($category->getId()) || is_null($category->getStoreId())) {
  671. return $category;
  672. }
  673. $categories = $this->_getCategories(null, $category->getStoreId(), $category->getPath() . '/');
  674. $category->setChilds(array());
  675. foreach ($categories as $child) {
  676. if (!is_array($child->getChilds())) {
  677. $child->setChilds(array());
  678. }
  679. if ($child->getParentId() == $category->getId()) {
  680. $category->setChilds($category->getChilds() + array($child->getId() => $child));
  681. }
  682. else {
  683. if (isset($categories[$child->getParentId()])) {
  684. if (!is_array($categories[$child->getParentId()]->getChilds())) {
  685. $categories[$child->getParentId()]->setChilds(array());
  686. }
  687. $categories[$child->getParentId()]->setChilds($categories[$child->getParentId()]->getChilds() + array($child->getId() => $child));
  688. }
  689. }
  690. }
  691. $category->setAllChilds($categories);
  692. return $category;
  693. }
  694. /**
  695. * Retrieves all children ids of root category tree
  696. * Actually this routine can be used to get children ids of any category, not only root.
  697. * But as far as result is cached in memory, it's not recommended to do so.
  698. *
  699. * @param Varien_Object $category
  700. * @return Varien_Object
  701. */
  702. public function getRootChildrenIds($categoryId, $categoryPath, $includeStart = true)
  703. {
  704. if (!isset($this->_rootChildrenIds[$categoryId])) {
  705. // Select all descedant category ids
  706. $adapter = $this->_getReadAdapter();
  707. $select = $adapter->select()
  708. ->from(array($this->getTable('catalog/category')), array('entity_id'))
  709. ->where('path LIKE ?', $categoryPath . '/%');
  710. $categoryIds = array();
  711. $rowSet = $adapter->fetchAll($select);
  712. foreach ($rowSet as $row) {
  713. $categoryIds[$row['entity_id']] = $row['entity_id'];
  714. }
  715. $this->_rootChildrenIds[$categoryId] = $categoryIds;
  716. }
  717. $categoryIds = $this->_rootChildrenIds[$categoryId];
  718. if ($includeStart) {
  719. $categoryIds[$categoryId] = $categoryId;
  720. }
  721. return $categoryIds;
  722. }
  723. /**
  724. * Retrieve category parent path
  725. *
  726. * @param Varien_Object $category
  727. * @return string
  728. */
  729. public function getCategoryParentPath(Varien_Object $category)
  730. {
  731. $store = Mage::app()->getStore($category->getStoreId());
  732. if ($category->getId() == $store->getRootCategoryId()) {
  733. return '';
  734. }
  735. elseif ($category->getParentId() == 1 || $category->getParentId() == $store->getRootCategoryId()) {
  736. return '';
  737. }
  738. else {
  739. $parentCategory = $this->getCategory($category->getParentId(), $store->getId());
  740. return $parentCategory->getUrlPath() . '/';
  741. }
  742. }
  743. /**
  744. * Retrieve product ids by category
  745. *
  746. * @param Varien_Object|int $category
  747. * @return array
  748. */
  749. public function getProductIdsByCategory($category)
  750. {
  751. $productIds = array();
  752. if ($category instanceof Varien_Object) {
  753. $categoryId = $category->getId();
  754. }
  755. else {
  756. $categoryId = $category;
  757. }
  758. $select = $this->_getWriteAdapter()->select()
  759. ->from($this->getTable('catalog/category_product'))
  760. ->where('category_id=?', $categoryId)
  761. ->order('product_id');
  762. $rowSet = $this->_getWriteAdapter()->fetchAll($select);
  763. foreach ($rowSet as $row) {
  764. $productIds[$row['product_id']] = $row['product_id'];
  765. }
  766. return $productIds;
  767. }
  768. /**
  769. * Retrieve Product data objects
  770. *
  771. * @param int|array $productIds
  772. * @param int $storeId
  773. * @param int $entityId
  774. * @param int $lastEntityId
  775. * @return array
  776. */
  777. protected function _getProducts($productIds = null, $storeId, $entityId = 0, &$lastEntityId)
  778. {
  779. $products = array();
  780. $websiteId = Mage::app()->getStore($storeId)->getWebsiteId();
  781. if (!is_null($productIds)) {
  782. if (!is_array($productIds)) {
  783. $productIds = array($productIds);
  784. }
  785. }
  786. $select = $this->_getWriteAdapter()->select()
  787. ->useStraightJoin(true)
  788. ->from(array('e' => $this->getTable('catalog/product')), array('entity_id'))
  789. ->join(
  790. array('w' => $this->getTable('catalog/product_website')),
  791. $this->_getWriteAdapter()->quoteInto('e.entity_id=w.product_id AND w.website_id=?', $websiteId),
  792. array()
  793. )
  794. ->where('e.entity_id>?', $entityId)
  795. ->order('e.entity_id')
  796. ->limit($this->_productLimit);
  797. if (!is_null($productIds)) {
  798. $select->where('e.entity_id IN(?)', $productIds);
  799. }
  800. $query = $this->_getWriteAdapter()->query($select);
  801. while ($row = $query->fetch()) {
  802. $product = new Varien_Object($row);
  803. $product->setIdFieldName('entity_id');
  804. $product->setCategoryIds(array());
  805. $product->setStoreId($storeId);
  806. $products[$product->getId()] = $product;
  807. $lastEntityId = $product->getId();
  808. }
  809. unset($query);
  810. if ($products) {
  811. $select = $this->_getReadAdapter()->select()
  812. ->from(
  813. $this->getTable('catalog/category_product'),
  814. array('product_id', 'category_id'))
  815. ->where('product_id IN(?)', array_keys($products));
  816. $categories = $this->_getReadAdapter()->fetchAll($select);
  817. foreach ($categories as $category) {
  818. $productId = $category['product_id'];
  819. $categoryIds = $products[$productId]->getCategoryIds();
  820. $categoryIds[] = $category['category_id'];
  821. $products[$productId]->setCategoryIds($categoryIds);
  822. }
  823. foreach (array('name', 'url_key', 'url_path') as $attributeCode) {
  824. $attributes = $this->_getProductAttribute($attributeCode, array_keys($products), $storeId);
  825. foreach ($attributes as $productId => $attributeValue) {
  826. $products[$productId]->setData($attributeCode, $attributeValue);
  827. }
  828. }
  829. }
  830. return $products;
  831. }
  832. /**
  833. * Retrieve Product data object
  834. *
  835. * @param int $productId
  836. * @param int $storeId
  837. * @return Varien_Object
  838. */
  839. public function getProduct($productId, $storeId)
  840. {
  841. $lastId = 0;
  842. $products = $this->_getProducts($productId, $storeId, 0, $lastId);
  843. if (isset($products[$productId])) {
  844. return $products[$productId];
  845. }
  846. return false;
  847. }
  848. /**
  849. * Retrieve Product data obects for store
  850. *
  851. * @param int $storeId
  852. * @param int $lastEntityId
  853. * @return array
  854. */
  855. public function getProductsByStore($storeId, &$lastEntityId)
  856. {
  857. return $this->_getProducts(null, $storeId, $lastEntityId, $lastEntityId);
  858. }
  859. /**
  860. * Retrieve Product data objects in category
  861. *
  862. * @param Varien_Object $category
  863. * @param int $lastEntityId
  864. * @return array
  865. */
  866. public function getProductsByCategory(Varien_Object $category, &$lastEntityId)
  867. {
  868. $productIds = $this->getProductIdsByCategory($category);
  869. if (!$productIds) {
  870. return array();
  871. }
  872. return $this->_getProducts($productIds, $category->getStoreId(), $lastEntityId, $lastEntityId);
  873. }
  874. /**
  875. * Find and remove unused products rewrites - a case when products were moved away from the category
  876. * (either to other category or deleted), so rewrite "category_id-product_id" is invalid
  877. *
  878. * @param int $storeId
  879. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Url
  880. */
  881. public function clearCategoryProduct($storeId)
  882. {
  883. $select = $this->_getWriteAdapter()->select()
  884. ->from(array('tur' => $this->getMainTable()), $this->getIdFieldName())
  885. ->joinLeft(
  886. array('tcp' => $this->getTable('catalog/category_product')),
  887. 'tur.category_id=tcp.category_id AND tur.product_id=tcp.product_id',
  888. array()
  889. )->where('tur.store_id=?', $storeId)
  890. ->where('tur.category_id IS NOT NULL')
  891. ->where('tur.product_id IS NOT NULL')
  892. ->where('tcp.category_id IS NULL');
  893. $rowSet = $this->_getWriteAdapter()->fetchAll($select);
  894. $rewriteIds = array();
  895. foreach ($rowSet as $row) {
  896. $rewriteIds[] = $row[$this->getIdFieldName()];
  897. }
  898. if ($rewriteIds) {
  899. $where = $this->_getWriteAdapter()->quoteInto($this->getIdFieldName() . ' IN(?)', $rewriteIds);
  900. $this->_getWriteAdapter()->delete($this->getMainTable(), $where);
  901. }
  902. return $this;
  903. }
  904. /**
  905. * Remove unused rewrites for product - called after we created all needed rewrites for product and know the categories
  906. * where the product is contained ($excludeCategoryIds), so we can remove all invalid product rewrites that have other category ids
  907. *
  908. * Notice: this routine is not identical to clearCategoryProduct(), because after checking all categories this one removes rewrites
  909. * for product still contained within categories.
  910. *
  911. * @param int $productId Product entity Id
  912. * @param int $storeId Store Id for rewrites
  913. * @param array $excludeCategoryIds Array of category Ids that should be skipped
  914. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Url
  915. */
  916. public function clearProductRewrites($productId, $storeId, $excludeCategoryIds = array())
  917. {
  918. $adapter = $this->_getWriteAdapter();
  919. $where = $adapter->quoteInto('product_id=?', $productId);
  920. $where.= $adapter->quoteInto(' AND store_id=?', $storeId);
  921. if (!empty($excludeCategoryIds)) {
  922. $where.= $adapter->quoteInto(' AND category_id NOT IN (?)', $excludeCategoryIds);
  923. $where.= ' AND category_id IS NOT NULL'; // If there's at least one category to skip, also skip root category, because product belongs to website
  924. }
  925. $adapter->delete($this->getMainTable(), $where);
  926. return $this;
  927. }
  928. /**
  929. * Finds and deletes all old category and category/product rewrites for store
  930. * left from the times when categories/products belonged to store
  931. *
  932. * @param int $storeId
  933. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Url
  934. */
  935. public function clearStoreCategoriesInvalidRewrites($storeId)
  936. {
  937. // Form a list of all current store categories ids
  938. $store = $this->getStores($storeId);
  939. $rootCategoryId = $store->getRootCategoryId();
  940. if (!$rootCategoryId) {
  941. return $this;
  942. }
  943. $categoryIds = $this->getRootChildrenIds($rootCategoryId, $store->getRootCategoryPath());
  944. // Remove all store catalog rewrites that are for some category or cartegory/product not within store categories
  945. $adapter = $this->_getWriteAdapter();
  946. $condition = $adapter->quoteInto('store_id = ?', $storeId);
  947. $condition .= ' AND category_id IS NOT NULL'; // For sure check that it's a catalog rewrite
  948. $condition .= $adapter->quoteInto(' AND category_id NOT IN (?)', $categoryIds);
  949. $adapter->delete($this->getMainTable(), $condition);
  950. return $this;
  951. }
  952. /**
  953. * Finds and deletes product rewrites (that are not assigned to any category) for store
  954. * left from the times when product was assigned to this store's website and now is not assigned
  955. *
  956. * Notice: this routine is different from clearProductRewrites() and clearCategoryProduct() because
  957. * it handles direct rewrites to product without defined category (category_id IS NULL) whilst that routines
  958. * handle only product rewrites within categories
  959. *
  960. * @param int $storeId
  961. * @param int|array|null $productId
  962. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Url
  963. */
  964. public function clearStoreProductsInvalidRewrites($storeId, $productId = null)
  965. {
  966. $store = $this->getStores($storeId);
  967. $read = $this->_getReadAdapter();
  968. $select = $read->select()
  969. ->from(array('rewrite' => $this->getMainTable()), $this->getIdFieldName())
  970. ->joinLeft(
  971. array('website' => $this->getTable('catalog/product_website')),
  972. 'rewrite.product_id=website.product_id AND ' . $read->quoteInto('website.website_id = ?', $store->getWebsiteId()),
  973. array()
  974. )->where('rewrite.store_id=?', $storeId)
  975. ->where('rewrite.category_id IS NULL');
  976. if ($productId) {
  977. $select->where('rewrite.product_id IN (?)', $productId);
  978. } else {
  979. $select->where('rewrite.product_id IS NOT NULL');
  980. }
  981. $select->where('website.website_id IS NULL');
  982. $rowSet = $read->fetchAll($select);
  983. $rewriteIds = array();
  984. foreach ($rowSet as $row) {
  985. $rewriteIds[] = $row[$this->getIdFieldName()];
  986. }
  987. if ($rewriteIds) {
  988. $write =$this->_getWriteAdapter();
  989. $where = $write->quoteInto($this->getIdFieldName() . ' IN(?)', $rewriteIds);
  990. $this->_getWriteAdapter()->delete($this->getMainTable(), $where);
  991. }
  992. return $this;
  993. }
  994. /**
  995. * Finds and deletes old rewrites for store
  996. * a) category rewrites left from the times when store had some other root category
  997. * b) product rewrites left from products that once belonged to this site, but then deleted or just removed from website
  998. *
  999. * @param int $storeId
  1000. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Url
  1001. */
  1002. public function clearStoreInvalidRewrites($storeId)
  1003. {
  1004. $this->clearStoreCategoriesInvalidRewrites($storeId);
  1005. $this->clearStoreProductsInvalidRewrites($storeId);
  1006. return $this;
  1007. }
  1008. /**
  1009. * Delete rewrites for associated to category products
  1010. *
  1011. * @param int $categoryId
  1012. * @param array $productIds
  1013. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Url
  1014. */
  1015. public function deleteCategoryProductRewrites($categoryId, $productIds)
  1016. {
  1017. $this->deleteCatagoryProductStoreRewrites($categoryId, $productIds);
  1018. return $this;
  1019. }
  1020. /**
  1021. * Delete URL rewrites for category products of specific store
  1022. *
  1023. * @param int $categoryId
  1024. * @param array|int|null $productIds
  1025. * @param null|int $storeId
  1026. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Url
  1027. */
  1028. public function deleteCategoryProductStoreRewrites($categoryId, $productIds=null, $storeId=null)
  1029. {
  1030. $adapter = $this->_getWriteAdapter();
  1031. // Notice that we don't include category_id = NULL in case of root category,
  1032. // because product removed from all categories but assigned to store's website is still
  1033. // assumed to be in root cat. Unassigned products must be removed by other routine.
  1034. $condition = $adapter->quoteInto('category_id=?', $categoryId);
  1035. if (empty($productIds)) {
  1036. $condition.= ' AND product_id IS NOT NULL';
  1037. } else {
  1038. $condition.= $adapter->quoteInto(' AND product_id IN (?)', $productIds);
  1039. }
  1040. if ($storeId !== null) {
  1041. $condition.= $adapter->quoteInto(' AND store_id IN(?)', $storeId);
  1042. }
  1043. $adapter->delete($this->getMainTable(), $condition);
  1044. return $this;
  1045. }
  1046. /**
  1047. * Retrieve rewrites and visibility by store
  1048. *
  1049. * Input array format:
  1050. * product_id as key and store_id as value
  1051. *
  1052. * Output array format (product_id as key)
  1053. * store_id int; store id
  1054. * visibility int; visibility for store
  1055. * url_rewrite string; rewrite URL for store
  1056. *
  1057. * @param array $products
  1058. * @return array
  1059. */
  1060. public function getRewriteByProductStore(array $products)
  1061. {
  1062. $result = array();
  1063. if (empty($products)) {
  1064. return $result;
  1065. }
  1066. $select = $this->_getReadAdapter()->select()
  1067. ->from(
  1068. array('i' => $this->getTable('catalog/category_product_index')),
  1069. array('product_id', 'store_id', 'visibility'))
  1070. ->joinLeft(
  1071. array('r' => $this->getMainTable()),
  1072. 'i.product_id = r.product_id AND i.store_id=r.store_id AND r.category_id IS NULL',
  1073. array('request_path')
  1074. );
  1075. foreach ($products as $productId => $storeId) {
  1076. $catId = Mage::app()->getStore($storeId)->getRootCategoryId();
  1077. $cond = join(' AND ', array(
  1078. $this->_getReadAdapter()->quoteInto('i.product_id=?', $productId),
  1079. $this->_getReadAdapter()->quoteInto('i.store_id=?', $storeId),
  1080. $this->_getReadAdapter()->quoteInto('i.category_id=?', $catId),
  1081. ));
  1082. $select->orWhere($cond);
  1083. }
  1084. $query = $this->_getReadAdapter()->query($select);
  1085. while ($row = $query->fetch()) {
  1086. $result[$row['product_id']] = array(
  1087. 'store_id' => $row['store_id'],
  1088. 'visibility' => $row['visibility'],
  1089. 'url_rewrite' => $row['request_path'],
  1090. );
  1091. }
  1092. return $result;
  1093. }
  1094. /**
  1095. * Find and return final id path by request path
  1096. * Needed for permanent redirect old URLs.
  1097. *
  1098. * @param string $requestPath
  1099. * @param int $storeId
  1100. * @param array $_checkedPaths internal varible to prevent infinite loops.
  1101. * @return string | bool
  1102. */
  1103. public function findFinalTargetPath($requestPath, $storeId, &$_checkedPaths = array())
  1104. {
  1105. if (in_array($requestPath, $_checkedPaths)) {
  1106. return false;
  1107. }
  1108. $_checkedPaths[] = $requestPath;
  1109. $select = $this->_getWriteAdapter()->select()
  1110. ->from($this->getMainTable(), array('target_path', 'id_path'))
  1111. ->where('store_id = ?', $storeId)
  1112. ->where('request_path = ?', $requestPath);
  1113. if ($row = $this->_getWriteAdapter()->fetchRow($select)) {
  1114. $idPath = $this->findFinalTargetPath($row['target_path'], $storeId, $_checkedPaths);
  1115. if (!$idPath) {
  1116. return $row['id_path'];
  1117. } else {
  1118. return $idPath;
  1119. }
  1120. }
  1121. return false;
  1122. }
  1123. /**
  1124. * Delete rewrite path record from the database.
  1125. *
  1126. * @param string $requestPath
  1127. * @param int $storeId
  1128. */
  1129. public function deleteRewrite($requestPath, $storeId)
  1130. {
  1131. $db = $this->_getWriteAdapter();
  1132. $db->delete(
  1133. $this->getMainTable(),
  1134. "store_id = {$db->quote($storeId)} AND request_path = {$db->quote($requestPath)}"
  1135. );
  1136. }
  1137. }