├── README.md ├── app ├── code │ └── community │ │ └── EcomDev │ │ └── UrlRewrite │ │ ├── Helper │ │ └── Data.php │ │ ├── Model │ │ ├── Indexer.php │ │ └── Mysql4 │ │ │ ├── Indexer.php │ │ │ └── Select.php │ │ ├── Test │ │ ├── Config │ │ │ └── Main.php │ │ └── Model │ │ │ └── Mysql4 │ │ │ ├── Indexer.php │ │ │ └── Indexer │ │ │ ├── expectations │ │ │ ├── testCreationOfCategoryRelations.yaml │ │ │ ├── testCreationOfCategoryRequestPathIndex.yaml │ │ │ ├── testCreationOfProductRequestPathIndex.yaml │ │ │ └── testCreationOfRootCategory.yaml │ │ │ ├── fixtures │ │ │ ├── categoryRelationIndex.yaml │ │ │ ├── categoryRequestPathIndex.yaml │ │ │ ├── categoryUrlKeyIndex.yaml │ │ │ ├── clear.yaml │ │ │ ├── data.yaml │ │ │ └── rootCategoryIndex.yaml │ │ │ └── providers │ │ │ ├── testCreationOfCategoryRelations.yaml │ │ │ ├── testCreationOfCategoryRequestPathIndex.yaml │ │ │ └── testCreationOfProductRequestPathIndex.yaml │ │ ├── etc │ │ └── config.xml │ │ └── sql │ │ └── ecomdev_urlrewrite_setup │ │ ├── mysql4-install-0.2.0.php │ │ └── mysql4-upgrade-0.2.0-0.2.1.php └── etc │ └── modules │ └── EcomDev_UrlRewrite.xml └── modman /README.md: -------------------------------------------------------------------------------- 1 | ![EcomDev](http://www.ecomdev.org/wp-content/themes/ecomdev/images/logo.png) 2 | 3 | Alternative Url Rewrite Implementation by EcomDev 4 | ================================================= 5 | * Full support of nested rewrites for categories 6 | * Full support of product rewrites 7 | * Full support of duplicates handling 8 | * Full support of transliteration during generation of url key 9 | * Full support of history saving logic 10 | * Url rewrite generation for product in anchor category 11 | 12 | 13 | System Requirements 14 | ------------------- 15 | * MySQL 5.x or higher 16 | * Magento 1.4.x or higher 17 | 18 | Compatibility 19 | ------------- 20 | 21 | Currently extension tested on these Magento versions: 22 | CE 1.4.2, 1.5.1, 1.6.0 23 | 24 | NOTICE 25 | ------ 26 | Currently this extension is in beta version, don't install it directly on live website without checking its stability on semilive/prelive/dev version 27 | 28 | Issue with Stored Routines 29 | ----------------------------- 30 | If you install extension on one db but then transfer db to anohter instance, it is possible that you forget to include stored porcedures with your dump. 31 | The fix can be done by performing the following action: 32 | * Drop all `ecomdev_urlrewrite_*` tables 33 | * Delete record from `core_resource` with code `ecomdev_urlrewrite_setup` 34 | 35 | -------------------------------------------------------------------------------- /app/code/community/EcomDev/UrlRewrite/Helper/Data.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | 19 | /** 20 | * Module helper 21 | * 22 | */ 23 | class EcomDev_UrlRewrite_Helper_Data extends Mage_Core_Helper_Abstract 24 | { 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/code/community/EcomDev/UrlRewrite/Model/Indexer.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | 19 | /** 20 | * Url rewrite indexer model 21 | * 22 | */ 23 | class EcomDev_UrlRewrite_Model_Indexer extends Mage_Catalog_Model_Indexer_Url 24 | { 25 | /** 26 | * Define resource model for indexer 27 | * 28 | * (non-PHPdoc) 29 | * @see Varien_Object::_construct() 30 | */ 31 | protected function _construct() 32 | { 33 | $this->_init('ecomdev_urlrewrite/indexer'); 34 | } 35 | 36 | /** 37 | * Get Indexer description 38 | * 39 | * @return string 40 | */ 41 | public function getDescription() 42 | { 43 | return Mage::helper('ecomdev_urlrewrite')->__('Index product and categories URL rewrites (Alternative by EcomDev)'); 44 | } 45 | 46 | /** 47 | * Register event data during category save process 48 | * 49 | * @param Mage_Index_Model_Event $event 50 | */ 51 | protected function _registerCategoryEvent(Mage_Index_Model_Event $event) 52 | { 53 | $category = $event->getDataObject(); 54 | if (!$category->getInitialSetupFlag() && $category->getLevel() > 1) { 55 | if ($category->dataHasChangedFor('is_anchor')) { 56 | $event->addNewData('rewrite_category_ids', array($category->getId())); 57 | } 58 | } 59 | 60 | return parent::_registerCategoryEvent($event); 61 | } 62 | 63 | /** 64 | * Process event 65 | * 66 | * @param Mage_Index_Model_Event $event 67 | */ 68 | protected function _processEvent(Mage_Index_Model_Event $event) 69 | { 70 | $data = $event->getNewData(); 71 | if (!empty($data['catalog_url_reindex_all'])) { 72 | $this->reindexAll(); 73 | return $this; 74 | } 75 | 76 | // Force rewrites history saving 77 | $dataObject = $event->getDataObject(); 78 | if ($dataObject instanceof Varien_Object && $dataObject->hasData('save_rewrites_history')) { 79 | $this->_getResource()->isSaveHistory($dataObject->getData('save_rewrites_history')); 80 | } 81 | 82 | if (isset($data['rewrite_category_ids']) || isset($data['rewrite_product_ids'])) { 83 | $this->callEventHandler($event); 84 | } 85 | 86 | $this->_getResource()->resetSaveHistory(); 87 | return $this; 88 | } 89 | 90 | /** 91 | * Rebuild all index data 92 | * 93 | */ 94 | public function reindexAll() 95 | { 96 | $this->_getResource()->reindexAll(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /app/code/community/EcomDev/UrlRewrite/Model/Mysql4/Indexer.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | 19 | /** 20 | * Url rewrite indexer resource model 21 | * 22 | */ 23 | class EcomDev_UrlRewrite_Model_Mysql4_Indexer extends Mage_Index_Model_Mysql4_Abstract 24 | { 25 | const TRANSLITERATE = 'transliterate'; 26 | const ROOT_CATEGORY = 'root_category'; 27 | const CATEGORY_URL_KEY = 'category_url_key'; 28 | const CATEGORY_REQUEST_PATH = 'category_request_path'; 29 | const CATEGORY_RELATION = 'category_relation'; 30 | const PRODUCT_RELATION = 'product_relation'; 31 | const PRODUCT_URL_KEY = 'product_url_key'; 32 | const PRODUCT_REQUEST_PATH = 'product_request_path'; 33 | const REWRITE = 'rewrite'; 34 | const DUPLICATE = 'duplicate'; 35 | const DUPLICATE_UPDATED = 'duplicate_updated'; 36 | const DUPLICATE_KEY = 'duplicate_key'; 37 | const DUPLICATE_INCREMENT = 'duplicate_increment'; 38 | const DUPLICATE_AGGREGATE = 'duplicate_aggregate'; 39 | 40 | const MAX_LENGTH_URL_PATH = 245; 41 | 42 | const ENTITY_CATEGORY = Mage_Catalog_Model_Category::ENTITY; 43 | const ENTITY_PRODUCT = Mage_Catalog_Model_Product::ENTITY; 44 | 45 | const RELATION_TYPE_NESTED = 'nested'; 46 | const RELATION_TYPE_ANCHOR = 'anchor'; 47 | 48 | /** 49 | * Id path templates for entities 50 | * 51 | * @var string 52 | */ 53 | const ID_PATH_CATEGORY = 'category/#id'; 54 | const ID_PATH_PRODUCT = 'product/#id'; 55 | const ID_PATH_PRODUCT_CATEGORY = 'product/#id/#cat'; 56 | 57 | /** 58 | * Target path templates for entities 59 | * 60 | * @var string 61 | */ 62 | const TARGET_PATH_CATEGORY = 'catalog/category/view/id/#id'; 63 | const TARGET_PATH_PRODUCT = 'catalog/product/view/id/#id'; 64 | const TARGET_PATH_PRODUCT_CATEGORY = 'catalog/product/view/id/#id/category/#cat'; 65 | 66 | /** 67 | * Replacement expressions for the templates above 68 | * @var string 69 | */ 70 | const REPLACE_CATEGORY = "REPLACE(?,'#id',%s)"; 71 | const REPLACE_PRODUCT = "REPLACE(?,'#id',%s)"; 72 | const REPLACE_PRODUCT_CATEGORY = "REPLACE(REPLACE(?,'#id',%s),'#cat',%s)"; 73 | 74 | /** 75 | * Save url history flag for forced history save 76 | * 77 | * @var boolean|null 78 | */ 79 | protected $_isSaveHistory = null; 80 | 81 | /** 82 | * List of path generation expressions 83 | * 84 | * @var array 85 | */ 86 | protected $_pathGenerateExpr = array( 87 | self::ID_PATH_CATEGORY => self::REPLACE_CATEGORY, 88 | self::ID_PATH_PRODUCT => self::REPLACE_PRODUCT, 89 | self::ID_PATH_PRODUCT_CATEGORY => self::REPLACE_PRODUCT_CATEGORY, 90 | self::TARGET_PATH_CATEGORY => self::REPLACE_CATEGORY, 91 | self::TARGET_PATH_PRODUCT => self::REPLACE_PRODUCT, 92 | self::TARGET_PATH_PRODUCT_CATEGORY => self::REPLACE_PRODUCT_CATEGORY, 93 | ); 94 | 95 | /** 96 | * Initialize resource model with tables lists 97 | * 98 | */ 99 | protected function _construct() 100 | { 101 | $tables = array( 102 | self::TRANSLITERATE => '', 103 | self::ROOT_CATEGORY => '', 104 | self::CATEGORY_URL_KEY => '', 105 | self::CATEGORY_REQUEST_PATH => '', 106 | self::CATEGORY_RELATION => '', 107 | self::PRODUCT_URL_KEY => '', 108 | self::PRODUCT_REQUEST_PATH => '', 109 | self::PRODUCT_RELATION => '', 110 | self::REWRITE => '', 111 | self::DUPLICATE => '', 112 | self::DUPLICATE_UPDATED => '', 113 | self::DUPLICATE_KEY => '', 114 | self::DUPLICATE_INCREMENT => '', 115 | self::DUPLICATE_AGGREGATE => '' 116 | ); 117 | 118 | foreach ($tables as $key => &$table) { 119 | $table = 'ecomdev_urlrewrite/' . $key; 120 | } 121 | 122 | $this->_setResource('ecomdev_urlrewrite', $tables); 123 | } 124 | 125 | /** 126 | * Checks if save history mode is enabled. 127 | * If $flag parameter is passed, then history save is forced 128 | * 129 | * @param boolean $flag 130 | * @return boolean 131 | */ 132 | public function isSaveHistory($flag = null) 133 | { 134 | if ($flag !== null) { 135 | $this->_isSaveHistory = (bool) $flag; 136 | } 137 | 138 | 139 | return $this->_isSaveHistory; 140 | } 141 | 142 | /** 143 | * Checks if save history mode is enabled on a particular store 144 | * 145 | * @param mixed $storeId 146 | * @return boolean 147 | */ 148 | public function isSaveHistoryStore($storeId) 149 | { 150 | if ($this->_isSaveHistory !== null) { 151 | return $this->_isSaveHistory; 152 | } 153 | 154 | return Mage::helper('catalog')->shouldSaveUrlRewritesHistory($storeId); 155 | } 156 | 157 | /** 158 | * Resets force history save flag 159 | * 160 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 161 | */ 162 | public function resetSaveHistory() 163 | { 164 | $this->_isSaveHistory = null; 165 | return $this; 166 | } 167 | 168 | /** 169 | * Returns EAV config model 170 | * 171 | * @return Mage_Eav_Model_Config 172 | */ 173 | protected function _getEavConfig() 174 | { 175 | return Mage::getSingleton('eav/config'); 176 | } 177 | 178 | /** 179 | * Shortcut for DB Adapter quoteInto() method 180 | * 181 | * @param string $expr 182 | * @param mixed $replacement 183 | * @return string 184 | */ 185 | protected function _quoteInto($expr, $replacement) 186 | { 187 | return $this->_getIndexAdapter()->quoteInto($expr, $replacement); 188 | } 189 | 190 | /** 191 | * Creates a new extended select instance 192 | * 193 | * @return EcomDev_UrlRewrite_Model_Mysql4_Select 194 | */ 195 | protected function _select() 196 | { 197 | return Mage::getResourceModel('ecomdev_urlrewrite/select', array($this->_getIndexAdapter())); 198 | } 199 | 200 | /** 201 | * Check that character is unicode single char. 202 | * Should not translate chars combination in tranlstate source 203 | * 204 | * @param string $item 205 | * @return boolean 206 | */ 207 | protected function _isSingleUtf8Char($item) 208 | { 209 | return iconv_strlen($item, 'UTF-8') == 1; 210 | } 211 | 212 | /** 213 | * Generates transliteration data for ECOMDEV_CLEAN_URL_KEY from logic 214 | * specified in url helpers 215 | * 216 | * @param boolean $checkIfEmpty if equals true, then generates data only if table is not empty 217 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 218 | */ 219 | protected function _generateTransliterateData($checkIfEmpty = false) 220 | { 221 | $translateTable = Mage::helper('catalog/product_url')->getConvertTable(); 222 | $validChars = array_filter(array_keys($translateTable), array($this, '_isSingleUtf8Char')); 223 | 224 | if ($validChars) { 225 | if (!$checkIfEmpty) { 226 | $this->_getIndexAdapter()->delete($this->getTable(self::TRANSLITERATE)); 227 | } else { 228 | $select = $this->_select(); 229 | $numberOfRows = $this->_getIndexAdapter()->fetchOne( 230 | $select->from($this->getTable(self::TRANSLITERATE), 'COUNT(character_to)') 231 | ); 232 | 233 | if ($numberOfRows) { 234 | return $this; 235 | } 236 | } 237 | 238 | $insert = array(); 239 | foreach ($validChars as $char) { 240 | $insert[] = array( 241 | 'character_from' => new Zend_Db_Expr($this->_quoteInto( 242 | 'LCASE(?)', 243 | $char 244 | )), 245 | 'character_to' => new Zend_Db_Expr($this->_quoteInto( 246 | 'LCASE(?)', 247 | $translateTable[$char] 248 | )) 249 | ); 250 | } 251 | 252 | 253 | $this->_getIndexAdapter()->insertOnDuplicate( 254 | $this->getTable(self::TRANSLITERATE), 255 | $insert, 256 | array('character_to') 257 | ); 258 | } 259 | 260 | return $this; 261 | } 262 | 263 | /** 264 | * Prepares category url key select 265 | * 266 | * @return EcomDev_UrlRewrite_Model_Mysql4_Select 267 | */ 268 | protected function _getCategoryUrlKeySelect() 269 | { 270 | $urlKeyAttribute = $this->_getEavConfig()->getAttribute(self::ENTITY_CATEGORY, 'url_key'); 271 | $nameAttribute = $this->_getEavConfig()->getAttribute(self::ENTITY_CATEGORY, 'name'); 272 | 273 | $select = $this->_select(); 274 | // Initialize tables for fullfilment of url key index for categories 275 | $select 276 | // Data should be generated for each store view 277 | // And only for categories in its store group 278 | ->from(array('root_index' => $this->getTable(self::ROOT_CATEGORY)), array()) 279 | ->join( 280 | array('category' => $this->getTable('catalog/category')), 281 | 'category.path LIKE root_index.path', 282 | array() 283 | ) 284 | ->joinLeft( 285 | array('original' => $this->getTable(self::CATEGORY_URL_KEY)), 286 | 'original.category_id = category.entity_id AND original.store_id = root_index.store_id', 287 | array() 288 | ) 289 | // Name attribute values retrieval (for default and store) 290 | ->joinLeft(array('name_default' => $nameAttribute->getBackendTable()), 291 | 'name_default.entity_id = category.entity_id' 292 | . ' AND name_default.store_id = 0' 293 | . $this->_quoteInto( 294 | ' AND name_default.attribute_id = ?', 295 | $nameAttribute->getAttributeId()), 296 | array()) 297 | ->joinLeft(array('name_store' => $nameAttribute->getBackendTable()), 298 | 'name_store.entity_id = category.entity_id' 299 | . ' AND name_store.store_id = root_index.store_id' 300 | . $this->_quoteInto( 301 | ' AND name_store.attribute_id = ?', 302 | $nameAttribute->getAttributeId()), 303 | array()) 304 | // Url key attribute retrieval (for default and store) 305 | ->joinLeft(array('url_key_default' => $urlKeyAttribute->getBackendTable()), 306 | 'url_key_default.entity_id = category.entity_id' 307 | . ' AND url_key_default.store_id = 0' 308 | . $this->_quoteInto( 309 | ' AND url_key_default.attribute_id = ?', 310 | $urlKeyAttribute->getAttributeId()), 311 | array()) 312 | ->joinLeft(array('url_key_store' => $urlKeyAttribute->getBackendTable()), 313 | 'url_key_store.entity_id = category.entity_id' 314 | . ' AND url_key_store.store_id = root_index.store_id' 315 | . $this->_quoteInto( 316 | ' AND url_key_store.attribute_id = ?', 317 | $urlKeyAttribute->getAttributeId()), 318 | array()); 319 | 320 | $urlKeySourceExpr = new Zend_Db_Expr( 321 | 'IFNULL(' 322 | . ' IFNULL(url_key_store.value, url_key_default.value), ' 323 | . ' IFNULL(name_store.value, name_default.value) ' 324 | . ')' 325 | ); 326 | 327 | $columns = array( 328 | 'store_id' => 'root_index.store_id', 329 | 'category_id' => 'category.entity_id', 330 | 'level' => 'category.level', 331 | 'url_key_source' => $urlKeySourceExpr, 332 | 'updated' => new Zend_Db_Expr( 333 | 'IF(original.category_id IS NULL, 1, ' 334 | . $urlKeySourceExpr . ' != original.url_key_source)' 335 | ) 336 | ); 337 | 338 | $select->columns($columns); 339 | 340 | Mage::dispatchEvent( 341 | 'ecomdev_urlrewrite_indexer_get_category_url_key_select', 342 | array('select' => $select, 'columns' => $columns, 'resource' => $this) 343 | ); 344 | 345 | return $select; 346 | } 347 | 348 | /** 349 | * Prepares product url key select 350 | * 351 | * @return EcomDev_UrlRewrite_Model_Mysql4_Select 352 | */ 353 | protected function _getProductUrlKeySelect() 354 | { 355 | $urlKeyAttribute = $this->_getEavConfig()->getAttribute(self::ENTITY_PRODUCT, 'url_key'); 356 | $nameAttribute = $this->_getEavConfig()->getAttribute(self::ENTITY_PRODUCT, 'name'); 357 | 358 | $select = $this->_select(); 359 | // Initialize tables for fullfilment of url key index for categories 360 | $select 361 | ->from(array('product' => $this->getTable('catalog/product')), array()) 362 | // Data should be generated only for products that are assigned for that are available on the website 363 | ->join( 364 | array('product_website' => $this->getTable('catalog/product_website')), 365 | 'product_website.product_id = product.entity_id', 366 | array()) 367 | ->join( 368 | array('store' => $this->getTable('core/store')), 369 | 'store.website_id = product_website.website_id', 370 | array()) 371 | ->joinLeft( 372 | array('original' => $this->getTable(self::PRODUCT_URL_KEY)), 373 | 'original.product_id = product.entity_id AND original.store_id = store.store_id', 374 | array() 375 | ) 376 | // Name attribute values retrieval (for default and store) 377 | ->join(array('name_default' => $nameAttribute->getBackendTable()), 378 | 'name_default.entity_id = product.entity_id' 379 | . ' AND name_default.store_id = 0' 380 | . $this->_quoteInto( 381 | ' AND name_default.attribute_id = ?', 382 | $nameAttribute->getAttributeId()), 383 | array()) 384 | ->joinLeft(array('name_store' => $nameAttribute->getBackendTable()), 385 | 'name_store.entity_id = product.entity_id' 386 | . ' AND name_store.store_id = store.store_id' 387 | . $this->_quoteInto( 388 | ' AND name_store.attribute_id = ?', 389 | $nameAttribute->getAttributeId()), 390 | array()) 391 | // Url key attribute retrieval (for default and store) 392 | ->joinLeft(array('url_key_default' => $urlKeyAttribute->getBackendTable()), 393 | 'url_key_default.entity_id = product.entity_id' 394 | . ' AND url_key_default.store_id = 0' 395 | . $this->_quoteInto( 396 | ' AND url_key_default.attribute_id = ?', 397 | $urlKeyAttribute->getAttributeId()), 398 | array()) 399 | ->joinLeft(array('url_key_store' => $urlKeyAttribute->getBackendTable()), 400 | 'url_key_store.entity_id = product.entity_id' 401 | . ' AND url_key_store.store_id = store.store_id' 402 | . $this->_quoteInto( 403 | ' AND url_key_store.attribute_id = ?', 404 | $urlKeyAttribute->getAttributeId()), 405 | array()); 406 | 407 | $urlKeySourceExpr = new Zend_Db_Expr( 408 | 'IFNULL(' 409 | . ' IFNULL(url_key_store.value, url_key_default.value), ' 410 | . ' IFNULL(name_store.value, name_default.value) ' 411 | . ')' 412 | ); 413 | 414 | $columns = array( 415 | 'store_id' => 'store.store_id', 416 | 'product_id' => 'product.entity_id', 417 | 'url_key_source' => $urlKeySourceExpr, 418 | 'updated' => new Zend_Db_Expr( 419 | 'IF(original.product_id IS NULL, 1, ' 420 | . $urlKeySourceExpr . ' != original.url_key_source)' 421 | ) 422 | ); 423 | 424 | $select->columns($columns); 425 | 426 | Mage::dispatchEvent( 427 | 'ecomdev_urlrewrite_indexer_get_product_url_key_select', 428 | array('select' => $select, 'columns' => $columns, 'resource' => $this) 429 | ); 430 | 431 | return $select; 432 | } 433 | 434 | /** 435 | * Prepares category request path select 436 | * 437 | * @return EcomDev_UrlRewrite_Model_Mysql4_Select 438 | */ 439 | protected function _getCategoryRequestPathSelect() 440 | { 441 | $select = $this->_select(); 442 | 443 | // Initialize tables for fullfilment of request path index for categories 444 | $select 445 | // Generate path index from already generated url url keys 446 | ->from( 447 | array('url_key' => $this->getTable(self::CATEGORY_URL_KEY)), 448 | array() 449 | ) 450 | ->joinLeft( 451 | array('relation' => $this->getTable(self::CATEGORY_RELATION)), 452 | $this->_quoteInto( 453 | 'relation.related_id = url_key.category_id AND relation.type = ?', self::RELATION_TYPE_NESTED 454 | ), 455 | array() 456 | ) 457 | ->joinLeft( 458 | array('parent_url_key' => $this->getTable(self::CATEGORY_URL_KEY)), 459 | 'parent_url_key.store_id = url_key.store_id AND parent_url_key.category_id = relation.category_id', 460 | array() 461 | ); 462 | 463 | $requestPathExpr = $this->_quoteInto( 464 | 'TRIM(LEADING ? FROM CONCAT(' 465 | . 'IFNULL(GROUP_CONCAT(parent_url_key.url_key ORDER BY parent_url_key.level ASC SEPARATOR ?), ?), ' 466 | . '?, url_key.url_key' 467 | . '))', 468 | '/' 469 | ); 470 | $columns = array( 471 | 'store_id' => 'url_key.store_id', 472 | 'id_path' => new Zend_Db_Expr($this->_quoteInto( 473 | sprintf( 474 | $this->_pathGenerateExpr[self::ID_PATH_CATEGORY], 475 | 'url_key.category_id' 476 | ), 477 | self::ID_PATH_CATEGORY 478 | )), 479 | 'category_id' => 'url_key.category_id', 480 | 'level' => 'url_key.level', 481 | 'request_path' => new Zend_Db_Expr($this->_quoteInto( 482 | sprintf('SUBSTRING(%s FROM 1 FOR ?)', $requestPathExpr), 483 | self::MAX_LENGTH_URL_PATH 484 | )), 485 | 'updated' => new Zend_Db_Expr('1') 486 | ); 487 | 488 | $select 489 | ->columns($columns) 490 | ->group(array('url_key.store_id', 'url_key.category_id')); 491 | 492 | Mage::dispatchEvent( 493 | 'ecomdev_urlrewrite_indexer_get_category_request_path_select', 494 | array('select' => $select, 'columns' => $columns, 'resource' => $this) 495 | ); 496 | 497 | return $select; 498 | } 499 | 500 | /** 501 | * Prepares product request path select 502 | * 503 | * @param boolean $category category 504 | * @return EcomDev_UrlRewrite_Model_Mysql4_Select 505 | */ 506 | protected function _getProductRequestPathSelect($category = false) 507 | { 508 | $select = $this->_select(); 509 | 510 | 511 | // Initialize tables for fullfilment of request path index for products 512 | if ($category !== false) { 513 | $select 514 | ->useStraightJoin(true) 515 | ->from( 516 | array('relation' => $this->getTable(self::PRODUCT_RELATION)), 517 | array() 518 | ) 519 | ->join( 520 | array('category' => $this->getTable(self::CATEGORY_REQUEST_PATH)), 521 | 'category.store_id = relation.store_id AND category.category_id = relation.category_id', 522 | array() 523 | ) 524 | ->join( 525 | array('url_key' => $this->getTable(self::PRODUCT_URL_KEY)), 526 | 'url_key.store_id = relation.store_id AND url_key.product_id = relation.product_id', 527 | array() 528 | ); 529 | } else { 530 | $select 531 | ->from(array('url_key' => $this->getTable(self::PRODUCT_URL_KEY)), array()); 532 | } 533 | 534 | $requestPathExpr = $this->_quoteInto( 535 | 'CONCAT(category.request_path, ?, url_key.url_key)', 536 | '/' 537 | ); 538 | 539 | $idPathExprKey = ($category !==false ? self::ID_PATH_PRODUCT_CATEGORY : self::ID_PATH_PRODUCT); 540 | 541 | $columns = array( 542 | 'store_id' => 'url_key.store_id', 543 | 'id_path' => new Zend_Db_Expr($this->_quoteInto( 544 | sprintf( 545 | $this->_pathGenerateExpr[$idPathExprKey], 546 | 'url_key.product_id', 547 | 'category.category_id' 548 | ), 549 | $idPathExprKey 550 | )), 551 | 'product_id' => 'url_key.product_id', 552 | 'category_id' => ($category === false ? new Zend_Db_Expr('NULL') : 'category.category_id'), 553 | 'request_path' => new Zend_Db_Expr($this->_quoteInto( 554 | sprintf( 555 | 'SUBSTRING(%s FROM 1 FOR ?)', 556 | $category === false ? 'url_key.url_key' : $requestPathExpr 557 | ), 558 | self::MAX_LENGTH_URL_PATH 559 | )), 560 | 'updated' => new Zend_Db_Expr('1') 561 | ); 562 | 563 | $select->columns($columns); 564 | 565 | Mage::dispatchEvent( 566 | 'ecomdev_urlrewrite_indexer_get_product_request_path_select', 567 | array('select' => $select, 'is_category' => $category) 568 | ); 569 | 570 | return $select; 571 | } 572 | 573 | /** 574 | * Generate root category list 575 | * 576 | * @return array 577 | */ 578 | public function getRootCategories() 579 | { 580 | 581 | $select = $this->_select() 582 | ->from($this->getTable(self::ROOT_CATEGORY)); 583 | 584 | return $this->_getReadAdapter()->fetchPairs($select); 585 | } 586 | 587 | /** 588 | * Generates root categories index 589 | * 590 | * @return int 591 | */ 592 | protected function _generateRootCategoryIndex() 593 | { 594 | $this->_getIndexAdapter()->truncate($this->getTable(self::ROOT_CATEGORY)); 595 | 596 | $select = $this->_select(); 597 | 598 | $select 599 | ->from(array('store' => $this->getTable('core/store')), 'store_id') 600 | ->join( 601 | array('store_group' => $this->getTable('core/store_group')), 602 | 'store_group.group_id = store.group_id', 603 | array() 604 | ) 605 | ->join( 606 | array('category' => $this->getTable('catalog/category')), 607 | 'category.level = 1 AND category.entity_id = store_group.root_category_id', 608 | array('path' => new Zend_Db_Expr($this->_quoteInto( 609 | 'CONCAT(path, ?)', '/%' 610 | ))) 611 | ); 612 | 613 | $this->insertFromSelect( 614 | $select, 615 | $this->getTable(self::ROOT_CATEGORY), 616 | $select->getColumnAliases() 617 | ); 618 | return $this; 619 | } 620 | 621 | /** 622 | * Returns data from category relations index table. 623 | * Result is an array where key is category id and value is an array of related ids 624 | * 625 | * @param array|int $categoryIds 626 | * @param string $type 627 | * @return array 628 | */ 629 | public function getCategoryRelations($categoryIds, $type) 630 | { 631 | if (!is_array($categoryIds)) { 632 | $categoryIds = array($categoryIds); 633 | } 634 | 635 | $select = $this->_getRelatedCategoryIdsSelect($categoryIds, $type) 636 | ->columns('category_id'); 637 | 638 | $result = array(); 639 | foreach ($this->_getReadAdapter()->fetchAll($select) as $relation) { 640 | $result[$relation['category_id']][] = $relation['related_id']; 641 | } 642 | 643 | return $result; 644 | } 645 | 646 | /** 647 | * Return select for retrieving all affected category ids for index 648 | * 649 | * @param array $categoryIds 650 | * @param string $type relation type 651 | * @return Varien_Db_Select 652 | */ 653 | protected function _getRelatedCategoryIdsSelect(array $categoryIds, $type = self::RELATION_TYPE_NESTED) 654 | { 655 | $select = $this->_select() 656 | ->from(array('relation' => $this->getTable(self::CATEGORY_RELATION)), 'related_id') 657 | ->where('relation.category_id IN(?)', $categoryIds) 658 | ->where('relation.type = ?', $type); 659 | 660 | Mage::dispatchEvent( 661 | 'ecomdev_urlrewrite_indexer_get_related_category_ids_select', 662 | array('select' => $select) 663 | ); 664 | 665 | return $select; 666 | } 667 | 668 | /** 669 | * Generates category url path index table data 670 | * 671 | * @param array|null $categoryIds 672 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 673 | */ 674 | protected function _generateCategoryRelationIndex(array $categoryIds = null) 675 | { 676 | if ($categoryIds !== null) { 677 | $childSelect = $this->_select(); 678 | $childSelect 679 | ->from(array('main' => $this->getTable('catalog/category')), array()) 680 | ->join(array('child' => $this->getTable('catalog/category')), 681 | 'child.path LIKE CONCAT(main.path, \'/%\')', 682 | array('entity_id')) 683 | ->where('main.children_count > ?', 0) 684 | ->where('child.children_count > ?', 0) 685 | ->where('main.entity_id IN(?)', $categoryIds); 686 | 687 | $categoryIds = array_merge( 688 | $categoryIds, 689 | $this->_getIndexAdapter()->fetchCol($childSelect) 690 | ); 691 | } 692 | 693 | Mage::dispatchEvent( 694 | 'ecomdev_urlrewrite_indexer_generate_category_relation_index_before', 695 | array('resource' => $this, 'category_ids' => $categoryIds) 696 | ); 697 | 698 | $condition = array(); 699 | 700 | if ($categoryIds !== null) { 701 | $condition['category_id IN(?)'] = $categoryIds; 702 | } 703 | 704 | $this->_getIndexAdapter()->delete( 705 | $this->getTable(self::CATEGORY_RELATION), 706 | $condition 707 | ); 708 | 709 | // Generate nested categories index 710 | $select = $this->_select(); 711 | $select->from(array('main' => $this->getTable('catalog/category')), array('category_id' => 'entity_id')) 712 | ->join(array('child' => $this->getTable('catalog/category')), 713 | 'child.path LIKE CONCAT(main.path, \'/%\')', 714 | array('related_id' => 'entity_id')) 715 | ->columns(array('type' => new Zend_Db_Expr($this->_quoteInto('?', self::RELATION_TYPE_NESTED)))); 716 | 717 | $select->where('main.level > ?', 1); 718 | $select->where('main.children_count > ?', 0); 719 | 720 | if ($categoryIds !== null) { 721 | $select->where('main.entity_id IN(?)', $categoryIds); 722 | } 723 | 724 | Mage::dispatchEvent( 725 | 'ecomdev_urlrewrite_indexer_generate_category_relation_index_nested', 726 | array('select' => $select, 'category_ids' => $categoryIds) 727 | ); 728 | 729 | $this->_getIndexAdapter()->query( 730 | $select->insertFromSelect( 731 | $this->getTable(self::CATEGORY_RELATION), 732 | $select->getColumnAliases() 733 | ) 734 | ); 735 | 736 | // Generate index for achor relations 737 | $select->reset(); 738 | $isAnchorAttribute = $this->_getEavConfig()->getAttribute(self::ENTITY_CATEGORY, 'is_anchor'); 739 | 740 | $select 741 | ->from( 742 | array('relation' => $this->getTable(self::CATEGORY_RELATION)), 743 | array( 744 | 'category_id', 'related_id', 745 | 'type' => new Zend_Db_Expr($this->_quoteInto('?', self::RELATION_TYPE_ANCHOR)) 746 | ) 747 | ) 748 | ->join( 749 | array('is_anchor' => $isAnchorAttribute->getBackendTable()), 750 | 'is_anchor.entity_id = relation.category_id' 751 | . ' AND is_anchor.store_id = 0' 752 | . $this->_quoteInto(' AND is_anchor.attribute_id = ?', $isAnchorAttribute->getAttributeId()), 753 | array()) 754 | ->where('is_anchor.value = ?', 1); 755 | 756 | if ($categoryIds !== null) { 757 | $select->where('relation.category_id IN(?)', $categoryIds); 758 | } 759 | 760 | Mage::dispatchEvent( 761 | 'ecomdev_urlrewrite_indexer_generate_category_relation_index_anchor', 762 | array('select' => $select, 'category_ids' => $categoryIds) 763 | ); 764 | 765 | $this->_getIndexAdapter()->query( 766 | $select->insertFromSelect( 767 | $this->getTable(self::CATEGORY_RELATION), 768 | $select->getColumnAliases() 769 | ) 770 | ); 771 | 772 | Mage::dispatchEvent( 773 | 'ecomdev_urlrewrite_indexer_generate_category_relation_index_after', 774 | array('resource' => $this, 'category_ids' => $categoryIds) 775 | ); 776 | return $this; 777 | } 778 | 779 | /** 780 | * Returns data from category request path index table. 781 | * Result is an array where key is category id and value is an array of request path 782 | * for each store view where its applicable 783 | * 784 | * @param array|int $categoryIds 785 | * @return array 786 | */ 787 | public function getCategoryRequestPathIndex($categoryIds) 788 | { 789 | if (!is_array($categoryIds)) { 790 | $categoryIds = array($categoryIds); 791 | } 792 | 793 | $select = $this->_select() 794 | ->from($this->getTable(self::CATEGORY_REQUEST_PATH)) 795 | ->where('category_id IN(?)', $categoryIds); 796 | 797 | $result = array(); 798 | foreach ($this->_getReadAdapter()->fetchAll($select) as $requestPath) { 799 | $result[$requestPath['category_id']][$requestPath['store_id']] = $requestPath['request_path']; 800 | } 801 | 802 | return $result; 803 | } 804 | 805 | /** 806 | * Get all category ids including releated 807 | * 808 | * @param array $categoryIds 809 | * @return array 810 | */ 811 | protected function _getCategoryIds(array $categoryIds, $type = self::RELATION_TYPE_NESTED) 812 | { 813 | $select = $this->_getRelatedCategoryIdsSelect($categoryIds, $type); 814 | return array_merge( 815 | $this->_getReadAdapter()->fetchCol($select), 816 | $categoryIds 817 | ); 818 | } 819 | 820 | /** 821 | * Update category url key index 822 | * 823 | * @param array $categoryIds 824 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 825 | */ 826 | protected function _generateCategoryUrlKeyIndex(array $categoryIds = null) 827 | { 828 | $this->_getIndexAdapter()->beginTransaction(); 829 | $select = $this->_getCategoryUrlKeySelect(); 830 | 831 | if ($categoryIds !== null) { 832 | $select->where('category.entity_id IN(?)', $categoryIds); 833 | } 834 | 835 | $result = $this->_getIndexAdapter()->query($select->insertFromSelect( 836 | $this->getTable(self::CATEGORY_URL_KEY), 837 | $select->getColumnAliases() 838 | )); 839 | 840 | $this->_getIndexAdapter()->commit(); 841 | 842 | if ($result->rowCount()) { 843 | $this->_getIndexAdapter()->update( 844 | $this->getTable(self::CATEGORY_URL_KEY), 845 | array( 846 | 'url_key' => new Zend_Db_Expr('ECOMDEV_CLEAN_URL_KEY(url_key_source)'), 847 | 'updated' => 0 848 | ), 849 | array( 850 | 'updated = ?' => 1 851 | ) 852 | ); 853 | } 854 | 855 | // Clear not existent rows 856 | $select->reset() 857 | ->from(array('url_key' => $this->getTable(self::CATEGORY_URL_KEY)), array()) 858 | ->joinLeft( 859 | array('root_category' => $this->getTable(self::ROOT_CATEGORY)), 860 | 'root_category.store_id = url_key.store_id', 861 | array() 862 | ) 863 | ->joinLeft( 864 | array('category' => $this->getTable('catalog/category')), 865 | 'category.entity_id = url_key.category_id AND category.path LIKE root_category.path', 866 | array() 867 | ) 868 | ->where('category.entity_id IS NULL'); 869 | 870 | $this->_getIndexAdapter()->query( 871 | $select->deleteFromSelect('url_key') 872 | ); 873 | 874 | 875 | return $this; 876 | } 877 | 878 | /** 879 | * Generates category url path index table data 880 | * 881 | * @param array|null $categoryIds 882 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 883 | */ 884 | protected function _generateCategoryRequestPathIndex(array $categoryIds = null) 885 | { 886 | Mage::dispatchEvent( 887 | 'ecomdev_urlrewrite_indexer_generate_category_url_path_index_before', 888 | array('resource' => $this, 'category_ids' => $categoryIds) 889 | ); 890 | 891 | $this->_generateCategoryRelationIndex($categoryIds); 892 | 893 | if ($categoryIds !== null) { 894 | $categoryIds = $this->_getCategoryIds($categoryIds); 895 | } else { 896 | $condition = ''; 897 | } 898 | 899 | $this->_generateCategoryUrlKeyIndex($categoryIds); 900 | 901 | if ($categoryIds === null) { 902 | $this->_getIndexAdapter()->truncate( 903 | $this->getTable(self::CATEGORY_REQUEST_PATH) 904 | ); 905 | } else { 906 | $this->_getIndexAdapter()->delete( 907 | $this->getTable(self::CATEGORY_REQUEST_PATH), 908 | array('category_id IN(?)' => $categoryIds) 909 | ); 910 | } 911 | 912 | $select = $this->_getCategoryRequestPathSelect(); 913 | 914 | if ($categoryIds !== null) { 915 | $select->where('url_key.category_id IN(?)', $categoryIds); 916 | } 917 | 918 | $this->_getIndexAdapter()->query( 919 | $select->insertFromSelect( 920 | $this->getTable(self::CATEGORY_REQUEST_PATH), 921 | $select->getColumnAliases(), 922 | false 923 | ) 924 | ); 925 | 926 | Mage::dispatchEvent( 927 | 'ecomdev_urlrewrite_indexer_generate_category_url_path_index_after', 928 | array('resource' => $this, 'category_ids' => $categoryIds) 929 | ); 930 | 931 | return $this; 932 | } 933 | 934 | /** 935 | * Returns data from product request path index table. 936 | * Result is an array where key is product id and value is a multidimensional 937 | * array of store-category-request-path information. 938 | * 939 | * For store root it has record with 0 category id 940 | * 941 | * @param array|int $productIds 942 | * @return array 943 | */ 944 | public function getProductRequestPathIndex($productIds) 945 | { 946 | if (!is_array($productIds)) { 947 | $productIds = array($productIds); 948 | } 949 | 950 | $select = $this->_select() 951 | ->from($this->getTable(self::PRODUCT_REQUEST_PATH)) 952 | ->where('product_id IN(?)', $productIds); 953 | 954 | $result = array(); 955 | foreach ($this->_getReadAdapter()->fetchAll($select) as $requestPath) { 956 | $categoryKey = (isset($requestPath['category_id']) ? $requestPath['category_id'] : 0); 957 | $result[$requestPath['product_id']][$requestPath['store_id']][$categoryKey] = $requestPath['request_path']; 958 | } 959 | 960 | return $result; 961 | } 962 | 963 | /** 964 | * Generates product to category real association index, 965 | * Required for easy retrieve anchor category association 966 | * 967 | * @param array|null $productIds 968 | * @param array|null $categoryIds 969 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 970 | */ 971 | protected function _generateProductRelationIndex($productIds = null, $categoryIds = null) 972 | { 973 | if ($productIds === null && $categoryIds === null) { 974 | $this->_getIndexAdapter()->truncate($this->getTable(self::PRODUCT_RELATION)); 975 | } elseif ($categoryIds === null) { 976 | $this->_getIndexAdapter()->delete( 977 | $this->getTable(self::PRODUCT_RELATION), 978 | array('product_id IN(?)' => $productIds) 979 | ); 980 | } else { 981 | $this->_getIndexAdapter()->delete( 982 | $this->getTable(self::PRODUCT_RELATION), 983 | array('category_id IN(?)' => $categoryIds) 984 | ); 985 | } 986 | 987 | $this->_getIndexAdapter()->beginTransaction(); 988 | $select = $this->_select(); 989 | // Direct relations 990 | $select 991 | ->from( 992 | array('category' => $this->getTable(self::CATEGORY_URL_KEY)), 993 | 'store_id' 994 | ) 995 | ->join( 996 | array('category_product' => $this->getTable('catalog/category_product')), 997 | 'category_product.category_id = category.category_id', 998 | array('category_id', 'product_id') 999 | ); 1000 | 1001 | if ($categoryIds !== null) { 1002 | $select->where('category.category_id IN(?)', $categoryIds); 1003 | } elseif ($productIds !== null) { 1004 | $select->where('category_product.product_id IN(?)', $productIds); 1005 | } 1006 | 1007 | $this->_getIndexAdapter()->query( 1008 | $select->insertIgnoreFromSelect( 1009 | $this->getTable(self::PRODUCT_RELATION), 1010 | $select->getColumnAliases() 1011 | ) 1012 | ); 1013 | 1014 | $select->reset(Zend_Db_Select::FROM) 1015 | ->reset(Zend_Db_Select::COLUMNS); 1016 | // Anchor relations 1017 | $select 1018 | ->from( 1019 | array('category' => $this->getTable(self::CATEGORY_URL_KEY)), 1020 | 'store_id' 1021 | ) 1022 | ->join( 1023 | array('anchor' => $this->getTable(self::CATEGORY_RELATION)), 1024 | $this->_quoteInto( 1025 | 'anchor.category_id = category.category_id AND anchor.type = ?', 1026 | self::RELATION_TYPE_ANCHOR 1027 | ), 1028 | 'category_id' 1029 | ) 1030 | ->join( 1031 | array('category_product' => $this->getTable('catalog/category_product')), 1032 | 'category_product.category_id = anchor.related_id', 1033 | 'product_id' 1034 | ); 1035 | 1036 | $this->_getIndexAdapter()->query( 1037 | $select->insertIgnoreFromSelect( 1038 | $this->getTable(self::PRODUCT_RELATION), 1039 | $select->getColumnAliases() 1040 | ) 1041 | ); 1042 | 1043 | $this->_getIndexAdapter()->commit(); 1044 | } 1045 | 1046 | /** 1047 | * Update product url key index 1048 | * 1049 | * @param array|Varien_Db_Select|null $productIds 1050 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 1051 | */ 1052 | protected function _generateProductUrlKeyIndex($productIds = null) 1053 | { 1054 | $this->_getIndexAdapter()->beginTransaction(); 1055 | $select = $this->_getProductUrlKeySelect(); 1056 | 1057 | if ($productIds !== null) { 1058 | $select->where('product.entity_id IN(?)', $productIds); 1059 | } 1060 | 1061 | $result = $this->_getIndexAdapter()->query($select->insertFromSelect( 1062 | $this->getTable(self::PRODUCT_URL_KEY), 1063 | $select->getColumnAliases() 1064 | )); 1065 | 1066 | $this->_getIndexAdapter()->commit(); 1067 | 1068 | if ($result->rowCount()) { 1069 | $this->_getIndexAdapter()->update( 1070 | $this->getTable(self::PRODUCT_URL_KEY), 1071 | array( 1072 | 'url_key' => new Zend_Db_Expr('ECOMDEV_CLEAN_URL_KEY(url_key_source)'), 1073 | 'updated' => 0 1074 | ), 1075 | array( 1076 | 'updated = ?' => 1 1077 | ) 1078 | ); 1079 | } 1080 | 1081 | // Clear not existent rows 1082 | $select->reset() 1083 | ->from(array('url_key' => $this->getTable(self::PRODUCT_URL_KEY)), array()) 1084 | ->joinLeft( 1085 | array('store' => $this->getTable('core/store')), 1086 | 'store.store_id = url_key.store_id', 1087 | array() 1088 | ) 1089 | ->joinLeft( 1090 | array('product_website' => $this->getTable('catalog/product_website')), 1091 | 'product_website.product_id = url_key.product_id AND store.website_id = product_website.website_id', 1092 | array() 1093 | ) 1094 | ->where('product_website.product_id IS NULL'); 1095 | 1096 | $this->_getIndexAdapter()->query( 1097 | $select->deleteFromSelect('url_key') 1098 | ); 1099 | 1100 | return $this; 1101 | } 1102 | 1103 | /** 1104 | * Generates product url path index by category ids or by product ids 1105 | * If no parameters specified, rebuilds all index 1106 | * 1107 | * @param array|null $categoryIds 1108 | * @param array|null $productIds 1109 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 1110 | */ 1111 | protected function _generateProductRequestPathIndex(array $categoryIds = null, array $productIds = null) 1112 | { 1113 | Mage::dispatchEvent( 1114 | 'ecomdev_urlrewrite_indexer_generate_product_url_path_index_before', 1115 | array('resource' => $this, 'category_ids' => $categoryIds, 'product_ids' => $productIds) 1116 | ); 1117 | if ($categoryIds !== null) { 1118 | $categoryIds = $this->_getCategoryIds($categoryIds); 1119 | $conditionSelect = $this->_select(); 1120 | $conditionSelect 1121 | ->from( 1122 | array('category_product' => $this->getTable('catalog/category_product')), 1123 | array('product_id') 1124 | ) 1125 | ->where('category_product.category_id IN(?)', $categoryIds); 1126 | 1127 | $condition = array( 1128 | 'product_id IN(?)' => $conditionSelect 1129 | ); 1130 | } elseif ($productIds !== null) { 1131 | $conditionSelect = $productIds; 1132 | $condition = array( 1133 | 'product_id IN(?)' => $conditionSelect 1134 | ); 1135 | } else { 1136 | $condition = ''; 1137 | $conditionSelect = null; 1138 | } 1139 | 1140 | $this->_generateProductRelationIndex($productIds, $categoryIds); 1141 | $this->_generateProductUrlKeyIndex($conditionSelect); 1142 | 1143 | if ($condition == '') { 1144 | $this->_getIndexAdapter()->truncate($this->getTable(self::PRODUCT_REQUEST_PATH)); 1145 | } else { 1146 | $this->_getIndexAdapter()->delete($this->getTable(self::PRODUCT_REQUEST_PATH), $condition); 1147 | } 1148 | 1149 | $this->_getIndexAdapter()->beginTransaction(); 1150 | 1151 | $rewriteGenerationTypes = array(false, true); 1152 | 1153 | if (!Mage::getStoreConfig(Mage_Catalog_Helper_Product::XML_PATH_PRODUCT_URL_USE_CATEGORY)) { 1154 | array_pop($rewriteGenerationTypes); 1155 | } 1156 | 1157 | // Initialize rewrite request path data 1158 | foreach ($rewriteGenerationTypes as $categoryRewriteFlag) { 1159 | $select = $this->_getProductRequestPathSelect($categoryRewriteFlag); 1160 | 1161 | if (isset($conditionSelect)) { 1162 | $select->where('url_key.product_id IN(?)', $conditionSelect); 1163 | } 1164 | 1165 | $this->_getIndexAdapter()->query( 1166 | $select->insertIgnoreFromSelect( 1167 | $this->getTable(self::PRODUCT_REQUEST_PATH), 1168 | $select->getColumnAliases() 1169 | ) 1170 | ); 1171 | } 1172 | 1173 | $this->_getIndexAdapter()->commit(); 1174 | 1175 | Mage::dispatchEvent( 1176 | 'ecomdev_urlrewrite_indexer_generate_product_url_path_index_after', 1177 | array('resource' => $this, 'category_ids' => $categoryIds, 'product_ids' => $productIds) 1178 | ); 1179 | return $this; 1180 | } 1181 | 1182 | /** 1183 | * This method clears invalid rewrites from core url rewrite 1184 | * 1185 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 1186 | */ 1187 | public function clearInvalidRewrites() 1188 | { 1189 | $this->_clearInvalidCategoryRewrites() 1190 | ->_clearInvalidProductRewrites(); 1191 | return $this; 1192 | } 1193 | 1194 | /** 1195 | * Clears dirty records in url rewrite for invalid category-store combination 1196 | * 1197 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 1198 | */ 1199 | protected function _clearInvalidCategoryRewrites() 1200 | { 1201 | $select = $this->_select(); 1202 | $select 1203 | ->from(array('rewrite' => $this->getTable('core/url_rewrite')), 'url_rewrite_id') 1204 | ->join( 1205 | array('category' => $this->getTable('catalog/category')), 1206 | 'category.entity_id = rewrite.category_id' 1207 | ) 1208 | ->join( 1209 | array('root_category' => $this->getTable(self::ROOT_CATEGORY)), 1210 | 'root_category.store_id = rewrite.store_id ' 1211 | ) 1212 | // If category is not in a store root category where rewrite is. 1213 | ->where('category.path NOT LIKE root_category.path'); 1214 | 1215 | Mage::dispatchEvent( 1216 | 'ecomdev_urlrewrite_indexer_clear_invalid_category_rewrites_select', 1217 | array('resource' => $this, 'select' => $select) 1218 | ); 1219 | 1220 | $this->_getIndexAdapter()->query( 1221 | $select->deleteFromSelect('rewrite') 1222 | ); 1223 | 1224 | return $this; 1225 | } 1226 | 1227 | /** 1228 | * Clears dirty records in url rewrite for invalid product-website combination 1229 | * 1230 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 1231 | */ 1232 | protected function _clearInvalidProductRewrites() 1233 | { 1234 | $select = $this->_select(); 1235 | $select 1236 | ->from(array('rewrite' => $this->getTable('core/url_rewrite')), 'url_rewrite_id') 1237 | ->join( 1238 | array('store' => $this->getTable('core/store')), 1239 | 'store.store_id = rewrite.store_id ' 1240 | ) 1241 | ->joinLeft( 1242 | array('product_website' => $this->getTable('catalog/product_website')), 1243 | 'product_website.website_id = store.website_id ' 1244 | . ' AND product_website.product_id = rewrite.product_id' 1245 | ) 1246 | // If product is not assigned to a website where url rewrite is. 1247 | ->where('rewrite.product_id IS NOT NULL') 1248 | ->where('product_website.product_id IS NULL'); 1249 | 1250 | Mage::dispatchEvent( 1251 | 'ecomdev_urlrewrite_indexer_clear_invalid_product_rewrites_select_root', 1252 | array('resource' => $this, 'select' => $select) 1253 | ); 1254 | 1255 | $this->_getIndexAdapter()->query( 1256 | $select->deleteFromSelect('rewrite') 1257 | ); 1258 | 1259 | $select 1260 | ->reset() 1261 | ->from(array('rewrite' => $this->getTable('core/url_rewrite')), 'url_rewrite_id') 1262 | ->joinLeft( 1263 | array('product_category' => $this->getTable('catalog/category_product')), 1264 | 'product_category.category_id = rewrite.category_id ' 1265 | . ' AND product_category.product_id = rewrite.product_id' 1266 | ) 1267 | // If product is not assigned to a category where url rewrite is. 1268 | ->where('rewrite.product_id IS NOT NULL') 1269 | ->where('rewrite.category_id IS NOT NULL') 1270 | ->where('product_category.category_id IS NULL'); 1271 | 1272 | Mage::dispatchEvent( 1273 | 'ecomdev_urlrewrite_indexer_clear_invalid_product_rewrites_select_category', 1274 | array('resource' => $this, 'select' => $select) 1275 | ); 1276 | 1277 | $this->_getIndexAdapter()->query( 1278 | $select->deleteFromSelect('rewrite') 1279 | ); 1280 | 1281 | return $this; 1282 | } 1283 | 1284 | /** 1285 | * Returns list of suffixes for every store view 1286 | * if it differs from default. 1287 | * 1288 | * @param string $entity 1289 | * @return array 1290 | */ 1291 | public function getUrlSuffixList($entity) 1292 | { 1293 | $callbacks = array( 1294 | self::ENTITY_CATEGORY => array( 1295 | Mage::helper('catalog/category'), 1296 | 'getCategoryUrlSuffix' 1297 | ), 1298 | self::ENTITY_PRODUCT => array( 1299 | Mage::helper('catalog/product'), 1300 | 'getProductUrlSuffix' 1301 | ) 1302 | ); 1303 | 1304 | if (!isset($callbacks[$entity])) { 1305 | $entity = self::ENTITY_CATEGORY; 1306 | } 1307 | 1308 | $result[] = call_user_func($callbacks[$entity], Mage_Core_Model_App::ADMIN_STORE_ID); 1309 | 1310 | foreach (Mage::app()->getStores() as $store) { 1311 | $suffix = call_user_func($callbacks[$entity], $store->getId()); 1312 | if ($suffix != $result[0]) { 1313 | $result[$store->getId()] = $suffix; 1314 | } 1315 | } 1316 | 1317 | return $result; 1318 | } 1319 | 1320 | /** 1321 | * Return unique url suffix list 1322 | * 1323 | * @return array 1324 | */ 1325 | public function getUniqueUrlSuffixList() 1326 | { 1327 | $result = array_values($this->getUrlSuffixList(self::ENTITY_CATEGORY)); 1328 | $result = array_merge($result, array_values($this->getUrlSuffixList(self::ENTITY_PRODUCT))); 1329 | 1330 | return array_filter(array_unique($result)); 1331 | } 1332 | 1333 | /** 1334 | * Updates data from core url rewrite table 1335 | * 1336 | * Created to keep information up to date 1337 | * 1338 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 1339 | */ 1340 | protected function _importFromRewrite() 1341 | { 1342 | $select = $this->_select(); 1343 | $select 1344 | ->from(array('core_rewrite' => $this->getTable('core/url_rewrite')), array()); 1345 | 1346 | $suffixExpr = 'core_rewrite.request_path'; 1347 | 1348 | foreach ($this->getUniqueUrlSuffixList() as $suffix) { 1349 | $suffixExpr = $this->_quoteInto('TRIM(TRAILING ? FROM ' . $suffixExpr . ')', $suffix); 1350 | } 1351 | 1352 | $duplicateKeyExpr = new Zend_Db_Expr( 1353 | $suffixExpr 1354 | ); 1355 | 1356 | $columns = array( 1357 | 'rewrite_id' => 'url_rewrite_id', 1358 | 'id_path' => 'id_path', 1359 | 'store_id' => 'store_id', 1360 | 'category_id' => 'category_id', 1361 | 'product_id' => 'product_id', 1362 | 'duplicate_key' => $duplicateKeyExpr, 1363 | 'original_request_path' => 'request_path', 1364 | 'request_path' => 'request_path', 1365 | 'target_path' => 'target_path', 1366 | 'updated' => new Zend_Db_Expr('1') 1367 | ); 1368 | 1369 | $select->columns($columns); 1370 | 1371 | Mage::dispatchEvent( 1372 | 'ecomdev_urlrewrite_indexer_import_from_rewrite_select_insert', 1373 | array('resource' => $this, 'select' => $select, 'columns' => $columns) 1374 | ); 1375 | 1376 | $this->_getIndexAdapter()->query( 1377 | $select->insertIgnoreFromSelect( 1378 | $this->getTable(self::REWRITE), 1379 | $select->getColumnAliases() 1380 | ) 1381 | ); 1382 | 1383 | foreach (array('id_path', 'store_id', 'target_path', 1384 | 'category_id', 'product_id') as $key) { 1385 | unset($columns[$key]); 1386 | } 1387 | 1388 | $select->reset() 1389 | ->join( 1390 | array('core_rewrite' => $this->getTable('core/url_rewrite')), 1391 | 'core_rewrite.store_id = rewrite_index.store_id AND core_rewrite.id_path = rewrite_index.id_path', 1392 | $columns 1393 | ) 1394 | ->where('rewrite_index.updated = ?', 0) 1395 | ->where('rewrite_index.request_path != core_rewrite.request_path'); 1396 | 1397 | $this->_getIndexAdapter()->query( 1398 | $select->crossUpdateFromSelect( 1399 | array('rewrite_index' => $this->getTable(self::REWRITE)) 1400 | ) 1401 | ); 1402 | 1403 | $this->_importDuplicatedKeys(); 1404 | $this->_finalizeRowsUpdate(self::REWRITE); 1405 | return $this; 1406 | } 1407 | 1408 | /** 1409 | * Marks records as updated in a particular index table 1410 | * 1411 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 1412 | */ 1413 | protected function _finalizeRowsUpdate($table) 1414 | { 1415 | $this->_getIndexAdapter()->update( 1416 | $this->getTable($table), 1417 | array('updated' => 0), 1418 | array('updated = ?' => 1) 1419 | ); 1420 | } 1421 | 1422 | /** 1423 | * Updates duplicate keys information that was inserted to rewrite table 1424 | * 1425 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 1426 | */ 1427 | protected function _importDuplicatedKeys() 1428 | { 1429 | $this->_getIndexAdapter()->beginTransaction(); 1430 | $select = $this->_select(); 1431 | $select 1432 | ->from( 1433 | array('rewrite' => $this->getTable(self::REWRITE)), 1434 | array() 1435 | ) 1436 | ->where('rewrite.updated=?', 1) 1437 | ->where('rewrite.duplicate_index IS NULL') 1438 | ->where('rewrite.duplicate_key REGEXP ?', '^[0-9a-z\\-]+-[0-9]+$'); 1439 | 1440 | $columns = array( 1441 | 'store_id' => 'store_id', 1442 | 'id_path' => 'id_path', 1443 | 'duplicate_key' => new Zend_Db_Expr($this->_quoteInto( 1444 | 'SUBSTR(' 1445 | . 'rewrite.duplicate_key, 1, ' 1446 | . 'LENGTH(rewrite.duplicate_key) - 1 -' 1447 | . 'LENGTH(SUBSTRING_INDEX(rewrite.duplicate_key, ?, -1))' 1448 | . ')', 1449 | '-' 1450 | )), 1451 | 'duplicate_index' => new Zend_Db_Expr( 1452 | $this->_quoteInto('SUBSTRING_INDEX(rewrite.duplicate_key, ?, -1)', '-') 1453 | ) 1454 | ); 1455 | 1456 | $select->columns($columns); 1457 | 1458 | Mage::dispatchEvent( 1459 | 'ecomdev_urlrewrite_indexer_import_duplicated_keys_select', 1460 | array('resource' => $this, 'select' => $select, 'columns' => $columns) 1461 | ); 1462 | 1463 | $result = $this->_getIndexAdapter()->query( 1464 | $select->insertIgnoreFromSelect( 1465 | $this->getTable(self::DUPLICATE), 1466 | $select->getColumnAliases() 1467 | ) 1468 | ); 1469 | 1470 | // Genereate list of keys that are presented only ones, 1471 | // E.g. is not duplicates for removal of them later 1472 | $select->reset() 1473 | ->from( 1474 | $this->getTable(self::DUPLICATE), 1475 | array('duplicate_key', 'store_id') 1476 | ) 1477 | ->group(array('duplicate_key', 'store_id')) 1478 | ->having('COUNT(*) = ?', 1); 1479 | 1480 | $this->_getIndexAdapter()->query( 1481 | $select->insertFromSelect( 1482 | $this->getTable(self::DUPLICATE_KEY), 1483 | $select->getColumnAliases(), 1484 | false 1485 | ) 1486 | ); 1487 | 1488 | $select->reset() 1489 | ->from( 1490 | array('duplicate' => $this->getTable(self::DUPLICATE)) 1491 | ) 1492 | ->join( 1493 | array('not_duplicate' => $this->getTable(self::DUPLICATE_KEY)), 1494 | 'not_duplicate.duplicate_key = duplicate.duplicate_key ' 1495 | . 'AND not_duplicate.store_id = duplicate.store_id ' 1496 | ); 1497 | 1498 | $this->_getIndexAdapter()->query( 1499 | $select->deleteFromSelect('duplicate') 1500 | ); 1501 | 1502 | $this->_getIndexAdapter()->commit(); 1503 | 1504 | $this->_updateRewriteDuplicates(); 1505 | 1506 | return $this; 1507 | } 1508 | 1509 | /** 1510 | * Update rewrites from duplicates 1511 | * 1512 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 1513 | */ 1514 | protected function _updateRewriteDuplicates() 1515 | { 1516 | $select = $this->_select(); 1517 | 1518 | $columns = array( 1519 | 'duplicate_key' => 'duplicate_key', 1520 | 'duplicate_index' => 'duplicate_index' 1521 | ); 1522 | 1523 | $select->join( 1524 | array('duplicate' => $this->getTable(self::DUPLICATE)), 1525 | 'duplicate.store_id = rewrite.store_id ' 1526 | . 'AND duplicate.id_path = rewrite.id_path ', 1527 | $columns 1528 | ); 1529 | 1530 | $this->_getIndexAdapter()->query( 1531 | $select->crossUpdateFromSelect(array('rewrite' => $this->getTable(self::REWRITE))) 1532 | ); 1533 | 1534 | $this->_clearDuplicates(); 1535 | 1536 | return $this; 1537 | } 1538 | 1539 | /** 1540 | * Updates rewrites from category url path indexe 1541 | * 1542 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 1543 | */ 1544 | protected function _importFromCategoryRequestPath() 1545 | { 1546 | $this->_getIndexAdapter()->beginTransaction(); 1547 | $select = $this->_select(); 1548 | $select 1549 | ->from( 1550 | array('rewrite' => $this->getTable(self::REWRITE)), 1551 | array() 1552 | ) 1553 | ->join( 1554 | array('request_path' => $this->getTable(self::CATEGORY_REQUEST_PATH)), 1555 | 'request_path.store_id = rewrite.store_id' 1556 | . ' AND request_path.id_path = rewrite.id_path', 1557 | array() 1558 | ) 1559 | ->where('request_path.updated = ?', 1); 1560 | 1561 | $columns = array( 1562 | 'target_path' => new Zend_Db_Expr($this->_quoteInto( 1563 | sprintf( 1564 | $this->_pathGenerateExpr[self::TARGET_PATH_CATEGORY], 1565 | 'request_path.category_id' 1566 | ), 1567 | self::TARGET_PATH_CATEGORY 1568 | )), 1569 | 'duplicate_key' => 'request_path.request_path', 1570 | 'duplicate_index' => new Zend_Db_Expr($this->_quoteInto( 1571 | 'IF(rewrite.duplicate_index IS NOT NULL ' 1572 | . ' AND SUBSTRING_INDEX(rewrite.duplicate_key, ?, -1) = SUBSTRING_INDEX(request_path.request_path, ?, -1), ' 1573 | . ' rewrite.duplicate_index, ' 1574 | . ' IF(request_path.request_path REGEXP \'[0-9]$\', 0, NULL))', 1575 | '/' 1576 | )), 1577 | 'updated' => new Zend_Db_Expr('1'), 1578 | // This one updated request path index 1579 | 'request_path.updated' => new Zend_Db_Expr('0') 1580 | ); 1581 | 1582 | $select->columns($columns); 1583 | 1584 | Mage::dispatchEvent( 1585 | 'ecomdev_urlrewrite_indexer_import_from_category_request_path_update', 1586 | array('select' => $select, 'columns' => $columns, 'resource' => $this) 1587 | ); 1588 | 1589 | $select->crossUpdateFromSelectImproved(); 1590 | $this->_getIndexAdapter()->commit(); 1591 | 1592 | $this->_getIndexAdapter()->beginTransaction(); 1593 | unset($columns['request_path.updated']); 1594 | 1595 | $columns['duplicate_index'] = new Zend_Db_Expr( 1596 | 'IF(request_path.request_path REGEXP \'[0-9]$\', 0, NULL)' 1597 | ); 1598 | 1599 | $columns = array( 1600 | 'store_id' => 'request_path.store_id', 1601 | 'id_path' => 'request_path.id_path', 1602 | 'category_id' => 'request_path.category_id' 1603 | ) + $columns; 1604 | 1605 | $select->reset(Varien_Db_Select::FROM) 1606 | ->reset(Varien_Db_Select::COLUMNS) 1607 | ->from( 1608 | array('request_path' => $this->getTable(self::CATEGORY_REQUEST_PATH)), 1609 | array() 1610 | ) 1611 | ->columns($columns); 1612 | 1613 | Mage::dispatchEvent( 1614 | 'ecomdev_urlrewrite_indexer_import_from_category_request_path_insert', 1615 | array('select' => $select, 'columns' => $columns, 'resource' => $this) 1616 | ); 1617 | 1618 | $this->_getIndexAdapter()->query( 1619 | $select->insertIgnoreFromSelect( 1620 | $this->getTable(self::REWRITE), 1621 | $select->getColumnAliases() 1622 | ) 1623 | ); 1624 | 1625 | $this->_finalizeRowsUpdate(self::CATEGORY_REQUEST_PATH); 1626 | $this->_getIndexAdapter()->commit(); 1627 | return $this; 1628 | } 1629 | 1630 | /** 1631 | * Updates rewrites from product url path indexe 1632 | * 1633 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 1634 | */ 1635 | protected function _importFromProductRequestPath() 1636 | { 1637 | $this->_getIndexAdapter()->beginTransaction(); 1638 | // Target path generation for product url without category 1639 | $targetPathWithoutCategory = $this->_quoteInto( 1640 | sprintf( 1641 | $this->_pathGenerateExpr[self::TARGET_PATH_PRODUCT], 1642 | 'request_path.product_id' 1643 | ), 1644 | self::TARGET_PATH_PRODUCT 1645 | ); 1646 | 1647 | // Target path for generation of product url with category 1648 | $targetPathWithCategory = $this->_quoteInto( 1649 | sprintf( 1650 | $this->_pathGenerateExpr[self::TARGET_PATH_PRODUCT_CATEGORY], 1651 | 'request_path.product_id', 1652 | 'request_path.category_id' 1653 | ), 1654 | self::TARGET_PATH_PRODUCT_CATEGORY 1655 | ); 1656 | 1657 | // Depending on value in category id field uses proper target path expression 1658 | $targetPathExpr = new Zend_Db_Expr(sprintf( 1659 | 'IF(request_path.category_id IS NULL, %s, %s)', 1660 | $targetPathWithoutCategory, 1661 | $targetPathWithCategory 1662 | )); 1663 | 1664 | // Update records before inserting new ones 1665 | $select = $this->_select(); 1666 | $select 1667 | ->from( 1668 | array('rewrite' => $this->getTable(self::REWRITE)), 1669 | array() 1670 | ) 1671 | ->join( 1672 | array('request_path' => $this->getTable(self::PRODUCT_REQUEST_PATH)), 1673 | 'request_path.store_id = rewrite.store_id' 1674 | . ' AND request_path.id_path = rewrite.id_path', 1675 | array() 1676 | ) 1677 | ->where('request_path.updated = ?', 1); 1678 | 1679 | $columns = array( 1680 | 'duplicate_key' => 'request_path.request_path', 1681 | 'duplicate_index' => new Zend_Db_Expr($this->_quoteInto( 1682 | 'IF(rewrite.duplicate_index IS NOT NULL ' 1683 | . ' AND SUBSTRING_INDEX(rewrite.duplicate_key, ?, -1) = SUBSTRING_INDEX(request_path.request_path, ?, -1), ' 1684 | . ' rewrite.duplicate_index, ' 1685 | . ' IF(request_path.request_path REGEXP \'[0-9]$\', 0, NULL))', 1686 | '/' 1687 | )), 1688 | 'target_path' => $targetPathExpr, 1689 | 'updated' => new Zend_Db_Expr('1'), 1690 | // This one updated request path index 1691 | 'request_path.updated' => new Zend_Db_Expr('0') 1692 | ); 1693 | 1694 | $select->columns($columns); 1695 | 1696 | Mage::dispatchEvent( 1697 | 'ecomdev_urlrewrite_indexer_import_from_product_request_path_update', 1698 | array('select' => $select, 'columns' => $columns, 'resource' => $this) 1699 | ); 1700 | 1701 | 1702 | $select->crossUpdateFromSelectImproved(); 1703 | $this->_getIndexAdapter()->commit(); 1704 | 1705 | $this->_getIndexAdapter()->beginTransaction(); 1706 | unset($columns['request_path.updated']); 1707 | 1708 | $columns['duplicate_index'] = new Zend_Db_Expr( 1709 | 'IF(request_path.request_path REGEXP \'[0-9]$\', 0, NULL)' 1710 | ); 1711 | 1712 | $columns = array( 1713 | 'store_id' => 'request_path.store_id', 1714 | 'id_path' => 'request_path.id_path', 1715 | 'category_id' => 'request_path.category_id', 1716 | 'product_id' => 'request_path.product_id' 1717 | ) + $columns; 1718 | 1719 | $select->reset(Varien_Db_Select::FROM) 1720 | ->reset(Varien_Db_Select::COLUMNS) 1721 | ->from( 1722 | array('request_path' => $this->getTable(self::PRODUCT_REQUEST_PATH)), 1723 | array() 1724 | ) 1725 | ->columns($columns); 1726 | 1727 | 1728 | Mage::dispatchEvent( 1729 | 'ecomdev_urlrewrite_indexer_import_from_product_request_path_insert', 1730 | array('select' => $select, 'columns' => $columns, 'resource' => $this) 1731 | ); 1732 | 1733 | $this->_getIndexAdapter()->query( 1734 | $select->insertIgnoreFromSelect( 1735 | $this->getTable(self::REWRITE), 1736 | $select->getColumnAliases() 1737 | ) 1738 | ); 1739 | 1740 | $this->_finalizeRowsUpdate(self::PRODUCT_REQUEST_PATH); 1741 | $this->_getIndexAdapter()->commit(); 1742 | return $this; 1743 | } 1744 | 1745 | /** 1746 | * This operation prepares data for duplicates resolving 1747 | * 1748 | * First of all it fullfils information about records 1749 | * with updated keys (duplicate_updated), then based on it generates records that 1750 | * are real duplicates into duplicate_key table 1751 | * 1752 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 1753 | */ 1754 | protected function _updateDuplicatedKeysInformation() 1755 | { 1756 | $this->_getIndexAdapter()->beginTransaction(); 1757 | $select = $this->_select(); 1758 | 1759 | // Selecting all updated records with duplicate key 1760 | $select 1761 | ->from( 1762 | $this->getTable(self::REWRITE), 1763 | array('duplicate_key', 'store_id') 1764 | ) 1765 | ->where('updated = ?', 1) 1766 | ->group(array('duplicate_key', 'store_id')); 1767 | 1768 | $this->_getIndexAdapter()->query( 1769 | $select->insertFromSelect( 1770 | $this->getTable(self::DUPLICATE_UPDATED), 1771 | $select->getColumnAliases(), 1772 | false 1773 | ) 1774 | ); 1775 | $this->_getIndexAdapter()->commit(); 1776 | 1777 | $this->_getIndexAdapter()->beginTransaction(); 1778 | // Saving only real duplicates into dulplicate_key table 1779 | $select 1780 | ->reset() 1781 | ->from( 1782 | array('duplicate_updated' => $this->getTable(self::DUPLICATE_UPDATED)), 1783 | array('duplicate_key', 'store_id') 1784 | ) 1785 | ->join( 1786 | array('rewrite' => $this->getTable(self::REWRITE)), 1787 | 'rewrite.duplicate_key = duplicate_updated.duplicate_key ' 1788 | . 'AND rewrite.store_id = duplicate_updated.store_id ', 1789 | array() 1790 | ) 1791 | ->indexForce('rewrite', 'IDX_DUPLICATE_KEY_STORE') 1792 | ->group(array('duplicate_updated.duplicate_key', 'duplicate_updated.store_id')) 1793 | ->having('COUNT(*) > ?', 1); 1794 | 1795 | $this->_getIndexAdapter()->query( 1796 | $select->insertFromSelect( 1797 | $this->getTable(self::DUPLICATE_KEY), 1798 | $select->getColumnAliases(), 1799 | false 1800 | ) 1801 | ); 1802 | 1803 | $this->_getIndexAdapter()->commit(); 1804 | $this->_getIndexAdapter()->truncate($this->getTable(self::DUPLICATE_UPDATED)); 1805 | 1806 | return $this; 1807 | } 1808 | 1809 | /** 1810 | * Resolve duplicates with request_path 1811 | * 1812 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 1813 | */ 1814 | protected function _resolveDuplicates() 1815 | { 1816 | $this->_updateDuplicatedKeysInformation(); 1817 | 1818 | $this->_getIndexAdapter()->beginTransaction(); 1819 | 1820 | $select = $this->_select(); 1821 | // Preparing data for duplicates table 1822 | $select 1823 | ->from( 1824 | array('rewrite' => $this->getTable(self::REWRITE)), 1825 | array() 1826 | ) 1827 | ->indexForce('rewrite', 'IDX_DUPLICATE_STORE') 1828 | ->join( 1829 | array('duplicate' => $this->getTable(self::DUPLICATE_KEY)), 1830 | 'duplicate.duplicate_key = rewrite.duplicate_key ' 1831 | . ' AND duplicate.store_id = rewrite.store_id', 1832 | array() 1833 | ) 1834 | ->where('rewrite.updated = ?', 1) 1835 | ->where('rewrite.duplicate_index IS NULL'); 1836 | 1837 | $columns = array( 1838 | 'store_id' => 'rewrite.store_id', 1839 | 'id_path' => 'rewrite.id_path', 1840 | 'duplicate_key' => 'rewrite.duplicate_key' 1841 | ); 1842 | 1843 | $select->columns($columns); 1844 | 1845 | $result = $this->_getWriteAdapter()->query( 1846 | $select->insertIgnoreFromSelect( 1847 | $this->getTable(self::DUPLICATE), 1848 | $select->getColumnAliases() 1849 | ) 1850 | ); 1851 | 1852 | $select->reset(Varien_Db_Select::FROM) 1853 | ->reset(Varien_Db_Select::WHERE) 1854 | ->from( 1855 | array('rewrite' => $this->getTable(self::REWRITE)), 1856 | array() 1857 | ) 1858 | ->where('rewrite.updated = ?', 1) 1859 | ->where('rewrite.duplicate_index = ?', 0); 1860 | 1861 | $result = $this->_getWriteAdapter()->query( 1862 | $select->insertIgnoreFromSelect( 1863 | $this->getTable(self::DUPLICATE), 1864 | $select->getColumnAliases() 1865 | ) 1866 | ); 1867 | 1868 | $this->_getIndexAdapter()->commit(); 1869 | 1870 | $this->_getIndexAdapter()->beginTransaction(); 1871 | 1872 | $select->reset() 1873 | ->from($this->getTable(self::DUPLICATE), array('store_id', 'id_path', 'duplicate_key')) 1874 | ->order(array('duplicate_key ASC', 'store_id ASC')); 1875 | 1876 | $this->_getIndexAdapter()->query( 1877 | $select->insertIgnoreFromSelect( 1878 | $this->getTable(self::DUPLICATE_INCREMENT), 1879 | $select->getColumnAliases() 1880 | ) 1881 | ); 1882 | 1883 | $this->_getIndexAdapter()->commit(); 1884 | 1885 | $this->_getIndexAdapter()->beginTransaction(); 1886 | $select->reset() 1887 | ->from( 1888 | array('rewrite' => $this->getTable(self::REWRITE)), 1889 | array('duplicate_key', 'store_id', 'max_index' => new Zend_Db_Expr('MAX(rewrite.duplicate_index)')) 1890 | )->group(array('rewrite.duplicate_key', 'rewrite.store_id')); 1891 | 1892 | $this->_getIndexAdapter()->query( 1893 | $select->insertIgnoreFromSelect( 1894 | $this->getTable(self::DUPLICATE_AGGREGATE), 1895 | $select->getColumnAliases() 1896 | ) 1897 | ); 1898 | 1899 | $select->reset() 1900 | ->from( 1901 | array('min_duplicate' => $this->getTable(self::DUPLICATE_INCREMENT)), 1902 | array('duplicate_key','store_id', 'min_duplicate_id' => new Zend_Db_Expr('MIN(min_duplicate.duplicate_id)')) 1903 | ); 1904 | 1905 | $select->group(array('min_duplicate.duplicate_key', 'min_duplicate.store_id')); 1906 | 1907 | // Changed because of issues with duplicate index calculations 1908 | $this->_getIndexAdapter()->query( 1909 | $select->insertFromSelect( 1910 | $this->getTable(self::DUPLICATE_AGGREGATE), 1911 | $select->getColumnAliases() 1912 | ) 1913 | ); 1914 | 1915 | $this->_getIndexAdapter()->commit(); 1916 | 1917 | $this->_getIndexAdapter()->beginTransaction(); 1918 | 1919 | $columns = array( 1920 | 'duplicate_index' => new Zend_Db_Expr( 1921 | // If it is fisrt time duplicate it starts from second element with such formula 1+id diff 1922 | // If not, then it uses max+1+id diff 1923 | 'IF(aggregate.max_index IS NULL, ' 1924 | . 'IF(duplicate_increment.duplicate_id = aggregate.min_duplicate_id, NULL, 0), ' 1925 | . 'aggregate.max_index + 1' 1926 | . ') + duplicate_increment.duplicate_id - aggregate.min_duplicate_id' 1927 | ) 1928 | ); 1929 | 1930 | $select->reset(); 1931 | $select 1932 | ->joinStraight( 1933 | array('aggregate' => $this->getTable(self::DUPLICATE_AGGREGATE)), 1934 | 'aggregate.duplicate_key = duplicate_increment.duplicate_key ' 1935 | . 'AND aggregate.store_id = duplicate_increment.store_id', 1936 | $columns 1937 | ); 1938 | 1939 | $this->_getIndexAdapter()->query( 1940 | $select->crossUpdateFromSelect(array('duplicate_increment' => $this->getTable(self::DUPLICATE_INCREMENT))) 1941 | ); 1942 | 1943 | $select->reset() 1944 | ->join( 1945 | array('duplicate_increment' => $this->getTable(self::DUPLICATE_INCREMENT)), 1946 | 'duplicate_increment.store_id = duplicate.store_id ' 1947 | . 'AND duplicate_increment.id_path = duplicate.id_path', 1948 | 'duplicate_index' 1949 | ); 1950 | 1951 | $this->_getIndexAdapter()->query( 1952 | $select->crossUpdateFromSelect(array('duplicate' => $this->getTable(self::DUPLICATE))) 1953 | ); 1954 | 1955 | $this->_getIndexAdapter()->commit(); 1956 | $this->_updateRewriteDuplicates(); 1957 | 1958 | return $this; 1959 | } 1960 | 1961 | /** 1962 | * Clears data in duplicate tables 1963 | * 1964 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 1965 | */ 1966 | protected function _clearDuplicates() 1967 | { 1968 | $this->_getIndexAdapter()->truncate($this->getTable(self::DUPLICATE)); 1969 | $this->_getIndexAdapter()->truncate($this->getTable(self::DUPLICATE_KEY)); 1970 | $this->_getIndexAdapter()->truncate($this->getTable(self::DUPLICATE_UPDATED)); 1971 | $this->_getIndexAdapter()->truncate($this->getTable(self::DUPLICATE_INCREMENT)); 1972 | $this->_getIndexAdapter()->truncate($this->getTable(self::DUPLICATE_AGGREGATE)); 1973 | 1974 | return $this; 1975 | } 1976 | 1977 | /** 1978 | * Synchornizes data imported into rewrite table 1979 | * (resolves duplicates, generates final request path, 1980 | * updates core url rewrite tables) 1981 | * 1982 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 1983 | */ 1984 | protected function _updateRewrites() 1985 | { 1986 | $this->_resolveDuplicates(); 1987 | 1988 | $categoryRequestPathExpr = $this->_getRequestPathExpr(self::ENTITY_CATEGORY); 1989 | $productRequestPathExpr = $this->_getRequestPathExpr(self::ENTITY_PRODUCT); 1990 | 1991 | $originalRequestPathExpr = $this->_quoteInto('IF(request_path = ?, original_request_path, request_path)', ''); 1992 | $requestPathExpr = 'IF(product_id IS NULL, ' 1993 | . $categoryRequestPathExpr . ', ' 1994 | . $productRequestPathExpr . ')'; 1995 | 1996 | $this->_getIndexAdapter()->update( 1997 | $this->getTable(self::REWRITE), 1998 | array( 1999 | 'original_request_path' => new Zend_Db_Expr( 2000 | $originalRequestPathExpr 2001 | ), 2002 | 'request_path' => new Zend_Db_Expr( 2003 | $requestPathExpr 2004 | ), 2005 | // Only update changed rows in core url rewrite 2006 | 'updated' => new Zend_Db_Expr( 2007 | '(original_request_path IS NULL ' 2008 | . 'OR original_request_path != request_path)' 2009 | ) 2010 | ), 2011 | array( 2012 | 'updated = ?' => 1 2013 | ) 2014 | ); 2015 | 2016 | $this->_saveUrlHistory(); 2017 | 2018 | $select = $this->_select(); 2019 | $select->from($this->getTable(self::REWRITE), array()) 2020 | ->where('updated = ?', 1); 2021 | 2022 | $isSystemExpr = $this->_quoteInto('id_path REGEXP ?', '/'); 2023 | $columns = array( 2024 | 'store_id' => 'store_id', 2025 | 'id_path' => 'id_path', 2026 | 'category_id' => 'category_id', 2027 | 'product_id' => 'product_id', 2028 | 'request_path' => 'request_path', 2029 | 'target_path' => 'target_path', 2030 | 'is_system' => new Zend_Db_Expr( 2031 | 'IF(' . $isSystemExpr . ', 1, 0)' 2032 | ), 2033 | 'options' => new Zend_Db_Expr($this->_quoteInto( 2034 | 'IF(' . $isSystemExpr .', \'\', ?)', 2035 | 'RP' 2036 | )) 2037 | ); 2038 | 2039 | $select->columns($columns); 2040 | $this->_getIndexAdapter()->query( 2041 | $select->insertFromSelect( 2042 | $this->getTable('core/url_rewrite'), 2043 | $select->getColumnAliases() 2044 | ) 2045 | ); 2046 | 2047 | $select 2048 | ->reset() 2049 | ->from(array('rewrite' => $this->getTable(self::REWRITE)), array()) 2050 | ->join( 2051 | array('core_rewrite' => $this->getTable('core/url_rewrite')), 2052 | 'core_rewrite.store_id = rewrite.store_id AND core_rewrite.id_path = rewrite.id_path', 2053 | array( 2054 | 'rewrite_id' => 'url_rewrite_id', 2055 | 'updated' => 0 2056 | ) 2057 | ) 2058 | ->where('rewrite.rewrite_id IS NULL'); 2059 | 2060 | 2061 | $select->crossUpdateFromSelectImproved(); 2062 | 2063 | $this->_finalizeRowsUpdate(self::REWRITE); 2064 | return $this; 2065 | } 2066 | 2067 | /** 2068 | * Get url suffix db expr for specific entity and store id field 2069 | * 2070 | * @param string $entity 2071 | * @param string $storeField 2072 | * @param string $requestPathField 2073 | * 2074 | */ 2075 | protected function _getRequestPathExpr($entity) 2076 | { 2077 | $suffixes = $this->getUrlSuffixList($entity); 2078 | 2079 | $default = array_shift($suffixes); 2080 | 2081 | if (!$suffixes) { 2082 | $suffixExpr = $this->_quoteInto('?', $default); 2083 | } else { 2084 | $suffixExpr = 'CASE store_id'; 2085 | 2086 | foreach ($suffixes as $storeId => $suffix) { 2087 | $suffixExpr .= $this->_quoteInto(' WHEN ? ', $storeId) 2088 | . $this->_quoteInto(' THEN ? ', $suffix); 2089 | } 2090 | 2091 | $suffixExpr .= $this->_quoteInto(' ELSE ? END', $default); 2092 | } 2093 | 2094 | $dublicateExpr = $this->_quoteInto('CONCAT(?, duplicate_index)', '-'); 2095 | 2096 | return 'CONCAT(duplicate_key, IFNULL(' . $dublicateExpr . ', \'\'), ' . $suffixExpr . ')'; 2097 | } 2098 | 2099 | /** 2100 | * Saves url history if flag is set for store 2101 | * or forced 2102 | * 2103 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 2104 | */ 2105 | protected function _saveUrlHistory() 2106 | { 2107 | if ($this->isSaveHistory() === false) { 2108 | return $this; 2109 | } 2110 | 2111 | $storeIds = array(); 2112 | foreach (Mage::app()->getStores() as $store) { 2113 | if ($this->isSaveHistoryStore($store->getId())) { 2114 | $storeIds[] = $store->getId(); 2115 | } 2116 | } 2117 | 2118 | if(empty($storeIds)) { 2119 | return $this; 2120 | } 2121 | 2122 | $select = $this->_select(); 2123 | $select->from($this->getTable(self::REWRITE), array()) 2124 | ->where('original_request_path != request_path') 2125 | ->where('store_id IN(?)', $storeIds) 2126 | ->where('rewrite_id IS NOT NULL') 2127 | ->where('updated = ?', 1); 2128 | 2129 | $columns = array( 2130 | 'store_id' => 'store_id', 2131 | // Alternative to generateUniqueId() method 2132 | 'id_path' => new Zend_Db_Expr($this->_quoteInto( 2133 | 'CONCAT(CRC32(CONCAT(NOW(), id_path)), ?, SUBSTR(RAND(), 3))', 2134 | '_' 2135 | )), 2136 | 'category_id' => 'category_id', 2137 | 'product_id' => 'product_id', 2138 | 'request_path' => 'original_request_path', 2139 | 'target_path' => 'request_path' 2140 | ); 2141 | 2142 | $select->columns($columns); 2143 | $this->_getIndexAdapter()->query( 2144 | $select->insertFromSelect( 2145 | $this->getTable(self::REWRITE), 2146 | $select->getColumnAliases() 2147 | ) 2148 | ); 2149 | 2150 | return $this; 2151 | } 2152 | 2153 | /** 2154 | * Reindex all urls for categories and products 2155 | * (non-PHPdoc) 2156 | * @see Mage_Index_Model_Mysql4_Abstract::reindexAll() 2157 | */ 2158 | public function reindexAll() 2159 | { 2160 | $this 2161 | ->_generateTransliterateData() 2162 | ->_generateRootCategoryIndex() 2163 | ->clearInvalidRewrites() 2164 | ->_generateCategoryRequestPathIndex() 2165 | ->_generateProductRequestPathIndex() 2166 | ->_importFromRewrite() 2167 | ->_importFromCategoryRequestPath() 2168 | ->_importFromProductRequestPath() 2169 | ->_updateRewrites(); 2170 | } 2171 | 2172 | /** 2173 | * Reindex urls for specified categories and products 2174 | * 2175 | * @param array $categoryIds 2176 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 2177 | */ 2178 | public function updateCategoryRewrites(array $cateoryIds) 2179 | { 2180 | $this 2181 | ->_generateTransliterateData(true) 2182 | ->_generateRootCategoryIndex() 2183 | ->clearInvalidRewrites() 2184 | ->_generateCategoryRequestPathIndex($cateoryIds) 2185 | ->_generateProductRequestPathIndex($cateoryIds) 2186 | ->_importFromRewrite() 2187 | ->_importFromCategoryRequestPath() 2188 | ->_importFromProductRequestPath() 2189 | ->_updateRewrites(); 2190 | return $this; 2191 | } 2192 | 2193 | /** 2194 | * Reindex urls for specified product ids or for products 2195 | * that are in specified category ids 2196 | * 2197 | * @param array|null $productIds ignored if category ids is not null 2198 | * @param array|null $categoryIds 2199 | * @return EcomDev_UrlRewrite_Model_Mysql4_Indexer 2200 | */ 2201 | public function updateProductRewrites(array $productIds, array $categoryIds = null) 2202 | { 2203 | $this 2204 | ->_generateTransliterateData(true) 2205 | ->_generateProductRequestPathIndex($categoryIds, $productIds) 2206 | ->_importFromRewrite() 2207 | ->_importFromProductRequestPath() 2208 | ->_updateRewrites(); 2209 | return $this; 2210 | } 2211 | 2212 | /** 2213 | * Updates product rewrite on after save event operation 2214 | * 2215 | * @param Mage_Index_Model_Event $event 2216 | */ 2217 | public function catalogProductSave(Mage_Index_Model_Event $event) 2218 | { 2219 | $eventData = $event->getNewData(); 2220 | $productIds = isset($eventData['rewrite_product_ids']) ? $eventData['rewrite_product_ids'] : null; 2221 | $categoryIds = isset($eventData['rewrite_category_ids']) ? $eventData['rewrite_category_ids'] : null; 2222 | $this->updateProductRewrites($productIds, $categoryIds); 2223 | } 2224 | 2225 | /** 2226 | * Updates category rewrites on after save event operation 2227 | * 2228 | * @param Mage_Index_Model_Event $event 2229 | */ 2230 | public function catalogCategorySave(Mage_Index_Model_Event $event) 2231 | { 2232 | $eventData = $event->getNewData(); 2233 | $this->updateCategoryRewrites($eventData['rewrite_category_ids']); 2234 | } 2235 | 2236 | /** 2237 | * Overriden to not produce an error on enterprise 1.11.1.0, there is no need in disabling keys for indexer... 2238 | * 2239 | */ 2240 | public function disableTableKeys() 2241 | { 2242 | return $this; 2243 | } 2244 | 2245 | /** 2246 | * Overriden to not produce an error on enterprise 1.11.1.0, there is no need in disabling keys for indexer... 2247 | * 2248 | */ 2249 | public function enableTableKeys() 2250 | { 2251 | return $this; 2252 | } 2253 | } 2254 | -------------------------------------------------------------------------------- /app/code/community/EcomDev/UrlRewrite/Model/Mysql4/Select.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | 19 | /** 20 | * Custom select object, because Varien_Db_Select 21 | * doesn't support real cross update from select! 22 | * 23 | * Also has some addtional nice helper methods 24 | * for working with data 25 | * 26 | * DO NOT SUPPORT MDMS, DON'T USE IT WITH NOT MYSQL RESOURCES 27 | */ 28 | class EcomDev_UrlRewrite_Model_Mysql4_Select extends Varien_Db_Select 29 | { 30 | const SQL_UPDATE = 'UPDATE'; 31 | const SQL_SET = 'SET'; 32 | 33 | // Index part key 34 | const INDEX = 'INDEX'; 35 | 36 | // Type of index specification for select 37 | const INDEX_FORCE = 'FORCE'; 38 | const INDEX_IGNORE = 'IGNORE'; 39 | const INDEX_USE = 'USE'; 40 | 41 | // Index information specification 42 | const SQL_INDEX_FORCE = 'FORCE INDEX(%s)'; 43 | const SQL_INDEX_IGNORE = 'IGNORE INDEX(%s)'; 44 | const SQL_INDEX_USE = 'USE INDEX(%s)'; 45 | 46 | /** 47 | * Types that is available for index instructions in select 48 | * 49 | * @var array 50 | */ 51 | protected $_indexSqlTypes = array( 52 | self::INDEX_USE => self::SQL_INDEX_USE, 53 | self::INDEX_IGNORE => self::SQL_INDEX_IGNORE, 54 | self::INDEX_FORCE => self::SQL_INDEX_FORCE 55 | ); 56 | 57 | /** 58 | * Fix of adapter from possible call via singleton 59 | * 60 | * @param array|Zend_Db_Adapter_Abstract 61 | * 62 | */ 63 | public function __construct($adapter) 64 | { 65 | if (is_array($adapter)) { 66 | $adapter = current($adapter); 67 | } 68 | 69 | self::$_joinTypes[] = self::SQL_STRAIGHT_JOIN; 70 | 71 | parent::__construct($adapter); 72 | } 73 | 74 | /** 75 | * Returns used tables in select 76 | * 77 | * @return array 78 | */ 79 | public function getUsedTables() 80 | { 81 | $tables = array(); 82 | foreach ($this->_parts[self::FROM] as $correlationName => $table) { 83 | $tables[$correlationName] = $table['tableName']; 84 | } 85 | 86 | return $tables; 87 | } 88 | 89 | /** 90 | * Return list of used column aliases 91 | * 92 | * @return array 93 | */ 94 | public function getColumnAliases() 95 | { 96 | $aliases = array(); 97 | foreach ($this->_parts[self::COLUMNS] as $columnEntry) { 98 | list(, $column, $alias) = $columnEntry; 99 | if (empty($alias)) { 100 | $alias = $column; 101 | } 102 | $aliases[] = $alias; 103 | } 104 | 105 | return $aliases; 106 | } 107 | 108 | /** 109 | * Return list of used columns 110 | * 111 | * @return array 112 | */ 113 | public function getColumns() 114 | { 115 | $columns = array(); 116 | 117 | foreach ($this->_parts[self::COLUMNS] as $columnEntry) { 118 | list($correlationName, $column, $alias) = $columnEntry; 119 | if (empty($alias)) { 120 | $alias = $column; 121 | } 122 | 123 | $columns[(string)$alias] = ( 124 | $column instanceof Zend_Db_Expr ? 125 | $column : 126 | $this->_adapter->quoteIdentifier(array($correlationName, $column)) 127 | ); 128 | } 129 | 130 | return $columns; 131 | } 132 | 133 | /** 134 | * Executes cross select from update based on the current select object 135 | * 136 | * @return Zend_Db_Statement 137 | */ 138 | public function crossUpdateFromSelectImproved() 139 | { 140 | $parts[] = self::SQL_UPDATE . ' ' . ltrim($this->_renderFrom(''), ' ' . self::SQL_FROM); 141 | 142 | $tableAliases = $this->getUsedTables(); 143 | $defaultTableAlias = key($tableAliases); 144 | 145 | $columns = array(); 146 | foreach ($this->getColumns() as $alias => $column) { 147 | if (strpos($alias, '.') !== false) { 148 | $tableAlias = substr($alias, 0, strpos($alias, '.')); 149 | $alias = substr($alias, strpos($alias, '.') + 1); 150 | } else { 151 | $tableAlias = $defaultTableAlias; 152 | } 153 | 154 | $columns[] = $this->_adapter->quoteIdentifier(array($tableAlias, $alias)) 155 | . ' = ' . $column; 156 | } 157 | 158 | $parts[] = self::SQL_SET . ' ' . implode(", ", $columns); 159 | $parts[] = $this->_renderWhere(''); 160 | 161 | return $this->_adapter->query(implode("\n", $parts), $this->getBind()); 162 | } 163 | 164 | 165 | 166 | /** 167 | * Add index information statement 168 | * 169 | * @param string $correlationName 170 | * @param string $type 171 | * @param array|type $indexes 172 | * @return EcomDev_UrlRewrite_Model_Mysql4_Select 173 | */ 174 | protected function _index($correlationName, $type, $indexes) 175 | { 176 | if (!is_array($indexes)) { 177 | $indexes = array($indexes); 178 | } 179 | 180 | $this->_parts[self::INDEX][$correlationName] = array('type' => $type, 'indexes' => $indexes); 181 | return $this; 182 | } 183 | 184 | /** 185 | * Force using of a specific index for the table 186 | * 187 | * @param string $correlationName table alias 188 | * @param string|array $indexes index name(s) 189 | * @return EcomDev_UrlRewrite_Model_Mysql4_Select 190 | */ 191 | public function indexForce($correlationName, $indexes) 192 | { 193 | $this->_index($correlationName, self::INDEX_FORCE, $indexes); 194 | return $this; 195 | } 196 | 197 | /** 198 | * Tells MySQL optimazer which indexes it should use 199 | * 200 | * @param string $correlationName table alias 201 | * @param string|array $indexes index name(s) 202 | * @return EcomDev_UrlRewrite_Model_Mysql4_Select 203 | */ 204 | public function indexUse($correlationName, $indexes) 205 | { 206 | $this->_index($correlationName, self::INDEX_USE, $indexes); 207 | return $this; 208 | } 209 | 210 | /** 211 | * Tells MySQL optimazer wich indexes should be ignored 212 | * 213 | * @param string $correlationName table alias 214 | * @param string|array $indexes index name(s) 215 | * @return EcomDev_UrlRewrite_Model_Mysql4_Select 216 | */ 217 | public function indexIgnore($correlationName, $indexes) 218 | { 219 | $this->_index($correlationName, self::INDEX_IGNORE, $indexes); 220 | return $this; 221 | } 222 | 223 | /** 224 | * Render FROM clause with using of addotional specifications for index usage 225 | * 226 | * @param string $sql SQL query 227 | * @return string 228 | */ 229 | protected function _renderFrom($sql) 230 | { 231 | $sql = parent::_renderFrom($sql); 232 | // Add index definitions for MySQL optimizer 233 | $replace = array(); 234 | foreach ($this->_parts[self::FROM] as $correlationName => $table) { 235 | if (!isset($this->_parts[self::INDEX][$correlationName]) 236 | || !isset($this->_indexSqlTypes[$this->_parts[self::INDEX][$correlationName]['type']])) { 237 | continue; 238 | } 239 | 240 | 241 | $indexInstruction = $this->_parts[self::INDEX][$correlationName]; 242 | 243 | $replace['from'][] = $this->_getQuotedTable($table['tableName'], $correlationName); 244 | $replace['to'][] = $this->_getQuotedTable($table['tableName'], $correlationName) 245 | . ' ' 246 | . sprintf( 247 | $this->_indexSqlTypes[$indexInstruction['type']], 248 | implode(',', array_map( 249 | array($this->_adapter, 'quoteIdentifier'), 250 | $indexInstruction['indexes'] 251 | )) 252 | ); 253 | } 254 | 255 | if ($replace) { 256 | $sql = str_replace($replace['from'], $replace['to'], $sql); 257 | } 258 | 259 | return $sql; 260 | } 261 | 262 | 263 | // Backward compatibility issue with method in Magento core!! 264 | /** 265 | * Straight join BC method, 266 | * since Varien just removed it in 1.6 instead of marking as depracated 267 | * 268 | * @param array|string|Zend_Db_Expr $name The table name. 269 | * @param string $cond Join on this condition. 270 | * @param array|string $cols The columns to select from the joined table. 271 | * @param string $schema The database name to specify, if any. 272 | * @return Zend_Db_Select This Zend_Db_Select object. 273 | */ 274 | public function joinStraight($name, $cond, $cols = self::SQL_WILDCARD, $schema = null) 275 | { 276 | return $this->_join(self::SQL_STRAIGHT_JOIN, $name, $cond, $cols, $schema); 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /app/code/community/EcomDev/UrlRewrite/Test/Config/Main.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | 19 | /** 20 | * Configuration test 21 | * 22 | */ 23 | class EcomDev_UrlRewrite_Test_Config_Main extends EcomDev_PHPUnit_Test_Case_Config 24 | { 25 | /** 26 | * Test model definitions for module 27 | * 28 | * @test 29 | */ 30 | public function modelDefinitions() 31 | { 32 | $this->assertModelAlias( 33 | 'ecomdev_urlrewrite/indexer', 34 | 'EcomDev_UrlRewrite_Model_Indexer' 35 | ); 36 | 37 | $this->assertResourceModelAlias( 38 | 'ecomdev_urlrewrite/indexer', 39 | 'EcomDev_UrlRewrite_Model_Mysql4_Indexer' 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/code/community/EcomDev/UrlRewrite/Test/Model/Mysql4/Indexer.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | 19 | /** 20 | * Url Rewrite Indexer resource model integration test case 21 | * Very simple one, just checks output that was generated by SQL queries 22 | * 23 | * @loadSharedFixture data 24 | * @doNotIndexAll 25 | */ 26 | class EcomDev_UrlRewrite_Test_Model_Mysql4_Indexer extends EcomDev_PHPUnit_Test_Case 27 | { 28 | /** 29 | * Url rewrite indexer resource model instance 30 | * 31 | * @var EcomDev_UrlRewrite_Model_Mysql4_Indexer 32 | */ 33 | protected $resourceModel = null; 34 | 35 | /** 36 | * Initializes model under test and disables events 37 | * for checking logic in isolation from custom its customizations 38 | * 39 | * (non-PHPdoc) 40 | * @see EcomDev_PHPUnit_Test_Case::setUp() 41 | */ 42 | protected function setUp() 43 | { 44 | parent::setUp(); 45 | $this->resourceModel = Mage::getResourceModel('ecomdev_urlrewrite/indexer'); 46 | $this->app()->disableEvents(); 47 | } 48 | 49 | /** 50 | * Enables events back 51 | * (non-PHPdoc) 52 | * @see EcomDev_PHPUnit_Test_Case::tearDown() 53 | */ 54 | protected function tearDown() 55 | { 56 | parent::tearDown(); 57 | $this->app()->enableEvents(); 58 | } 59 | 60 | /** 61 | * Test for generation of root category index data 62 | * 63 | * @loadFixture clear 64 | * @covers EcomDev_UrlRewrite_Model_Mysql4_Indexer::_generateRootCategoryIndex 65 | * @covers EcomDev_UrlRewrite_Model_Mysql4_Indexer::getRootCatgories 66 | */ 67 | public function testCreationOfRootCategory() 68 | { 69 | // Generate index 70 | EcomDev_Utils_Reflection::invokeRestrictedMethod( 71 | $this->resourceModel, 72 | '_generateRootCategoryIndex' 73 | ); 74 | 75 | $result = $this->resourceModel->getRootCategories(); 76 | $this->assertEquals( 77 | $this->expected()->getResult(), 78 | $result 79 | ); 80 | 81 | return $this; 82 | } 83 | 84 | /** 85 | * Test for generation of category relation index data 86 | * 87 | * @param string $dataSet for expectation 88 | * @param int|array $categoryIds 89 | * @param string $type 90 | * @param array|null $reindexCategoryIds if null reindex all category relations 91 | * 92 | * @loadFixture clear 93 | * @dataProvider dataProvider 94 | * @covers EcomDev_UrlRewrite_Model_Mysql4_Indexer::_generateCategoryRelationIndex 95 | * @covers EcomDev_UrlRewrite_Model_Mysql4_Indexer::getCategoryRelations 96 | * @covers EcomDev_UrlRewrite_Model_Mysql4_Indexer::_getRelatedCategoryIdsSelect 97 | */ 98 | public function testCreationOfCategoryRelations($dataSet, $categoryIds, $type, array $reindexCategoryIds = null) 99 | { 100 | // Generate index 101 | EcomDev_Utils_Reflection::invokeRestrictedMethod( 102 | $this->resourceModel, 103 | '_generateCategoryRelationIndex', 104 | array($reindexCategoryIds) 105 | ); 106 | 107 | $result = $this->resourceModel->getCategoryRelations($categoryIds, $type); 108 | $this->assertEquals( 109 | $this->expected($dataSet)->getResult(), 110 | $result 111 | ); 112 | return $this; 113 | } 114 | 115 | /** 116 | * Test for generation of category request path index data 117 | * 118 | * @param string $dataSet for expectation 119 | * @param int|array $categoryIds 120 | * @param array|null $reindexCategoryIds if null reindex all data 121 | * 122 | * @loadFixture clear 123 | * @loadFixture rootCategoryIndex 124 | * @dataProvider dataProvider 125 | * @covers EcomDev_UrlRewrite_Model_Mysql4_Indexer::_generateCategoryRelationIndex 126 | * @covers EcomDev_UrlRewrite_Model_Mysql4_Indexer::getCategoryRequestPathIndex 127 | * @covers EcomDev_UrlRewrite_Model_Mysql4_Indexer::_getCategoryRequestPathSelect 128 | * @covers EcomDev_UrlRewrite_Model_Mysql4_Indexer::_cleanUrlPath 129 | */ 130 | public function testCreationOfCategoryRequestPathIndex($dataSet, $categoryIds, array $reindexCategoryIds = null) 131 | { 132 | // Generate transliteration 133 | EcomDev_Utils_Reflection::invokeRestrictedMethod( 134 | $this->resourceModel, 135 | '_generateTransliterateData' 136 | ); 137 | 138 | // Generate index 139 | EcomDev_Utils_Reflection::invokeRestrictedMethod( 140 | $this->resourceModel, 141 | '_generateCategoryRequestPathIndex', 142 | array($reindexCategoryIds) 143 | ); 144 | 145 | $result = $this->resourceModel->getCategoryRequestPathIndex($categoryIds); 146 | $this->assertEquals( 147 | $this->expected($dataSet)->getResult(), 148 | $result 149 | ); 150 | 151 | return $this; 152 | } 153 | 154 | /** 155 | * Test for generation of category request path index data 156 | * 157 | * If no product and categories ids specified for reindex process, it will rebuild all index 158 | * If product and category ids specified together, product ids will be ignored 159 | * 160 | * @param string $dataSet for expectation 161 | * @param int|array $categoryIds 162 | * @param array|null $reindexCategoryIds 163 | * @param array|null $reindexProductIds 164 | * 165 | * @loadFixture clear 166 | * @loadFixture rootCategoryIndex 167 | * @loadFixture categoryRelationIndex 168 | * @loadFixture categoryRequestPathIndex 169 | * @loadFixture categoryUrlKeyIndex 170 | * @dataProvider dataProvider 171 | * @covers EcomDev_UrlRewrite_Model_Mysql4_Indexer::_generateProductUrlPathIndex 172 | * @covers EcomDev_UrlRewrite_Model_Mysql4_Indexer::_getProductRequestPathSelect 173 | * @covers EcomDev_UrlRewrite_Model_Mysql4_Indexer::getProductRequestPathIndex 174 | * @covers EcomDev_UrlRewrite_Model_Mysql4_Indexer::_cleanUrlPath 175 | */ 176 | public function testCreationOfProductRequestPathIndex($dataSet, $productIds, array $reindexCategoryIds = null, array $reindexProductIds = null) 177 | { 178 | // Generate transliteration 179 | EcomDev_Utils_Reflection::invokeRestrictedMethod( 180 | $this->resourceModel, 181 | '_generateTransliterateData' 182 | ); 183 | 184 | // Generate index 185 | EcomDev_Utils_Reflection::invokeRestrictedMethod( 186 | $this->resourceModel, 187 | '_generateProductRequestPathIndex', 188 | array($reindexCategoryIds, $reindexProductIds) 189 | ); 190 | 191 | $result = $this->resourceModel->getProductRequestPathIndex($productIds); 192 | 193 | $this->assertEquals( 194 | $this->expected($dataSet)->getResult(), 195 | $result 196 | ); 197 | 198 | return $this; 199 | } 200 | 201 | } 202 | -------------------------------------------------------------------------------- /app/code/community/EcomDev/UrlRewrite/Test/Model/Mysql4/Indexer/expectations/testCreationOfCategoryRelations.yaml: -------------------------------------------------------------------------------- 1 | full_reindex_nested: # Should return all child relations for categories 2 | result: 3 | 22: [221, 222, 2211] 4 | 221: [2211] 5 | 30: [31, 32] 6 | full_reindex_nested_single_id: # Should return all child relations for category specified in dataprovider 7 | result: 8 | 22: [221, 222, 2211] 9 | full_reindex_anchor: # Should return only relations for categories that are anchors 10 | result: 11 | 221: [2211] 12 | 30: [31, 32] 13 | full_reindex_anchor_single_id: # Should return only relations for categories that are anchors 14 | result: 15 | 221: [2211] 16 | partial_reindex_nested: # Shoudl return only generated relations for books 17 | result: 18 | 22: [221, 222, 2211] 19 | 221: [2211] 20 | partial_reindex_anchor: # Shoudl return only generated relations for books and that are anchors 21 | result: 22 | 221: [2211] 23 | -------------------------------------------------------------------------------- /app/code/community/EcomDev/UrlRewrite/Test/Model/Mysql4/Indexer/expectations/testCreationOfCategoryRequestPathIndex.yaml: -------------------------------------------------------------------------------- 1 | full_reindex: # Should return all request paths for all categories 2 | result: 3 | 21: 4 | 1: comedy # Default store 5 | 21: comedy # English 6 | 22: komodie # German 7 | 23: komedija # Ukrainian 8 | 22: 9 | 1: sci-fi 10 | 21: sci-fi 11 | 22: sci-fi 12 | 23: fantastyka 13 | 30: 14 | 31: advanced 15 | 32: advanced 16 | 33: advanced 17 | 31: 18 | 31: advanced/url-rewrites 19 | 32: advanced/url-rewrites 20 | 33: advanced/url-rewrites 21 | 32: 22 | 31: advanced/url-rewrites 23 | 32: advanced/url-rewrites 24 | 33: advanced/url-rewrites 25 | 221: 26 | 1: sci-fi/aliens 27 | 21: sci-fi/aliens 28 | 22: sci-fi/aliens 29 | 23: fantastyka/pribul-ci # Magento has priority on translation of Russion 30 | # characters before translating Ukrainian characters, 31 | # so Ukrainian "и" is translated as "i" instead of "y" 32 | 2211: 33 | 1: sci-fi/aliens/humanoids 34 | 21: sci-fi/aliens/humanoids 35 | 22: sci-fi/aliens/humanoide 36 | 23: fantastyka/pribul-ci/gumanoidi 37 | 222: 38 | 1: sci-fi/time-travels 39 | 21: sci-fi/time-travels 40 | 22: sci-fi/zeitreisen 41 | 23: fantastyka/podorozhi-u-chasi 42 | full_reindex_single_id: # Should return only url path for specified category 43 | result: 44 | 221: 45 | 1: sci-fi/aliens # Default 46 | 21: sci-fi/aliens # English 47 | 22: sci-fi/aliens # German 48 | 23: fantastyka/pribul-ci # Ukrainian 49 | partial_reindex: # Should return only request paths for books 50 | result: 51 | 21: 52 | 1: comedy # Default 53 | 21: comedy # English 54 | 22: komodie # German 55 | 23: komedija # Ukrainian 56 | 22: 57 | 1: sci-fi 58 | 21: sci-fi 59 | 22: sci-fi 60 | 23: fantastyka 61 | 221: 62 | 1: sci-fi/aliens 63 | 21: sci-fi/aliens 64 | 22: sci-fi/aliens 65 | 23: fantastyka/pribul-ci 66 | 2211: 67 | 1: sci-fi/aliens/humanoids 68 | 21: sci-fi/aliens/humanoids 69 | 22: sci-fi/aliens/humanoide 70 | 23: fantastyka/pribul-ci/gumanoidi 71 | 222: 72 | 1: sci-fi/time-travels 73 | 21: sci-fi/time-travels 74 | 22: sci-fi/zeitreisen 75 | 23: fantastyka/podorozhi-u-chasi 76 | -------------------------------------------------------------------------------- /app/code/community/EcomDev/UrlRewrite/Test/Model/Mysql4/Indexer/expectations/testCreationOfProductRequestPathIndex.yaml: -------------------------------------------------------------------------------- 1 | full_reindex: 2 | result: 3 | 21001: 4 | # Store level 5 | 21: 6 | # Category Level 7 | 0: viktor-yanukovych-autobiography 8 | 21: comedy/viktor-yanukovych-autobiography 9 | 22: 10 | 0: viktor-yanukovych-autobiography 11 | 21: komodie/viktor-yanukovych-autobiography 12 | 23: 13 | 0: viktor-yanukovych-autobiography 14 | 21: komedija/viktor-yanukovych-autobiography 15 | 22101: 16 | 21: 17 | 0: herbert-george-wells-the-war-of-the-worlds 18 | 221: sci-fi/aliens/herbert-george-wells-the-war-of-the-worlds 19 | 22: 20 | 0: herbert-george-wells-the-war-of-the-worlds 21 | 221: sci-fi/aliens/herbert-george-wells-the-war-of-the-worlds 22 | 23: 23 | 0: gerbert-dzhordzh-uells-vijna-svitiv 24 | 221: fantastyka/pribul-ci/gerbert-dzhordzh-uells-vijna-svitiv 25 | 22111: 26 | 21: 27 | 0: jack-williamson-the-humanoids-a-novel 28 | 221: sci-fi/aliens/jack-williamson-the-humanoids-a-novel # Anchor should have a rewrite as well 29 | 2211: sci-fi/aliens/humanoids/jack-williamson-the-humanoids-a-novel 30 | 22: 31 | 0: jack-williamson-the-humanoids-a-novel 32 | 221: sci-fi/aliens/jack-williamson-the-humanoids-a-novel 33 | 2211: sci-fi/aliens/humanoide/jack-williamson-the-humanoids-a-novel 34 | 23: 35 | 0: jack-williamson-the-humanoids-a-novel 36 | 221: fantastyka/pribul-ci/jack-williamson-the-humanoids-a-novel 37 | 2211: fantastyka/pribul-ci/gumanoidi/jack-williamson-the-humanoids-a-novel 38 | 22201: 39 | 21: 40 | 0: wells-h-g-1898-the-time-machine 41 | 222: sci-fi/time-travels/wells-h-g-1898-the-time-machine 42 | 22: 43 | 0: wells-h-g-1898-the-time-machine 44 | 222: sci-fi/zeitreisen/wells-h-g-1898-the-time-machine 45 | 23: 46 | 0: gerbert-dzhordzh-uells-mashina-chasu 47 | 222: fantastyka/podorozhi-u-chasi/gerbert-dzhordzh-uells-mashina-chasu 48 | # Duplicated keys for these products shouldn't be resolved at step of path generation 49 | 31001: 50 | 31: 51 | 0: extension 52 | 30: advanced/extension 53 | 31: advanced/url-rewrites/extension 54 | 32: advanced/url-rewrites/extension 55 | 32: 56 | 0: extension 57 | 30: advanced/extension 58 | 31: advanced/url-rewrites/extension 59 | 32: advanced/url-rewrites/extension 60 | 33: 61 | 0: extension 62 | 30: advanced/extension 63 | 31: advanced/url-rewrites/extension 64 | 32: advanced/url-rewrites/extension 65 | 31002: 66 | 31: 67 | 0: extension 68 | 30: advanced/extension 69 | 31: advanced/url-rewrites/extension 70 | 32: advanced/url-rewrites/extension 71 | 32: 72 | 0: extension 73 | 30: advanced/extension 74 | 31: advanced/url-rewrites/extension 75 | 32: advanced/url-rewrites/extension 76 | 33: 77 | 0: extension 78 | 30: advanced/extension 79 | 31: advanced/url-rewrites/extension 80 | 32: advanced/url-rewrites/extension 81 | single_id: 82 | result: 83 | 21001: 84 | # Store level 85 | 21: 86 | # Category Level 87 | 0: viktor-yanukovych-autobiography 88 | 21: comedy/viktor-yanukovych-autobiography 89 | 22: 90 | 0: viktor-yanukovych-autobiography 91 | 21: komodie/viktor-yanukovych-autobiography 92 | 23: 93 | 0: viktor-yanukovych-autobiography 94 | 21: komedija/viktor-yanukovych-autobiography 95 | partial_reindex_by_category_ids: 96 | result: 97 | 21001: 98 | # Store level 99 | 21: 100 | # Category Level 101 | 0: viktor-yanukovych-autobiography 102 | 21: comedy/viktor-yanukovych-autobiography 103 | 22: 104 | 0: viktor-yanukovych-autobiography 105 | 21: komodie/viktor-yanukovych-autobiography 106 | 23: 107 | 0: viktor-yanukovych-autobiography 108 | 21: komedija/viktor-yanukovych-autobiography 109 | 22101: 110 | 21: 111 | 0: herbert-george-wells-the-war-of-the-worlds 112 | 221: sci-fi/aliens/herbert-george-wells-the-war-of-the-worlds 113 | 22: 114 | 0: herbert-george-wells-the-war-of-the-worlds 115 | 221: sci-fi/aliens/herbert-george-wells-the-war-of-the-worlds 116 | 23: 117 | 0: gerbert-dzhordzh-uells-vijna-svitiv 118 | 221: fantastyka/pribul-ci/gerbert-dzhordzh-uells-vijna-svitiv 119 | 22111: 120 | 21: 121 | 0: jack-williamson-the-humanoids-a-novel 122 | 221: sci-fi/aliens/jack-williamson-the-humanoids-a-novel # Anchor should have a rewrite as well 123 | 2211: sci-fi/aliens/humanoids/jack-williamson-the-humanoids-a-novel 124 | 22: 125 | 0: jack-williamson-the-humanoids-a-novel 126 | 221: sci-fi/aliens/jack-williamson-the-humanoids-a-novel 127 | 2211: sci-fi/aliens/humanoide/jack-williamson-the-humanoids-a-novel 128 | 23: 129 | 0: jack-williamson-the-humanoids-a-novel 130 | 221: fantastyka/pribul-ci/jack-williamson-the-humanoids-a-novel 131 | 2211: fantastyka/pribul-ci/gumanoidi/jack-williamson-the-humanoids-a-novel 132 | 22201: 133 | 21: 134 | 0: wells-h-g-1898-the-time-machine 135 | 222: sci-fi/time-travels/wells-h-g-1898-the-time-machine 136 | 22: 137 | 0: wells-h-g-1898-the-time-machine 138 | 222: sci-fi/zeitreisen/wells-h-g-1898-the-time-machine 139 | 23: 140 | 0: gerbert-dzhordzh-uells-mashina-chasu 141 | 222: fantastyka/podorozhi-u-chasi/gerbert-dzhordzh-uells-mashina-chasu 142 | partial_reindex_by_product_ids: # It should return only reindexed products 143 | result: 144 | 21001: 145 | # Store level 146 | 21: 147 | # Category Level 148 | 0: viktor-yanukovych-autobiography 149 | 21: comedy/viktor-yanukovych-autobiography 150 | 22: 151 | 0: viktor-yanukovych-autobiography 152 | 21: komodie/viktor-yanukovych-autobiography 153 | 23: 154 | 0: viktor-yanukovych-autobiography 155 | 21: komedija/viktor-yanukovych-autobiography 156 | 22101: 157 | 21: 158 | 0: herbert-george-wells-the-war-of-the-worlds 159 | 221: sci-fi/aliens/herbert-george-wells-the-war-of-the-worlds 160 | 22: 161 | 0: herbert-george-wells-the-war-of-the-worlds 162 | 221: sci-fi/aliens/herbert-george-wells-the-war-of-the-worlds 163 | 23: 164 | 0: gerbert-dzhordzh-uells-vijna-svitiv 165 | 221: fantastyka/pribul-ci/gerbert-dzhordzh-uells-vijna-svitiv 166 | -------------------------------------------------------------------------------- /app/code/community/EcomDev/UrlRewrite/Test/Model/Mysql4/Indexer/expectations/testCreationOfRootCategory.yaml: -------------------------------------------------------------------------------- 1 | result: 2 | 1: 1/2/% 3 | 21: 1/2/% 4 | 22: 1/2/% 5 | 23: 1/2/% 6 | 31: 1/3/% 7 | 32: 1/3/% 8 | 33: 1/3/% 9 | -------------------------------------------------------------------------------- /app/code/community/EcomDev/UrlRewrite/Test/Model/Mysql4/Indexer/fixtures/categoryRelationIndex.yaml: -------------------------------------------------------------------------------- 1 | tables: # Pregenerated category relation index for data fixture 2 | ecomdev_urlrewrite/category_relation: 3 | - category_id: 22 4 | related_id: 221 5 | type: "nested" 6 | - category_id: 22 7 | related_id: 222 8 | type: "nested" 9 | - category_id: 22 10 | related_id: 2211 11 | type: "nested" 12 | - category_id: 30 13 | related_id: 31 14 | type: "anchor" 15 | - category_id: 30 16 | related_id: 31 17 | type: "nested" 18 | - category_id: 30 19 | related_id: 32 20 | type: "anchor" 21 | - category_id: 30 22 | related_id: 32 23 | type: "nested" 24 | - category_id: 221 25 | related_id: 2211 26 | type: "anchor" 27 | - category_id: 221 28 | related_id: 2211 29 | type: "nested" 30 | -------------------------------------------------------------------------------- /app/code/community/EcomDev/UrlRewrite/Test/Model/Mysql4/Indexer/fixtures/categoryRequestPathIndex.yaml: -------------------------------------------------------------------------------- 1 | tables: # Pregenerated category url path index, for testing product request path generation 2 | ecomdev_urlrewrite/category_request_path: 3 | - category_id: 21 4 | store_id: 1 5 | id_path: category/21 6 | request_path: "comedy" 7 | updated: 1 8 | - category_id: 21 9 | store_id: 21 10 | id_path: category/21 11 | request_path: "comedy" 12 | updated: 1 13 | - category_id: 21 14 | store_id: 22 15 | id_path: category/21 16 | request_path: "komodie" 17 | updated: 1 18 | - category_id: 21 19 | store_id: 23 20 | id_path: category/21 21 | request_path: "komedija" 22 | updated: 1 23 | - category_id: 22 24 | store_id: 1 25 | id_path: category/22 26 | request_path: "sci-fi" 27 | updated: 1 28 | - category_id: 22 29 | store_id: 21 30 | id_path: category/22 31 | request_path: "sci-fi" 32 | updated: 1 33 | - category_id: 22 34 | store_id: 22 35 | id_path: category/22 36 | request_path: "sci-fi" 37 | updated: 1 38 | - category_id: 22 39 | store_id: 23 40 | id_path: category/22 41 | request_path: "fantastyka" 42 | updated: 1 43 | - category_id: 30 44 | store_id: 31 45 | id_path: category/30 46 | request_path: "advanced" 47 | updated: 1 48 | - category_id: 30 49 | store_id: 32 50 | id_path: category/30 51 | request_path: "advanced" 52 | updated: 1 53 | - category_id: 30 54 | store_id: 33 55 | id_path: category/30 56 | request_path: "advanced" 57 | updated: 1 58 | - category_id: 31 59 | store_id: 31 60 | id_path: category/31 61 | request_path: "advanced/url-rewrites" 62 | updated: 1 63 | - category_id: 31 64 | store_id: 32 65 | id_path: category/31 66 | request_path: "advanced/url-rewrites" 67 | updated: 1 68 | - category_id: 31 69 | store_id: 33 70 | id_path: category/31 71 | request_path: "advanced/url-rewrites" 72 | updated: 1 73 | - category_id: 32 74 | store_id: 31 75 | id_path: category/32 76 | request_path: "advanced/url-rewrites" 77 | updated: 1 78 | - category_id: 32 79 | store_id: 32 80 | id_path: category/32 81 | request_path: "advanced/url-rewrites" 82 | updated: 1 83 | - category_id: 32 84 | store_id: 33 85 | id_path: category/32 86 | request_path: "advanced/url-rewrites" 87 | updated: 1 88 | - category_id: 221 89 | store_id: 1 90 | id_path: category/221 91 | request_path: "sci-fi/aliens" 92 | updated: 1 93 | - category_id: 221 94 | store_id: 21 95 | id_path: category/221 96 | request_path: "sci-fi/aliens" 97 | updated: 1 98 | - category_id: 221 99 | store_id: 22 100 | id_path: category/221 101 | request_path: "sci-fi/aliens" 102 | updated: 1 103 | - category_id: 221 104 | store_id: 23 105 | id_path: category/221 106 | request_path: "fantastyka/pribul-ci" 107 | updated: 1 108 | - category_id: 222 109 | store_id: 1 110 | id_path: category/222 111 | request_path: "sci-fi/time-travels" 112 | updated: 1 113 | - category_id: 222 114 | store_id: 21 115 | id_path: category/222 116 | request_path: "sci-fi/time-travels" 117 | updated: 1 118 | - category_id: 222 119 | store_id: 22 120 | id_path: category/222 121 | request_path: "sci-fi/zeitreisen" 122 | updated: 1 123 | - category_id: 222 124 | store_id: 23 125 | request_path: "fantastyka/podorozhi-u-chasi" 126 | updated: 1 127 | - category_id: 2211 128 | store_id: 1 129 | id_path: category/2211 130 | request_path: "sci-fi/aliens/humanoids" 131 | updated: 1 132 | - category_id: 2211 133 | store_id: 21 134 | id_path: category/2211 135 | request_path: "sci-fi/aliens/humanoids" 136 | updated: 1 137 | - category_id: 2211 138 | store_id: 22 139 | id_path: category/2211 140 | request_path: "sci-fi/aliens/humanoide" 141 | updated: 1 142 | - category_id: 2211 143 | store_id: 23 144 | id_path: category/2211 145 | request_path: "fantastyka/pribul-ci/gumanoidi" 146 | updated: 1 147 | -------------------------------------------------------------------------------- /app/code/community/EcomDev/UrlRewrite/Test/Model/Mysql4/Indexer/fixtures/categoryUrlKeyIndex.yaml: -------------------------------------------------------------------------------- 1 | tables: # Pregenerated category url key index, for testing product request path generation 2 | ecomdev_urlrewrite/category_url_key: 3 | - category_id: 21 4 | store_id: 1 5 | level: 2 6 | updated: 0 7 | url_key_source: Comedy 8 | url_key: comedy 9 | - category_id: 21 10 | store_id: 21 11 | level: 2 12 | updated: 0 13 | url_key_source: Comedy 14 | url_key: comedy 15 | - category_id: 21 16 | store_id: 22 17 | level: 2 18 | updated: 0 19 | url_key_source: Komödie 20 | url_key: komodie 21 | - category_id: 21 22 | store_id: 23 23 | level: 2 24 | updated: 0 25 | url_key_source: Комедія 26 | url_key: komedija 27 | - category_id: 22 28 | store_id: 1 29 | level: 2 30 | updated: 0 31 | url_key_source: sci-fi 32 | url_key: sci-fi 33 | - category_id: 22 34 | store_id: 21 35 | level: 2 36 | updated: 0 37 | url_key_source: sci-fi 38 | url_key: sci-fi 39 | - category_id: 22 40 | store_id: 22 41 | level: 2 42 | updated: 0 43 | url_key_source: sci-fi 44 | url_key: sci-fi 45 | - category_id: 22 46 | store_id: 23 47 | level: 2 48 | updated: 0 49 | url_key_source: fantastyka 50 | url_key: fantastyka 51 | - category_id: 30 52 | store_id: 31 53 | level: 2 54 | updated: 0 55 | url_key_source: Advanced 56 | url_key: advanced 57 | - category_id: 30 58 | store_id: 32 59 | level: 2 60 | updated: 0 61 | url_key_source: Advanced 62 | url_key: advanced 63 | - category_id: 30 64 | store_id: 33 65 | level: 2 66 | updated: 0 67 | url_key_source: Advanced 68 | url_key: advanced 69 | - category_id: 31 70 | store_id: 31 71 | level: 3 72 | updated: 0 73 | url_key_source: Url Rewrites 74 | url_key: url-rewrites 75 | - category_id: 31 76 | store_id: 32 77 | level: 3 78 | updated: 0 79 | url_key_source: Url Rewrites 80 | url_key: url-rewrites 81 | - category_id: 31 82 | store_id: 33 83 | level: 3 84 | updated: 0 85 | url_key_source: Url Rewrites 86 | url_key: url-rewrites 87 | - category_id: 32 88 | store_id: 31 89 | level: 3 90 | updated: 0 91 | url_key_source: Url Rewrites 92 | url_key: url-rewrites 93 | - category_id: 32 94 | store_id: 32 95 | level: 3 96 | updated: 0 97 | url_key_source: Url Rewrites 98 | url_key: url-rewrites 99 | - category_id: 32 100 | store_id: 33 101 | level: 3 102 | updated: 0 103 | url_key_source: Url Rewrites 104 | url_key: url-rewrites 105 | - category_id: 221 106 | store_id: 1 107 | level: 3 108 | updated: 0 109 | url_key_source: Aliens 110 | url_key: aliens 111 | - category_id: 221 112 | store_id: 21 113 | level: 3 114 | updated: 0 115 | url_key_source: Aliens 116 | url_key: aliens 117 | - category_id: 221 118 | store_id: 22 119 | level: 3 120 | updated: 0 121 | url_key_source: Aliens 122 | url_key: aliens 123 | - category_id: 221 124 | store_id: 23 125 | level: 3 126 | updated: 0 127 | url_key_source: Прибульці 128 | url_key: pribul-ci 129 | - category_id: 222 130 | store_id: 1 131 | level: 3 132 | updated: 0 133 | url_key_source: Time Travels 134 | url_key: time-travels 135 | - category_id: 222 136 | store_id: 21 137 | level: 3 138 | updated: 0 139 | url_key_source: Time Travels 140 | url_key: time-travels 141 | - category_id: 222 142 | store_id: 22 143 | level: 3 144 | updated: 0 145 | url_key_source: Zeitreisen 146 | url_key: zeitreisen 147 | - category_id: 222 148 | store_id: 23 149 | level: 3 150 | updated: 0 151 | url_key_source: Подорожі у часі 152 | url_key: podorozhi-u-chasi 153 | - category_id: 2211 154 | store_id: 1 155 | level: 4 156 | updated: 0 157 | url_key_source: Humanoids 158 | url_key: humanoids 159 | - category_id: 2211 160 | store_id: 21 161 | level: 4 162 | updated: 0 163 | url_key_source: Humanoids 164 | url_key: humanoids 165 | - category_id: 2211 166 | store_id: 22 167 | level: 4 168 | updated: 0 169 | url_key_source: Humanoide 170 | url_key: humanoide 171 | - category_id: 2211 172 | store_id: 23 173 | level: 4 174 | updated: 0 175 | url_key_source: Гуманоїди 176 | url_key: gumanoidi 177 | -------------------------------------------------------------------------------- /app/code/community/EcomDev/UrlRewrite/Test/Model/Mysql4/Indexer/fixtures/clear.yaml: -------------------------------------------------------------------------------- 1 | tables: 2 | core/url_rewrite: [] 3 | ecomdev_urlrewrite/root_category: [] 4 | ecomdev_urlrewrite/product_url_key: [] 5 | ecomdev_urlrewrite/product_request_path: [] 6 | ecomdev_urlrewrite/category_url_key: [] 7 | ecomdev_urlrewrite/category_request_path: [] 8 | ecomdev_urlrewrite/category_relation: [] 9 | ecomdev_urlrewrite/rewrite: [] 10 | ecomdev_urlrewrite/duplicate: [] 11 | -------------------------------------------------------------------------------- /app/code/community/EcomDev/UrlRewrite/Test/Model/Mysql4/Indexer/fixtures/data.yaml: -------------------------------------------------------------------------------- 1 | # Fixture that contains all required data for url rewrites test 2 | # on different categories, product with different languages 3 | scope: 4 | website: # Initialize websites 5 | - website_id: 2 6 | code: books 7 | name: Books Website 8 | default_group_id: 2 9 | - website_id: 3 10 | code: extensions 11 | name: Extensions Website 12 | default_group_id: 3 13 | group: # Initializes store groups 14 | - group_id: 2 15 | website_id: 2 16 | name: Books Store 17 | default_store_id: 1 18 | root_category_id: 2 # Books Store 19 | - group_id: 3 20 | website_id: 3 21 | name: Extensions Store 22 | default_store_id: 31 23 | root_category_id: 3 # Extensions Store 24 | store: # Initializes store views 25 | - store_id: 21 # English language for Books Website 26 | website_id: 2 27 | group_id: 2 28 | code: books_eng 29 | name: English 30 | is_active: 1 31 | - store_id: 22 # German language for Books Website 32 | website_id: 2 33 | group_id: 2 34 | code: books_deu 35 | name: German 36 | is_active: 1 37 | - store_id: 23 # Ukrainian language for Books Website 38 | website_id: 2 39 | group_id: 2 40 | code: books_ukr 41 | name: Ukrainian 42 | is_active: 1 43 | - store_id: 31 # English language for Extensions Website 44 | website_id: 3 45 | group_id: 3 46 | code: extensions_eng 47 | name: English 48 | is_active: 1 49 | - store_id: 32 # German language for Extensions Website 50 | website_id: 3 51 | group_id: 3 52 | code: extensions_deu 53 | name: German 54 | is_active: 1 55 | - store_id: 33 # Ukrainian language for Extensions Website 56 | website_id: 3 57 | group_id: 3 58 | code: extensions_ukr 59 | name: Ukrainian 60 | is_active: 1 61 | eav: 62 | catalog_category: 63 | - entity_id: 1 # Root category 64 | attribute_set_id: 0 65 | path: 1 66 | name: Root 67 | level: 0 68 | children_count: 3 69 | is_active: 1 70 | - entity_id: 2 # Books Store category 71 | parent_id: 1 72 | path: 1/2 73 | name: Books Store 74 | level: 1 75 | children_count: 2 76 | is_active: 1 77 | # Books Store categories 78 | - entity_id: 21 79 | parent_id: 2 80 | path: 1/2/21 81 | name: Comedy 82 | level: 2 83 | children_count: 0 84 | is_active: 1 85 | /stores: 86 | books_deu: 87 | name: Komödie 88 | books_ukr: 89 | name: Комедія 90 | - entity_id: 22 91 | parent_id: 2 92 | path: 1/2/22 93 | name: Science-Fiction 94 | url_key: sci-fi 95 | level: 2 96 | children_count: 2 97 | is_active: 1 98 | /stores: 99 | books_ukr: 100 | name: Фантастика 101 | url_key: fantastyka 102 | - entity_id: 221 103 | parent_id: 22 104 | path: 1/2/22/221 105 | name: Aliens 106 | level: 3 107 | is_anchor: 1 108 | children_count: 1 109 | is_active: 1 110 | /stores: 111 | books_ukr: 112 | name: Прибульці 113 | - entity_id: 2211 114 | parent_id: 221 115 | path: 1/2/22/221/2211 116 | name: Humanoids 117 | level: 4 118 | children_count: 0 119 | /stores: 120 | books_deu: 121 | name: Humanoide 122 | books_ukr: 123 | name: Гуманоїди 124 | - entity_id: 222 125 | parent_id: 22 126 | path: 1/2/22/222 127 | name: Time Travels 128 | level: 3 129 | children_count: 0 130 | is_active: 1 131 | /stores: 132 | books_deu: 133 | name: Zeitreisen 134 | books_ukr: 135 | name: Подорожі у часі 136 | - entity_id: 3 # Extension Store category 137 | parent_id: 1 138 | path: 1/3 139 | name: Extension Store 140 | level: 1 141 | children_count: 1 142 | is_active: 1 143 | # Extension Store categories 144 | - entity_id: 30 145 | parent_id: 3 146 | path: 1/3/30 147 | name: Advanced 148 | level: 2 149 | children_count: 2 150 | is_active: 1 151 | is_anchor: 1 152 | - entity_id: 31 153 | parent_id: 30 154 | path: 1/3/30/31 155 | name: Url Rewrites 156 | level: 3 157 | children_count: 0 158 | is_active: 1 159 | - entity_id: 32 160 | parent_id: 30 161 | path: 1/3/30/31 162 | name: Url Rewrites Duplicate Key 163 | url_key: url-rewrites 164 | level: 3 165 | children_count: 0 166 | is_active: 1 167 | catalog_product: 168 | - entity_id: 21001 # First part of id is related to base category 169 | type_id: simple 170 | sku: yanukovych-0001 171 | name: "Viktor Yanukovych: Autobiography" 172 | short_description: "Viktor Yanukovych: Autobiography" 173 | description: "Viktor Yanukovych: Autobiography" 174 | stock: 175 | qty: 100000000.00 176 | is_in_stock: 1 177 | website_ids: 178 | - books 179 | category_ids: 180 | - 21 # Comedy 181 | price: 0.99 182 | tax_class_id: 2 # Taxable Goods 183 | status: 1 # Enabled 184 | visibility: 4 # Visible in Catalog & Search 185 | - entity_id: 22101 186 | type_id: simple 187 | sku: wells-0001 188 | name: "Herbert George Wells: The War of the Worlds" 189 | short_description: "Herbert George Wells: The War of the Worlds" 190 | description: "Herbert George Wells: The War of the Worlds" 191 | stock: 192 | qty: 100000000.00 193 | is_in_stock: 1 194 | website_ids: 195 | - books 196 | category_ids: 197 | - 221 # Aliens 198 | price: 0.99 199 | tax_class_id: 2 # Taxable Goods 200 | status: 1 # Enabled 201 | visibility: 4 # Visible in Catalog & Search 202 | /stores: 203 | books_ukr: 204 | name: "Герберт Джордж Уеллс: Війна світів" 205 | short_description: "Герберт Джордж Уеллс: Війна світів" 206 | description: "Герберт Джордж Уеллс: Війна світів" 207 | - entity_id: 22111 208 | type_id: simple 209 | sku: williamson-0001 210 | name: "Jack Williamson: The Humanoids: A Novel" 211 | short_description: "Jack Williamson: The Humanoids: A Novel" 212 | description: "Jack Williamson: The Humanoids: A Novel" 213 | stock: 214 | qty: 100000000.00 215 | is_in_stock: 1 216 | website_ids: 217 | - books 218 | category_ids: 219 | - 2211 # Aliens -> Humanoids 220 | price: 0.99 221 | tax_class_id: 2 # Taxable Goods 222 | status: 1 # Enabled 223 | visibility: 4 # Visible in Catalog & Search 224 | - entity_id: 22201 225 | type_id: simple 226 | sku: wells-0002 227 | name: "Wells, H.G. 1898. The Time Machine" 228 | short_description: "Wells, H.G. 1898. The Time Machine" 229 | description: "Wells, H.G. 1898. The Time Machine" 230 | stock: 231 | qty: 100000000.00 232 | is_in_stock: 1 233 | website_ids: 234 | - books 235 | category_ids: 236 | - 222 # Time travels 237 | price: 0.99 238 | tax_class_id: 2 # Taxable Goods 239 | status: 1 # Enabled 240 | visibility: 4 # Visible in Catalog & Search 241 | /stores: 242 | books_ukr: 243 | name: "Герберт Джордж Уеллс: Машина часу" 244 | short_description: "Герберт Джордж Уеллс: Машина часу" 245 | description: "Герберт Джордж Уеллс: Машина часу" 246 | - entity_id: 31001 247 | type_id: simple 248 | sku: extension-0001 249 | name: Extension 250 | short_description: Extension 251 | description: Extension 252 | stock: 253 | qty: 100000000.00 254 | is_in_stock: 1 255 | website_ids: 256 | - extensions 257 | category_ids: 258 | - 31 # Url Rewrites 259 | - 32 # Url Rewrites 2 260 | price: 0.99 261 | tax_class_id: 2 # Taxable Goods 262 | status: 1 # Enabled 263 | visibility: 4 # Visible in Catalog & Search 264 | - entity_id: 31002 # Duplicate keys test case 265 | type_id: simple 266 | sku: extension-0002 267 | name: Extension 268 | short_description: Extension 269 | description: Extension 270 | stock: 271 | qty: 100000000.00 272 | is_in_stock: 1 273 | website_ids: 274 | - extensions 275 | category_ids: 276 | - 31 # Url Rewrites 277 | - 32 # Url Rewrites 2 278 | price: 0.99 279 | tax_class_id: 2 # Taxable Goods 280 | status: 1 # Enabled 281 | visibility: 4 # Visible in Catalog & Search 282 | - entity_id: 31003 # Special test case with numbers in the end 283 | type_id: simple 284 | sku: extension-0003 285 | name: Extension 286 | short_description: Extension 287 | description: Extension 288 | stock: 289 | qty: 100000000.00 290 | is_in_stock: 1 291 | website_ids: 292 | - extensions 293 | category_ids: 294 | - 31 # Url Rewrites 295 | - 32 # Url Rewrites 2 296 | price: 0.99 297 | tax_class_id: 2 # Taxable Goods 298 | status: 1 # Enabled 299 | visibility: 4 # Visible in Catalog & Search 300 | -------------------------------------------------------------------------------- /app/code/community/EcomDev/UrlRewrite/Test/Model/Mysql4/Indexer/fixtures/rootCategoryIndex.yaml: -------------------------------------------------------------------------------- 1 | tables: 2 | ecomdev_urlrewrite/root_category: 3 | - store_id: 1 4 | path: 1/2/% 5 | - store_id: 21 6 | path: 1/2/% 7 | - store_id: 22 8 | path: 1/2/% 9 | - store_id: 23 10 | path: 1/2/% 11 | - store_id: 31 12 | path: 1/3/% 13 | - store_id: 32 14 | path: 1/3/% 15 | - store_id: 33 16 | path: 1/3/% 17 | -------------------------------------------------------------------------------- /app/code/community/EcomDev/UrlRewrite/Test/Model/Mysql4/Indexer/providers/testCreationOfCategoryRelations.yaml: -------------------------------------------------------------------------------- 1 | - 2 | - full_reindex_nested 3 | - [21, 22, 221, 2211, 222, 30, 31, 32] 4 | - nested 5 | - null 6 | - 7 | - full_reindex_nested_single_id 8 | - 22 9 | - nested 10 | - null 11 | - 12 | - full_reindex_anchor 13 | - [21, 22, 221, 2211, 222, 30, 31, 32] 14 | - anchor 15 | - null 16 | - 17 | - full_reindex_anchor_single_id 18 | - 221 19 | - anchor 20 | - null 21 | - 22 | - partial_reindex_nested 23 | - [21, 22, 221, 2211, 222, 30, 31, 32] 24 | - nested 25 | - [2] # reindex only books 26 | - 27 | - partial_reindex_anchor 28 | - [21, 22, 221, 2211, 222, 30, 31, 32] 29 | - anchor 30 | - [2, 22] # reindex only books 31 | -------------------------------------------------------------------------------- /app/code/community/EcomDev/UrlRewrite/Test/Model/Mysql4/Indexer/providers/testCreationOfCategoryRequestPathIndex.yaml: -------------------------------------------------------------------------------- 1 | - 2 | - full_reindex 3 | - [21, 22, 221, 2211, 222, 30, 31, 32] 4 | - null 5 | - 6 | - full_reindex_single_id 7 | - 221 8 | - null 9 | - 10 | - partial_reindex 11 | - [21, 22, 221, 2211, 222, 30, 31, 32] 12 | - [21, 22] # reindex only books 13 | -------------------------------------------------------------------------------- /app/code/community/EcomDev/UrlRewrite/Test/Model/Mysql4/Indexer/providers/testCreationOfProductRequestPathIndex.yaml: -------------------------------------------------------------------------------- 1 | - 2 | - full_reindex 3 | - [21001, 22101, 22111, 22201, 31001, 31002] 4 | - null 5 | - null 6 | - 7 | - single_id 8 | - 21001 9 | - null 10 | - 11 | - partial_reindex_by_category_ids 12 | - [21001, 22101, 22111, 22201, 31001, 31002] 13 | - [21, 22] # reindex only books 14 | - null 15 | - 16 | - partial_reindex_by_product_ids 17 | - [21001, 22101, 22111, 22201, 31001, 31002] 18 | - null 19 | - [21001, 22101] # Reindex only 2 products 20 | -------------------------------------------------------------------------------- /app/code/community/EcomDev/UrlRewrite/etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | 0.2.1 24 | 25 | 26 | 27 | 28 | 29 | EcomDev_UrlRewrite_Model 30 | ecomdev_urlrewrite_mysql4 31 | 32 | 33 | EcomDev_UrlRewrite_Model_Mysql4 34 | 35 | 36 | ecomdev_urlrewrite_root_category
37 |
38 | 39 | ecomdev_urlrewrite_category_url_key
40 |
41 | 42 | ecomdev_urlrewrite_category_request_path
43 |
44 | 45 | ecomdev_urlrewrite_product_relation
46 |
47 | 48 | ecomdev_urlrewrite_product_url_key
49 |
50 | 51 | ecomdev_urlrewrite_product_request_path
52 |
53 | 54 | ecomdev_urlrewrite_rewrite
55 |
56 | 57 | ecomdev_urlrewrite_duplicate
58 |
59 | 60 | ecomdev_urlrewrite_duplicate_updated
61 |
62 | 63 | ecomdev_urlrewrite_duplicate_key
64 |
65 | 66 | ecomdev_urlrewrite_duplicate_increment
67 |
68 | 69 | ecomdev_urlrewrite_duplicate_aggregate
70 |
71 | 72 | ecomdev_urlrewrite_category_relation
73 |
74 | 75 | ecomdev_urlrewrite_transliterate
76 |
77 |
78 |
79 |
80 | 81 | 82 | EcomDev_UrlRewrite_Helper 83 | 84 | 85 | 86 | 87 | 88 | EcomDev_UrlRewrite 89 | 90 | 91 | 92 | 93 | 94 | 95 | ecomdev_urlrewrite/indexer 96 | 97 | 98 | 99 |
100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 |
-------------------------------------------------------------------------------- /app/code/community/EcomDev/UrlRewrite/sql/ecomdev_urlrewrite_setup/mysql4-install-0.2.0.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | 19 | /* @var $this Mage_Core_Model_Resource_Setup */ 20 | $this->startSetup(); 21 | 22 | // This table provides data that can be used for LIKE path expressions 23 | // with categories 24 | $table = $this->getConnection()->newTable( 25 | $this->getTable('ecomdev_urlrewrite/root_category') 26 | ); 27 | 28 | $table 29 | ->addColumn( 30 | 'store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, 31 | array('unsigned' => true, 'nullable' => false, 'primary' => true) 32 | ) 33 | ->addColumn( 34 | 'path', Varien_Db_Ddl_Table::TYPE_CHAR, 16, 35 | array('unsigned' => true, 'nullable' => false) 36 | ) 37 | ->addIndex('IDX_CATEGORY_PATH', array('path')) 38 | ->setOption('collate', null); 39 | 40 | $this->getConnection()->createTable($table); 41 | 42 | // These two tables will not have any foreign keys 43 | // They will not be cleared automatically if product/category 44 | // or store will be deleted 45 | // They created to minimize time on update of the url key via clean_url_key 46 | // stored function 47 | $table = $this->getConnection()->newTable( 48 | $this->getTable('ecomdev_urlrewrite/category_url_key') 49 | ); 50 | 51 | $table 52 | ->addColumn( 53 | 'store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, 54 | array('unsigned' => true, 'nullable' => false, 'primary' => true) 55 | ) 56 | ->addColumn( 57 | 'category_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, 58 | array('unsigned' => true, 'nullable' => false, 'primary' => true) 59 | ) 60 | ->addColumn( 61 | 'level', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, 62 | array('unsigned' => true, 'nullable' => false) 63 | ) 64 | ->addColumn( 65 | 'updated', Varien_Db_Ddl_Table::TYPE_TINYINT, 1, 66 | array('unsigned' => true, 'nullable' => false, 'default' => 1) 67 | ) 68 | ->addColumn( 69 | 'url_key_source', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255, 70 | array('nullable' => false) 71 | ) 72 | ->addColumn( 73 | 'url_key', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255, 74 | array('nullable' => false) 75 | ) 76 | ->addIndex( 77 | 'IDX_UPDATED', array('updated') 78 | ) 79 | ->addIndex( 80 | 'IDX_LEVEL', array('level') 81 | ) 82 | ->addIndex( 83 | 'IDX_CATEGORY_STORE', array('category_id', 'store_id') 84 | ) 85 | ->setOption('collate', null); 86 | 87 | $this->getConnection()->createTable($table); 88 | 89 | $table = $this->getConnection()->newTable( 90 | $this->getTable('ecomdev_urlrewrite/product_url_key') 91 | ); 92 | 93 | $table 94 | ->addColumn( 95 | 'store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, 96 | array('unsigned' => true, 'nullable' => false, 'primary' => true) 97 | ) 98 | ->addColumn( 99 | 'product_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, 100 | array('unsigned' => true, 'nullable' => false, 'primary' => true) 101 | ) 102 | ->addColumn( 103 | 'updated', Varien_Db_Ddl_Table::TYPE_TINYINT, 1, 104 | array('unsigned' => true, 'nullable' => false, 'default' => 1) 105 | ) 106 | ->addColumn( 107 | 'url_key_source', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255, 108 | array('nullable' => false) 109 | ) 110 | ->addColumn( 111 | 'url_key', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255, 112 | array('nullable' => false) 113 | ) 114 | ->addIndex( 115 | 'IDX_UPDATED', array('updated') 116 | ) 117 | ->addIndex( 118 | 'IDX_PRODUCT_STORE', array('product_id', 'store_id') 119 | ) 120 | ->setOption('collate', null); 121 | 122 | $this->getConnection()->createTable($table); 123 | 124 | $table = $this->getConnection()->newTable( 125 | $this->getTable('ecomdev_urlrewrite/category_request_path') 126 | ); 127 | 128 | // Category request path index table 129 | $table 130 | ->addColumn( 131 | 'store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, 132 | array('unsigned' => true, 'nullable' => false, 'primary' => true) 133 | ) 134 | ->addColumn( 135 | 'id_path', Varien_Db_Ddl_Table::TYPE_CHAR, 32, 136 | array('nullable' => false, 'primary' => true) 137 | ) 138 | ->addColumn( 139 | 'category_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, 140 | array('unsigned' => true, 'nullable' => false) 141 | ) 142 | ->addColumn( 143 | 'level', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, 144 | array('unsigned' => true, 'nullable' => false) 145 | ) 146 | ->addColumn( 147 | 'request_path', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255, 148 | array('nullable' => false) 149 | ) 150 | ->addColumn( 151 | 'updated', Varien_Db_Ddl_Table::TYPE_TINYINT, 1, 152 | array('unsigned' => true, 'nullable' => false, 'default' => 1) 153 | ) 154 | ->addIndex( 155 | 'IDX_STORE_CATEGORY', array('store_id', 'category_id') 156 | ) 157 | ->addIndex( 158 | 'IDX_LEVEL', array('level') 159 | ) 160 | ->addIndex( 161 | 'IDX_UPDATED', array('updated') 162 | ) 163 | ->setOption('collate', null); 164 | 165 | $this->getConnection()->createTable($table); 166 | 167 | $table = $this->getConnection()->newTable( 168 | $this->getTable('ecomdev_urlrewrite/product_request_path') 169 | ); 170 | 171 | // Product request path index table 172 | $table 173 | ->addColumn( 174 | 'store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, 175 | array('unsigned' => true, 'nullable' => false, 'primary' => true) 176 | ) 177 | ->addColumn( 178 | 'id_path', Varien_Db_Ddl_Table::TYPE_CHAR, 32, 179 | array('nullable' => false, 'primary' => true) 180 | ) 181 | ->addColumn( 182 | 'product_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, 183 | array('unsigned' => true, 'nullable' => false) 184 | ) 185 | ->addColumn( 186 | 'category_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, 187 | array('unsigned' => true, 'nullable' => true) 188 | ) 189 | ->addColumn( 190 | 'request_path', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255, 191 | array('nullable' => false) 192 | ) 193 | ->addColumn( 194 | 'updated', Varien_Db_Ddl_Table::TYPE_TINYINT, 1, 195 | array('unsigned' => true, 'nullable' => false, 'default' => 1) 196 | ) 197 | ->addIndex( 198 | 'IDX_CATEGORY', 199 | array('category_id') 200 | ) 201 | ->addIndex( 202 | 'IDX_PRODUCT', 203 | array('product_id') 204 | ) 205 | ->addIndex( 206 | 'IDX_UPDATED', 207 | array('updated') 208 | ) 209 | ->setOption('collate', null); 210 | 211 | $this->getConnection()->createTable($table); 212 | 213 | // Rewrite table 214 | $table = $this->getConnection()->newTable( 215 | $this->getTable('ecomdev_urlrewrite/rewrite') 216 | ); 217 | 218 | $table 219 | ->addColumn( 220 | 'store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, 221 | array('unsigned' => true, 'nullable' => false, 'primary' => true) 222 | ) 223 | ->addColumn( 224 | 'id_path', Varien_Db_Ddl_Table::TYPE_CHAR, 32, 225 | array('nullable' => false, 'primary' => true) 226 | ) 227 | ->addColumn( 228 | 'rewrite_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, 229 | array('unsigned' => true, 'nullable' => true) 230 | ) 231 | ->addColumn( 232 | 'product_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, 233 | array('unsigned' => true, 'nullable' => true) 234 | ) 235 | ->addColumn( 236 | 'category_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, 237 | array('unsigned' => true, 'nullable' => true) 238 | ) 239 | ->addColumn( 240 | 'target_path', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255, 241 | array('nullable' => false) 242 | ) 243 | ->addColumn( 244 | 'request_path', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255, 245 | array('nullable' => false) 246 | ) 247 | ->addColumn( 248 | 'duplicate_key', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255, 249 | array('nullable' => false) 250 | ) 251 | ->addColumn( 252 | 'duplicate_index', Varien_Db_Ddl_Table::TYPE_INTEGER, null, 253 | array('nullable' => true, 'unsigned' => true) 254 | ) 255 | ->addColumn( 256 | 'original_request_path', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255, 257 | array('nullable' => true) 258 | ) 259 | ->addColumn( 260 | 'updated', Varien_Db_Ddl_Table::TYPE_TINYINT, 1, 261 | array('unsigned' => true, 'nullable' => false, 'default' => 1) 262 | ) 263 | ->addIndex( 264 | 'IDX_REWRITE', 265 | array('rewrite_id') 266 | ) 267 | ->addIndex( 268 | 'IDX_CATEGORY', 269 | array('category_id') 270 | ) 271 | ->addIndex( 272 | 'IDX_PRODUCT', 273 | array('product_id') 274 | ) 275 | ->addIndex( 276 | 'IDX_REQ_PATH', 277 | array('request_path') 278 | ) 279 | ->addIndex( 280 | 'IDX_ORIG_REQ_PATH_PAIR', 281 | array('request_path', 'original_request_path') 282 | ) 283 | ->addIndex( 284 | 'IDX_DUPLICATE_STORE', 285 | array('duplicate_key', 'store_id', 'duplicate_index') 286 | ) 287 | ->addIndex( 288 | 'IDX_DUPLICATE_KEY_STORE', 289 | array('duplicate_key', 'store_id') 290 | ) 291 | ->addIndex( 292 | 'IDX_UPDATED_DUPLICATE', 293 | array('updated', 'store_id', 'duplicate_key') 294 | ) 295 | ->addIndex( 296 | 'IDX_UPDATED', 297 | array('updated') 298 | ) 299 | ->addForeignKey( 300 | 'FK_ECOMDEV_URLREWRITE_REW_REWRITE_ID', 301 | 'rewrite_id', 302 | $this->getTable('core/url_rewrite'), 303 | 'url_rewrite_id', 304 | Varien_Db_Ddl_Table::ACTION_CASCADE 305 | ) 306 | ->setOption('collate', null); 307 | 308 | $this->getConnection()->createTable($table); 309 | 310 | // Information about wich duplicate keys were updated 311 | $table = $this->getConnection()->newTable( 312 | $this->getTable('ecomdev_urlrewrite/duplicate_updated') 313 | ); 314 | 315 | $table 316 | ->addColumn( 317 | 'duplicate_key', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255, 318 | array('nullable' => false, 'primary' => true) 319 | ) 320 | ->addColumn( 321 | 'store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, 322 | array('unsigned' => true, 'nullable' => false, 'primary' => true) 323 | ) 324 | ->setOption('collate', null); 325 | 326 | $this->getConnection()->createTable($table); 327 | 328 | // Contains infromation about really duplicated keys 329 | $table = $this->getConnection()->newTable( 330 | $this->getTable('ecomdev_urlrewrite/duplicate_key') 331 | ); 332 | 333 | $table 334 | ->addColumn( 335 | 'duplicate_key', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255, 336 | array('nullable' => false, 'primary' => true) 337 | ) 338 | ->addColumn( 339 | 'store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, 340 | array('unsigned' => true, 'nullable' => false, 'primary' => true) 341 | ) 342 | ->setOption('collate', null); 343 | 344 | $this->getConnection()->createTable($table); 345 | 346 | // Duplicates main table 347 | $table = $this->getConnection()->newTable($this->getTable('ecomdev_urlrewrite/duplicate')) 348 | ->addColumn( 349 | 'store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, 350 | array('unsigned' => true, 'nullable' => false, 'primary' => true) 351 | ) 352 | ->addColumn( 353 | 'id_path', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255, 354 | array('nullable' => false, 'primary' => true) 355 | ) 356 | ->addColumn( 357 | 'duplicate_key', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255, 358 | array('nullable' => false) 359 | ) 360 | ->addColumn( 361 | 'duplicate_index', Varien_Db_Ddl_Table::TYPE_INTEGER, null, 362 | array('nullable' => true, 'unsigned' => true) 363 | ) 364 | ->addIndex( 365 | 'IDX_STORE_DUPLICATE_KEY', 366 | array('duplicate_key', 'store_id') 367 | ) 368 | ->setOption('collate', null); 369 | 370 | $this->getConnection()->createTable($table); 371 | 372 | 373 | $table = $this->getConnection()->newTable( 374 | $this->getTable('ecomdev_urlrewrite/duplicate_increment') 375 | ); 376 | 377 | $table 378 | ->addColumn( 379 | 'duplicate_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, 380 | array('unsigned' => true, 'nullable' => false, 'primary' => true, 'identity' => true) 381 | ) 382 | ->addColumn( 383 | 'store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, 384 | array('unsigned' => true, 'nullable' => false) 385 | ) 386 | ->addColumn( 387 | 'id_path', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255, 388 | array('unsigned' => true, 'nullable' => false) 389 | ) 390 | ->addColumn( 391 | 'duplicate_key', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255, 392 | array('nullable' => false) 393 | ) 394 | ->addColumn( 395 | 'duplicate_index', Varien_Db_Ddl_Table::TYPE_INTEGER, null, 396 | array('nullable' => true, 'unsigned' => true) 397 | ) 398 | ->addIndex('IDX_STORE_ID_PATH', array('store_id', 'id_path')) 399 | ->addIndex('IDX_STORE_DUPLICATE_KEY', array('duplicate_key', 'store_id')) 400 | ->setOption('collate', null); 401 | 402 | $this->getConnection()->createTable($table); 403 | 404 | // If lower then 1.6, then there is a bug with auto_increment field 405 | // So we need to modify our column 406 | if (!method_exists($this->getConnection(), 'insertFromSelect')) { 407 | $this->getConnection()->modifyColumn( 408 | $this->getTable('ecomdev_urlrewrite/duplicate_increment'), 409 | 'duplicate_id', 'INT(10) UNSIGNED NOT NULL AUTO_INCREMENT' 410 | ); 411 | } 412 | 413 | $table = $this->getConnection()->newTable( 414 | $this->getTable('ecomdev_urlrewrite/duplicate_aggregate') 415 | ); 416 | 417 | $table 418 | ->addColumn( 419 | 'duplicate_key', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255, 420 | array('nullable' => false, 'primary' => true) 421 | ) 422 | ->addColumn( 423 | 'store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, 424 | array('unsigned' => true, 'nullable' => false, 'primary' => true) 425 | ) 426 | ->addColumn( 427 | 'max_index', Varien_Db_Ddl_Table::TYPE_INTEGER, null, 428 | array('nullable' => true, 'unsigned' => true) 429 | ) 430 | ->addColumn( 431 | 'min_duplicate_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, 432 | array('nullable' => false, 'unsigned' => true) 433 | ) 434 | ->setOption('collate', null); 435 | 436 | $this->getConnection()->createTable($table); 437 | 438 | 439 | $table = $this->getConnection()->newTable( 440 | $this->getTable('ecomdev_urlrewrite/category_relation') 441 | ); 442 | 443 | // Category relation index table 444 | $table 445 | ->addColumn( 446 | 'category_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, 447 | array('unsigned' => true, 'nullable' => false, 'primary' => true) 448 | ) 449 | ->addColumn( 450 | 'related_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, 451 | array('unsigned' => true, 'nullable' => false, 'primary' => true) 452 | ) 453 | ->addColumn( 454 | 'type', Varien_Db_Ddl_Table::TYPE_VARCHAR, 32, 455 | array('nullable' => false, 'primary' => true) 456 | ) 457 | ->addIndex('IDX_RELATION_BY_TYPE', array('category_id', 'type', 'related_id')); 458 | 459 | $this->getConnection()->createTable($table); 460 | 461 | $table = $this->getConnection()->newTable( 462 | $this->getTable('ecomdev_urlrewrite/product_relation') 463 | ); 464 | 465 | // Category product relation index table, 466 | // filled in before generation of request path 467 | $table 468 | ->addColumn( 469 | 'store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, 470 | array('unsigned' => true, 'nullable' => false, 'primary' => true) 471 | ) 472 | ->addColumn( 473 | 'category_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, 474 | array('unsigned' => true, 'nullable' => false, 'primary' => true) 475 | ) 476 | ->addColumn( 477 | 'product_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, 478 | array('unsigned' => true, 'nullable' => false, 'primary' => true) 479 | ); 480 | 481 | 482 | $this->getConnection()->createTable($table); 483 | 484 | // Transliterate characters table 485 | $table = $this->getConnection()->newTable($this->getTable('ecomdev_urlrewrite/transliterate')) 486 | ->addColumn( 487 | 'character_from', Varien_Db_Ddl_Table::TYPE_CHAR, 1, 488 | array('nullable' => false, 'primary' => true) 489 | ) 490 | ->addColumn( 491 | 'character_to', Varien_Db_Ddl_Table::TYPE_VARCHAR, 8, 492 | array('nullable' => false) 493 | ) 494 | ->setOption('collate', 'utf8_bin') 495 | ->setOption('type', 'MEMORY'); 496 | 497 | $this->getConnection()->createTable($table); 498 | 499 | // Url path formatter function Works only with mysql starting of 5.0 500 | $this->getConnection()->query('DROP FUNCTION IF EXISTS ECOMDEV_CLEAN_URL_KEY'); 501 | 502 | $this->getConnection()->query(" 503 | CREATE FUNCTION ECOMDEV_CLEAN_URL_KEY( 504 | _url_key VARCHAR(255) CHARSET utf8 505 | ) RETURNS varchar(255) CHARSET utf8 READS SQL DATA 506 | BEGIN 507 | DECLARE _char_position SMALLINT(5); 508 | DECLARE _char CHAR(8) CHARSET utf8; 509 | DECLARE _url_key_length SMALLINT(5); 510 | DECLARE _clean_url_key VARCHAR(255) CHARACTER SET utf8; 511 | DECLARE _translate_to_char VARCHAR(8) CHARSET utf8; 512 | DECLARE _normal_characters VARCHAR(72) 513 | CHARSET utf8 DEFAULT '!@#$%^&*()<>?:;\\'\"\\\\|[]_+=-01234567890abcdefghijklmnopqrstuvwxyz'; 514 | 515 | SET _url_key = LCASE(_url_key); 516 | SET _clean_url_key = ''; 517 | SET _url_key_length = LENGTH(_url_key); 518 | SET _char_position = 1; 519 | 520 | WHILE _char_position <= _url_key_length DO 521 | SET _char = SUBSTRING(_url_key, _char_position, 1); 522 | 523 | IF NOT LOCATE(_char, _normal_characters) THEN 524 | SELECT character_to INTO _translate_to_char 525 | FROM {$this->getTable('ecomdev_urlrewrite/transliterate')} 526 | WHERE character_from = _char LIMIT 1; 527 | SET _char = IFNULL(_translate_to_char, ''); 528 | END IF; 529 | 530 | IF _char REGEXP '[0-9a-z]' THEN 531 | SET _clean_url_key = CONCAT(_clean_url_key, _char); 532 | ELSE 533 | SET _clean_url_key = CONCAT(_clean_url_key, '-'); 534 | END IF; 535 | 536 | IF _char_position > 1 AND SUBSTR(_clean_url_key, LENGTH(_clean_url_key)-1, 2) = '--' THEN 537 | SET _clean_url_key = SUBSTR(_clean_url_key, 1, LENGTH(_clean_url_key)-1); 538 | END IF; 539 | 540 | SET _char_position = _char_position + 1; 541 | END WHILE; 542 | 543 | RETURN TRIM(BOTH '-' FROM CONCAT('', _clean_url_key)); 544 | END 545 | "); 546 | 547 | $this->getConnection()->update( 548 | $this->getTable('index/process'), 549 | array( 550 | 'status' => 'require_reindex' 551 | ), 552 | array( 553 | 'indexer_code = ?' => 'catalog_url' 554 | ) 555 | ); 556 | 557 | $this->endSetup(); 558 | -------------------------------------------------------------------------------- /app/code/community/EcomDev/UrlRewrite/sql/ecomdev_urlrewrite_setup/mysql4-upgrade-0.2.0-0.2.1.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | 19 | /* @var $this Mage_Core_Model_Resource_Setup */ 20 | $this->startSetup(); 21 | 22 | // Fix auto-increment problem with 1.6 23 | if (method_exists($this->getConnection(), 'insertFromSelect')) { 24 | $this->getConnection()->modifyColumn( 25 | $this->getTable('ecomdev_urlrewrite/duplicate_increment'), 26 | 'duplicate_id', 'INT(10) UNSIGNED NOT NULL AUTO_INCREMENT' 27 | ); 28 | 29 | $this->getConnection()->update( 30 | $this->getTable('index/process'), 31 | array( 32 | 'status' => 'require_reindex' 33 | ), 34 | array( 35 | 'indexer_code = ?' => 'catalog_url' 36 | ) 37 | ); 38 | } 39 | 40 | $this->endSetup(); 41 | -------------------------------------------------------------------------------- /app/etc/modules/EcomDev_UrlRewrite.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | community 24 | true 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /modman: -------------------------------------------------------------------------------- 1 | # Alternative Url Rewrites extension 2 | app/code/community/EcomDev/UrlRewrite app/code/community/EcomDev/UrlRewrite 3 | app/etc/modules/EcomDev_UrlRewrite.xml app/etc/modules/EcomDev_UrlRewrite.xml 4 | --------------------------------------------------------------------------------