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 }