File indexing completed on 2025-05-04 05:29:11
0001 <?php 0002 0003 /** 0004 * ocs-webserver 0005 * 0006 * Copyright 2016 by pling GmbH. 0007 * 0008 * This file is part of ocs-webserver. 0009 * 0010 * This program is free software: you can redistribute it and/or modify 0011 * it under the terms of the GNU Affero General Public License as 0012 * published by the Free Software Foundation, either version 3 of the 0013 * License, or (at your option) any later version. 0014 * 0015 * This program is distributed in the hope that it will be useful, 0016 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0018 * GNU Affero General Public License for more details. 0019 * 0020 * You should have received a copy of the GNU Affero General Public License 0021 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0022 **/ 0023 class Default_Model_Search_Lucene 0024 { 0025 0026 /** @var Zend_Search_Lucene */ 0027 protected $_index; 0028 /** @var Zend_Config */ 0029 protected $config; 0030 0031 /** 0032 * @param array|Zend_config $config 0033 * @throws Exception 0034 */ 0035 function __construct($config = null) 0036 { 0037 if (false == isset($config)) { 0038 if (Zend_Registry::isRegistered('config')) { 0039 $this->config = Zend_Registry::get('config')->settings->search; 0040 } else { 0041 throw new Exception(__CLASS__ . ' needs config object in constructor'); 0042 } 0043 } 0044 0045 if ($config instanceof Zend_Config) { 0046 $this->config = $config; 0047 } elseif (is_array($config)) { 0048 $this->config = new Zend_Config($config); 0049 } 0050 0051 Zend_Search_Lucene_Analysis_Analyzer::setDefault(new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8_CaseInsensitive()); 0052 } 0053 0054 /** 0055 * @param int $storeId 0056 * @param string $searchIndexId 0057 * @throws Zend_Exception 0058 */ 0059 public function createStoreSearchIndex($storeId, $searchIndexId) 0060 { 0061 Zend_Registry::get('logger')->debug(__METHOD__ . ' - ' . print_r(func_get_args(), true)); 0062 0063 $this->initStoreForSearchEngine($searchIndexId); 0064 0065 $searchIndexEngine = Zend_Search_Lucene::create($this->config->path . $searchIndexId); 0066 0067 $elementsForIndex = $this->fetchElementsForIndex($storeId); 0068 0069 $this->createSearchIndex($searchIndexEngine, $elementsForIndex); 0070 } 0071 0072 /** 0073 * @param string $searchIndexId 0074 * @throws Exception 0075 */ 0076 private function initStoreForSearchEngine($searchIndexId) 0077 { 0078 $dataPath = $this->config->path; 0079 0080 if (false == file_exists($dataPath)) { 0081 throw new Zend_Exception('DataPath for search engine does not exist or has no rights: ' . $dataPath); 0082 } 0083 if (false == is_writable($dataPath)) { 0084 throw new Zend_Exception('DataPath for search engine not writable: ' . $dataPath); 0085 } 0086 $pathSearchIndex = $dataPath . DIRECTORY_SEPARATOR . $searchIndexId; 0087 if (file_exists($pathSearchIndex)) { 0088 if (false == is_writable($pathSearchIndex)) { 0089 throw new Zend_Exception($dataPath . DIRECTORY_SEPARATOR . $searchIndexId . ' is not writable'); 0090 } 0091 } else { 0092 if (false == mkdir($dataPath . $searchIndexId)) { 0093 throw new Zend_Exception($dataPath . $searchIndexId . ' could not created'); 0094 } 0095 } 0096 } 0097 0098 /** 0099 * @param int $storeId 0100 * @return array 0101 */ 0102 private function fetchElementsForIndex($storeId) 0103 { 0104 $storeCategories = $this->fetchCategoriesForStore($storeId); 0105 return $this->fetchElementsForCategories($storeCategories); 0106 } 0107 0108 /** 0109 * Returns all category ids which stored in database for this storeId. When 0110 * nothing was found, it returns all main categories in database. 0111 * 0112 * @param int $storeId 0113 * @return array 0114 */ 0115 private function fetchCategoriesForStore($storeId) 0116 { 0117 $modelStoreCategories = new Default_Model_DbTable_ConfigStoreCategory(); 0118 $resultSet = $modelStoreCategories->fetchAllCategoriesForStore($storeId); 0119 if (count($resultSet) > 0) { 0120 return $resultSet; 0121 } 0122 $modelCategories = new Default_Model_DbTable_ProjectCategory(); 0123 $resultSet = $modelCategories->fetchMainCatIdsOrdered(); 0124 $subCatIds = $modelCategories->fetchChildIds($resultSet); 0125 return array_merge($resultSet, $subCatIds); 0126 } 0127 0128 /** 0129 * @param array $storeCategories 0130 * @return array 0131 */ 0132 private function fetchElementsForCategories($storeCategories) 0133 { 0134 $modelProduct = new Default_Model_Project(); 0135 return $modelProduct->fetchProductsForCategories($storeCategories); 0136 } 0137 0138 /** 0139 * @param Zend_Search_Lucene_Interface $searchIndexEngine 0140 * @param array $elementsForIndex 0141 */ 0142 private function createSearchIndex($searchIndexEngine, $elementsForIndex) 0143 { 0144 foreach ($elementsForIndex as $element) { 0145 $doc = $this->createIndexDocument($element); 0146 $searchIndexEngine->addDocument($doc); 0147 } 0148 $searchIndexEngine->optimize(); 0149 } 0150 0151 /** 0152 * @param array $element 0153 * @return Zend_Search_Lucene_Document 0154 */ 0155 protected function createIndexDocument($element) 0156 { 0157 $doc = new Zend_Search_Lucene_Document(); 0158 0159 $doc->addField(Zend_Search_Lucene_Field::keyword('project_id', $element['project_id'])); 0160 $doc->addField(Zend_Search_Lucene_Field::keyword('member_id', $element['member_id'])); 0161 $doc->addField(Zend_Search_Lucene_Field::keyword('project_category_id', $element['project_category_id'])); 0162 0163 $doc->addField(Zend_Search_Lucene_Field::text('title', $element['title'], 'UTF-8')); 0164 $doc->addField(Zend_Search_Lucene_Field::text('description', $element['description'], 'UTF-8')); 0165 $doc->addField(Zend_Search_Lucene_Field::text('username', $element['username'], 'UTF-8')); 0166 $doc->addField(Zend_Search_Lucene_Field::text('category', $element['cat_title'], 'UTF-8')); 0167 0168 $isUpdate = ($element['type_id'] == Default_Model_DbTable_Project::PROJECT_TYPE_UPDATE); 0169 $helperBuildProductUrl = new Default_View_Helper_BuildProductUrl(); 0170 if ($isUpdate) { 0171 $showUrl = $helperBuildProductUrl->buildProductUrl($element['pid']) . '#anker_' . $element['project_id']; 0172 $plingUrl = $helperBuildProductUrl->buildProductUrl($element['pid'], 'pling'); 0173 } else { 0174 $showUrl = $helperBuildProductUrl->buildProductUrl($element['project_id']); 0175 $plingUrl = $helperBuildProductUrl->buildProductUrl($element['project_id'], 'pling'); 0176 } 0177 0178 $doc->addField(Zend_Search_Lucene_Field::unIndexed('showUrl', $showUrl)); 0179 $doc->addField(Zend_Search_Lucene_Field::unIndexed('plingUrl', $plingUrl)); 0180 0181 $doc->addField(Zend_Search_Lucene_Field::unIndexed('uuid', $element['uuid'])); 0182 $doc->addField(Zend_Search_Lucene_Field::unIndexed('type_id', $element['type_id'])); 0183 $doc->addField(Zend_Search_Lucene_Field::unIndexed('pid', $element['pid'])); 0184 $doc->addField(Zend_Search_Lucene_Field::unIndexed('image_small', $element['image_small'])); 0185 0186 $doc->addField(Zend_Search_Lucene_Field::unIndexed('facebook_code', $element['facebook_code'])); 0187 $doc->addField(Zend_Search_Lucene_Field::unIndexed('twitter_code', $element['twitter_code'])); 0188 $doc->addField(Zend_Search_Lucene_Field::unIndexed('google_code', $element['google_code'])); 0189 $doc->addField(Zend_Search_Lucene_Field::unIndexed('link_1', $element['link_1'])); 0190 $doc->addField(Zend_Search_Lucene_Field::unIndexed('ppload_collection_id', 0191 $element['ppload_collection_id'])); 0192 0193 $doc->addField(Zend_Search_Lucene_Field::unIndexed('validated', $element['validated'])); 0194 $doc->addField(Zend_Search_Lucene_Field::unIndexed('amount', $element['amount'])); 0195 $doc->addField(Zend_Search_Lucene_Field::unIndexed('claimable', $element['claimable'])); 0196 $doc->addField(Zend_Search_Lucene_Field::unIndexed('claimed_by_member', $element['claimed_by_member'])); 0197 $doc->addField(Zend_Search_Lucene_Field::unIndexed('created_at', $element['created_at'])); 0198 $doc->addField(Zend_Search_Lucene_Field::unIndexed('changed_at', $element['changed_at'])); 0199 $doc->addField(Zend_Search_Lucene_Field::unIndexed('project_changed_at', $element['project_changed_at'])); 0200 0201 $doc->addField(Zend_Search_Lucene_Field::unIndexed('profile_image_url', $element['profile_image_url'])); 0202 $doc->addField(Zend_Search_Lucene_Field::unIndexed('paypal_mail', $element['paypal_mail'])); 0203 $doc->addField(Zend_Search_Lucene_Field::unIndexed('dwolla_id', $element['dwolla_id'])); 0204 $doc->addField(Zend_Search_Lucene_Field::unIndexed('mail', $element['mail'])); 0205 $doc->addField(Zend_Search_Lucene_Field::unIndexed('roleId', $element['roleId'])); 0206 0207 $doc->addField(Zend_Search_Lucene_Field::unIndexed('version', $element['version'])); 0208 $doc->addField(Zend_Search_Lucene_Field::unIndexed('count_likes', $element['count_likes'])); 0209 $doc->addField(Zend_Search_Lucene_Field::unIndexed('count_dislikes', $element['count_dislikes'])); 0210 $doc->addField(Zend_Search_Lucene_Field::unIndexed('count_comments', $element['count_comments'])); 0211 $doc->addField(Zend_Search_Lucene_Field::unIndexed('count_downloads_hive', 0212 $element['count_downloads_hive'])); 0213 0214 //$doc->addField(Zend_Search_Lucene_Field::unIndexed('amount_received', $element['amount_received'])); 0215 //$doc->addField(Zend_Search_Lucene_Field::unIndexed('count_plings', $element['count_plings'])); 0216 //$doc->addField(Zend_Search_Lucene_Field::unIndexed('count_plingers', $element['count_plingers'])); 0217 //$doc->addField(Zend_Search_Lucene_Field::unIndexed('latest_pling', $element['latest_pling'])); 0218 0219 $doc->addField(Zend_Search_Lucene_Field::unIndexed('laplace_score', $element['laplace_score'])); 0220 0221 $doc->addField(Zend_Search_Lucene_Field::unIndexed('source_id', $element['source_id'])); 0222 $doc->addField(Zend_Search_Lucene_Field::unIndexed('source_pk', $element['source_pk'])); 0223 return $doc; 0224 } 0225 0226 /** 0227 * @param array $product 0228 */ 0229 public function addDocument($product) 0230 { 0231 $catAncestors = $this->findCatAncestors($product['project_category_id']); 0232 $stores = $this->findStoresForCategories($catAncestors); 0233 $document = $this->createIndexDocument($product); 0234 $this->addDocumentToStoreIndex($document, $stores); 0235 } 0236 0237 /** 0238 * @param int $project_category_id 0239 * @return array 0240 */ 0241 private function findCatAncestors($project_category_id) 0242 { 0243 // find all ancestors for given cat id up to the root node 0244 $modelCategory = new Default_Model_DbTable_ProjectCategory(); 0245 $resultRow = $modelCategory->fetchAncestorsAsId($project_category_id); 0246 0247 // build array 0248 $listCatId = count($resultRow) > 0 ? explode(',', $resultRow['ancestors']) : array(); 0249 $listCatId[] = $project_category_id; 0250 0251 return $listCatId; 0252 } 0253 0254 /** 0255 * @param array $catAncestors 0256 * @return array 0257 */ 0258 private function findStoresForCategories($catAncestors) 0259 { 0260 // find cat id's in store category config 0261 $modelStore = new Default_Model_DbTable_ConfigStoreCategory(); 0262 $stores = $modelStore->fetchStoresForCatdId($catAncestors); 0263 return $stores; 0264 } 0265 0266 /** 0267 * @param Zend_Search_Lucene_Document $document 0268 * @param array $stores 0269 * @throws Zend_Exception 0270 */ 0271 private function addDocumentToStoreIndex($document, $stores) 0272 { 0273 // $configSearch = $this->config->settings->search; 0274 $dataPath = $this->config->path; 0275 foreach ($stores as $store) { 0276 $indexPath = $dataPath . $store['config_id_name'] . DIRECTORY_SEPARATOR; 0277 try { 0278 $index = Zend_Search_Lucene::open($indexPath); 0279 } catch (Exception $e) { 0280 Zend_Registry::get('logger')->err(__METHOD__ . ' - cannot open data path for search index: ' . $indexPath); 0281 Zend_Registry::get('logger')->err(__METHOD__ . ' - ' . $e->getMessage()); 0282 continue; 0283 } 0284 $index->addDocument($document); 0285 } 0286 } 0287 0288 /** 0289 * @param array $product 0290 */ 0291 public function deleteDocument($product) 0292 { 0293 $catAncestors = $this->findCatAncestors($product['project_category_id']); 0294 $stores = $this->findStoresForCategories($catAncestors); 0295 $this->delDocumentInStoreIndex($product['project_id'], $stores); 0296 } 0297 0298 /** 0299 * @param int $productId 0300 * @param array $stores 0301 * @throws Zend_Exception 0302 */ 0303 private function delDocumentInStoreIndex($productId, $stores) 0304 { 0305 // delete product in search index for every store 0306 // $configSearch = $this->config->settings->search; 0307 $dataPath = $this->config->path; 0308 foreach ($stores as $store) { 0309 $indexPath = $dataPath . $store['config_id_name'] . DIRECTORY_SEPARATOR; 0310 try { 0311 $index = Zend_Search_Lucene::open($indexPath); 0312 } catch (Exception $e) { 0313 Zend_Registry::get('logger')->err(__METHOD__ . ' - cannot open data path for search index: ' . $indexPath); 0314 Zend_Registry::get('logger')->err(__METHOD__ . ' - ' . $e->getMessage()); 0315 continue; 0316 } 0317 $documents = $index->find('project_id:' . $productId); 0318 foreach ($documents as $hit) { 0319 $index->delete($hit->id); 0320 } 0321 // $index->optimize(); 0322 } 0323 } 0324 0325 /** 0326 * @param array $product 0327 */ 0328 public function updateDocument($product) 0329 { 0330 $catAncestors = $this->findCatAncestors($product['project_category_id']); 0331 $stores = $this->findStoresForCategories($catAncestors); 0332 $this->delDocumentInStoreIndex($product['project_id'], $stores); 0333 $document = $this->createIndexDocument($product); 0334 $this->addDocumentToStoreIndex($document, $stores); 0335 } 0336 0337 }