00001 <?php
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034 class Mage_CatalogSearch_Model_Mysql4_Fulltext extends Mage_Core_Model_Mysql4_Abstract
00035 {
00036
00037
00038
00039
00040
00041 protected $_searchableAttributes = null;
00042
00043
00044
00045
00046
00047
00048 protected $_separator = ' ';
00049
00050
00051
00052
00053
00054
00055 protected $_productTypes = array();
00056
00057
00058
00059
00060 protected function _construct()
00061 {
00062 $this->_init('catalogsearch/fulltext', 'product_id');
00063 }
00064
00065
00066
00067
00068
00069
00070
00071
00072 public function rebuildIndex($storeId = null, $productIds = null)
00073 {
00074 if (is_null($storeId)) {
00075 foreach (Mage::app()->getStores(false) as $store) {
00076 $this->_rebuildStoreIndex($store->getId(), $productIds);
00077 }
00078 } else {
00079 $this->_rebuildStoreIndex($storeId, $productIds);
00080 }
00081 return $this;
00082 }
00083
00084
00085
00086
00087
00088
00089
00090
00091 protected function _rebuildStoreIndex($storeId, $productIds = null)
00092 {
00093 $this->cleanIndex($storeId, $productIds);
00094
00095
00096 $staticFields = array();
00097 foreach ($this->_getSearchableAttributes('static') as $attribute) {
00098 $staticFields[] = $attribute->getAttributeCode();
00099 }
00100 $dynamicFields = array(
00101 'int' => array_keys($this->_getSearchableAttributes('int')),
00102 'varchar' => array_keys($this->_getSearchableAttributes('varchar')),
00103 'text' => array_keys($this->_getSearchableAttributes('text')),
00104 );
00105
00106
00107 $visibility = $this->_getSearchableAttribute('visibility');
00108 $status = $this->_getSearchableAttribute('status');
00109 $visibilityVals = Mage::getSingleton('catalog/product_visibility')->getVisibleInSearchIds();
00110 $statusVals = Mage::getSingleton('catalog/product_status')->getVisibleStatusIds();
00111
00112 $lastProductId = 0;
00113 while (true) {
00114 $products = $this->_getSearchableProducts($storeId, $staticFields, $productIds, $lastProductId);
00115 if (!$products) {
00116 break;
00117 }
00118
00119 $productAttributes = array();
00120 $productRelations = array();
00121 foreach ($products as $productData) {
00122 $lastProductId = $productData['entity_id'];
00123 $productAttributes[$productData['entity_id']] = $productData['entity_id'];
00124 $productChilds = $this->_getProductChildIds($productData['entity_id'], $productData['type_id']);
00125 $productRelations[$productData['entity_id']] = $productChilds;
00126 if ($productChilds) {
00127 foreach ($productChilds as $productChildId) {
00128 $productAttributes[$productChildId] = $productChildId;
00129 }
00130 }
00131 }
00132
00133 $productIndexes = array();
00134 $productAttributes = $this->_getProductAttributes($storeId, $productAttributes, $dynamicFields);
00135 foreach ($products as $productData) {
00136 if (!isset($productAttributes[$productData['entity_id']])) {
00137 continue;
00138 }
00139 $protductAttr = $productAttributes[$productData['entity_id']];
00140 if (!isset($protductAttr[$visibility->getId()]) || !in_array($protductAttr[$visibility->getId()], $visibilityVals)) {
00141 continue;
00142 }
00143 if (!isset($protductAttr[$status->getId()]) || !in_array($protductAttr[$status->getId()], $statusVals)) {
00144 continue;
00145 }
00146
00147 $productIndex = array(
00148 $productData['entity_id'] => $protductAttr
00149 );
00150 if ($productChilds = $productRelations[$productData['entity_id']]) {
00151 foreach ($productChilds as $productChildId) {
00152 if (isset($productAttributes[$productChildId])) {
00153 $productIndex[$productChildId] = $productAttributes[$productChildId];
00154 }
00155 }
00156 }
00157
00158 $index = $this->_prepareProductIndex($productIndex, $productData, $storeId);
00159 $productIndexes[$productData['entity_id']] = $index;
00160
00161 }
00162 $this->_saveProductIndexes($storeId, $productIndexes);
00163 }
00164
00165 $this->resetSearchResults();
00166
00167 return $this;
00168 }
00169
00170
00171
00172
00173
00174
00175
00176
00177
00178
00179
00180 protected function _getSearchableProducts($storeId, array $staticFields, $productIds = null, $lastProductId = 0, $limit = 100)
00181 {
00182 $entityType = $this->getEavConfig()->getEntityType('catalog_product');
00183 $store = Mage::app()->getStore($storeId);
00184
00185 $select = $this->_getReadAdapter()->select()
00186 ->from(
00187 array('e' => $this->getTable('catalog/product')),
00188 array_merge(array('entity_id', 'type_id'), $staticFields))
00189 ->joinInner(
00190 array('website' => $this->getTable('catalog/product_website')),
00191 $this->_getReadAdapter()->quoteInto('website.product_id=e.entity_id AND website.website_id=?', $store->getWebsiteId()),
00192 array()
00193 );
00194
00195 if (!is_null($productIds)) {
00196 $select->where('e.entity_id IN(?)', $productIds);
00197 }
00198
00199 $select->where('e.entity_id>?', $lastProductId)
00200 ->limit($limit)
00201 ->order('e.entity_id');
00202
00203 return $this->_getReadAdapter()->fetchAll($select);
00204 }
00205
00206
00207
00208
00209
00210
00211 public function resetSearchResults()
00212 {
00213 $this->beginTransaction();
00214 try {
00215 $this->_getWriteAdapter()->update($this->getTable('catalogsearch/search_query'), array('is_processed' => 0));
00216 $this->_getWriteAdapter()->query("TRUNCATE TABLE {$this->getTable('catalogsearch/result')}");
00217
00218 $this->commit();
00219 }
00220 catch (Exception $e) {
00221 $this->rollBack();
00222 throw $e;
00223 }
00224
00225 Mage::dispatchEvent('catalogsearch_reset_search_result');
00226
00227 return $this;
00228 }
00229
00230
00231
00232
00233
00234
00235
00236
00237 public function cleanIndex($storeId = null, $productId = null)
00238 {
00239 $where = array();
00240
00241 if (!is_null($storeId)) {
00242 $where[] = $this->_getWriteAdapter()->quoteInto('store_id=?', $storeId);
00243 }
00244 if (!is_null($productId)) {
00245 $where[] = $this->_getWriteAdapter()->quoteInto('product_id IN(?)', $productId);
00246 }
00247
00248 $this->_getWriteAdapter()->delete($this->getMainTable(), join(' AND ', $where));
00249 return $this;
00250 }
00251
00252
00253
00254
00255
00256
00257
00258
00259
00260 public function prepareResult($object, $queryText, $query)
00261 {
00262 if (!$query->getIsProcessed()) {
00263 $searchType = $object->getSearchType($query->getStoreId());
00264
00265 $stringHelper = Mage::helper('core/string');
00266
00267
00268 $bind = array(
00269 ':query' => $queryText
00270 );
00271 $like = array();
00272
00273 $fulltextCond = '';
00274 $likeCond = '';
00275 $separateCond = '';
00276
00277 if ($searchType == Mage_CatalogSearch_Model_Fulltext::SEARCH_TYPE_LIKE
00278 || $searchType == Mage_CatalogSearch_Model_Fulltext::SEARCH_TYPE_COMBINE) {
00279 $words = $stringHelper->splitWords($queryText, true, $query->getMaxQueryWords());
00280 $likeI = 0;
00281 foreach ($words as $word) {
00282 $like[] = '`s`.`data_index` LIKE :likew' . $likeI;
00283 $bind[':likew' . $likeI] = '%' . $word . '%';
00284 $likeI ++;
00285 }
00286 if ($like) {
00287 $likeCond = '(' . join(' AND ', $like) . ')';
00288 }
00289 }
00290 if ($searchType == Mage_CatalogSearch_Model_Fulltext::SEARCH_TYPE_FULLTEXT
00291 || $searchType == Mage_CatalogSearch_Model_Fulltext::SEARCH_TYPE_COMBINE) {
00292 $fulltextCond = 'MATCH (`s`.`data_index`) AGAINST (:query IN BOOLEAN MODE)';
00293 }
00294 if ($searchType == Mage_CatalogSearch_Model_Fulltext::SEARCH_TYPE_COMBINE && $likeCond) {
00295 $separateCond = ' OR ';
00296 }
00297
00298 $sql = sprintf("REPLACE INTO `{$this->getTable('catalogsearch/result')}` "
00299 . "(SELECT '%d', `s`.`product_id`, MATCH (`s`.`data_index`) AGAINST (:query IN BOOLEAN MODE) "
00300 . "FROM `{$this->getMainTable()}` AS `s` INNER JOIN `{$this->getTable('catalog/product')}` AS `e`"
00301 . "ON `e`.`entity_id`=`s`.`product_id` WHERE (%s%s%s) AND `s`.`store_id`='%d')",
00302 $query->getId(),
00303 $fulltextCond,
00304 $separateCond,
00305 $likeCond,
00306 $query->getStoreId()
00307 );
00308
00309 $this->_getWriteAdapter()->query($sql, $bind);
00310
00311 $query->setIsProcessed(1);
00312 }
00313
00314 return $this;
00315 }
00316
00317
00318
00319
00320
00321
00322 public function getEavConfig()
00323 {
00324 return Mage::getSingleton('eav/config');
00325 }
00326
00327
00328
00329
00330
00331
00332 protected function _getSearchableAttributes($backendType = null)
00333 {
00334 if (is_null($this->_searchableAttributes)) {
00335 $this->_searchableAttributes = array();
00336 $entityType = $this->getEavConfig()->getEntityType('catalog_product');
00337 $entity = $entityType->getEntity();
00338
00339 $whereCond = array(
00340 $this->_getReadAdapter()->quoteInto('is_searchable=?', 1),
00341 $this->_getReadAdapter()->quoteInto('attribute_code IN(?)', array('status', 'visibility'))
00342 );
00343
00344 $select = $this->_getReadAdapter()->select()
00345 ->from($this->getTable('eav/attribute'))
00346 ->where('entity_type_id=?', $entityType->getEntityTypeId())
00347 ->where(join(' OR ', $whereCond));
00348 $attributesData = $this->_getReadAdapter()->fetchAll($select);
00349 $this->getEavConfig()->importAttributesData($entityType, $attributesData);
00350 foreach ($attributesData as $attributeData) {
00351 $attributeCode = $attributeData['attribute_code'];
00352 $attribute = $this->getEavConfig()->getAttribute($entityType, $attributeCode);
00353 $attribute->setEntity($entity);
00354 $this->_searchableAttributes[$attribute->getId()] = $attribute;
00355 }
00356 unset($attributesData);
00357 }
00358 if (!is_null($backendType)) {
00359 $attributes = array();
00360 foreach ($this->_searchableAttributes as $attribute) {
00361 if ($attribute->getBackendType() == $backendType) {
00362 $attributes[$attribute->getId()] = $attribute;
00363 }
00364 }
00365 return $attributes;
00366 }
00367 return $this->_searchableAttributes;
00368 }
00369
00370
00371
00372
00373
00374
00375
00376 protected function _getSearchableAttribute($attribute)
00377 {
00378 $attributes = $this->_getSearchableAttributes();
00379 if (is_numeric($attribute)) {
00380 if (isset($attributes[$attribute])) {
00381 return $attributes[$attribute];
00382 }
00383 }
00384 elseif (is_string($attribute)) {
00385 foreach ($attributes as $attributeModel) {
00386 if ($attributeModel->getAttributeCode() == $attribute) {
00387 return $attributeModel;
00388 }
00389 }
00390 }
00391 return $this->getEavConfig()->getAttribute('catalog_product', $attribute);
00392 }
00393
00394
00395
00396
00397
00398
00399
00400
00401
00402
00403 protected function _getProductAttributes($storeId, array $productIds, array $atributeTypes)
00404 {
00405 $result = array();
00406 $selects = array();
00407 foreach ($atributeTypes as $backendType => $attributeIds) {
00408 if ($attributeIds) {
00409 $tableName = $this->getTable('catalog/product') . '_' . $backendType;
00410 $selects[] = $this->_getReadAdapter()->select()
00411 ->from(
00412 array('t_default' => $tableName),
00413 array('entity_id', 'attribute_id'))
00414 ->joinLeft(
00415 array('t_store' => $tableName),
00416 $this->_getReadAdapter()->quoteInto("t_default.entity_id=t_store.entity_id AND t_default.attribute_id=t_store.attribute_id AND t_store.store_id=?", $storeId),
00417 array('value'=>'IFNULL(t_store.value, t_default.value)'))
00418 ->where('t_default.store_id=?', 0)
00419 ->where('t_default.attribute_id IN(?)', $attributeIds)
00420 ->where('t_default.entity_id IN(?)', $productIds);
00421 }
00422 }
00423
00424 if ($selects) {
00425 $select = '('.join(')UNION(', $selects).')';
00426 $query = $this->_getReadAdapter()->query($select);
00427 while ($row = $query->fetch()) {
00428 $result[$row['entity_id']][$row['attribute_id']] = $row['value'];
00429 }
00430 }
00431
00432 return $result;
00433 }
00434
00435
00436
00437
00438
00439
00440
00441 protected function _getProductTypeInstance($typeId)
00442 {
00443 if (!isset($this->_productTypes[$typeId])) {
00444 $productEmulator = $this->_getProductEmulator();
00445 $productEmulator->setTypeId($typeId);
00446
00447 $this->_productTypes[$typeId] = Mage::getSingleton('catalog/product_type')
00448 ->factory($productEmulator);
00449 }
00450 return $this->_productTypes[$typeId];
00451 }
00452
00453
00454
00455
00456
00457
00458
00459
00460 protected function _getProductChildIds($productId, $typeId)
00461 {
00462 $typeInstance = $this->_getProductTypeInstance($typeId);
00463 $relation = $typeInstance->isComposite()
00464 ? $typeInstance->getRelationInfo()
00465 : false;
00466
00467 if ($relation && $relation->getTable() && $relation->getParentFieldName() && $relation->getChildFieldName()) {
00468 $select = $this->_getReadAdapter()->select()
00469 ->from(
00470 array('main' => $this->getTable($relation->getTable())),
00471 array($relation->getChildFieldName()))
00472 ->where("{$relation->getParentFieldName()}=?", $productId);
00473 if (!is_null($relation->getWhere())) {
00474 $select->where($relation->getWhere());
00475 }
00476 return $this->_getReadAdapter()->fetchCol($select);
00477 }
00478
00479 return null;
00480 }
00481
00482
00483
00484
00485
00486
00487 protected function _getProductEmulator()
00488 {
00489 $productEmulator = new Varien_Object();
00490 $productEmulator->setIdFieldName('entity_id');
00491 return $productEmulator;
00492 }
00493
00494
00495
00496
00497
00498
00499
00500
00501 protected function _prepareProductIndex($indexData, $productData, $storeId)
00502 {
00503 $index = array();
00504 foreach ($this->_getSearchableAttributes('static') as $attribute) {
00505 if (isset($productData[$attribute->getAttributeCode()])) {
00506 if ($value = $this->_getAttributeValue($attribute->getId(), $productData[$attribute->getAttributeCode()], $storeId)) {
00507 $index[] = $value;
00508 }
00509 }
00510 }
00511 foreach ($indexData as $attributeData) {
00512 foreach ($attributeData as $attributeId => $attributeValue) {
00513 if ($value = $this->_getAttributeValue($attributeId, $attributeValue, $storeId)) {
00514 $index[] = $value;
00515 }
00516 }
00517 }
00518
00519 $product = $this->_getProductEmulator()
00520 ->setId($productData['entity_id'])
00521 ->setTypeId($productData['type_id'])
00522 ->setStoreId($storeId);
00523 $typeInstance = $this->_getProductTypeInstance($productData['type_id']);
00524 if ($data = $typeInstance->getSearchableData($product)) {
00525 $index = array_merge($index, $data);
00526 }
00527
00528 return join($this->_separator, $index);
00529 }
00530
00531
00532
00533
00534
00535
00536
00537
00538 protected function _getAttributeValue($attributeId, $value, $storeId)
00539 {
00540 $attribute = $this->_getSearchableAttribute($attributeId);
00541 if (!$attribute->getIsSearchable()) {
00542 return null;
00543 }
00544 if ($attribute->usesSource()) {
00545 $attribute->setStoreId($storeId);
00546 $value = $attribute->getSource()->getOptionText($value);
00547 }
00548
00549 if (is_array($value)) {
00550 $value = implode($this->_separator, $value);
00551 }
00552
00553 return preg_replace("#\s+#si", " ", trim(strip_tags($value)));
00554 }
00555
00556
00557
00558
00559
00560
00561
00562
00563
00564 protected function _saveProductIndex($productId, $storeId, $index)
00565 {
00566 $this->_getWriteAdapter()->insert($this->getMainTable(), array(
00567 'product_id' => $productId,
00568 'store_id' => $storeId,
00569 'data_index' => $index
00570 ));
00571 return $this;
00572 }
00573
00574
00575
00576
00577
00578
00579
00580
00581 protected function _saveProductIndexes($storeId, $productIndexes)
00582 {
00583 $values = array();
00584 $bind = array();
00585 foreach ($productIndexes as $productId => &$index) {
00586 $values[] = sprintf('(%s,%s,%s)',
00587 $this->_getWriteAdapter()->quoteInto('?', $productId),
00588 $this->_getWriteAdapter()->quoteInto('?', $storeId),
00589 '?'
00590 );
00591 $bind[] = $index;
00592 }
00593
00594 if ($values) {
00595 $sql = "REPLACE INTO `{$this->getMainTable()}` VALUES"
00596 . join(',', $values);
00597 $this->_getWriteAdapter()->query($sql, $bind);
00598 }
00599
00600 return $this;
00601 }
00602 }