├── 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 | 
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 |
--------------------------------------------------------------------------------