|
eZ Publish
[trunk]
|
00001 <?php 00002 /** 00003 * File containing the eZSearchEngine class. 00004 * 00005 * @copyright Copyright (C) 1999-2012 eZ Systems AS. All rights reserved. 00006 * @license http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 00007 * @version //autogentag// 00008 * @package kernel 00009 */ 00010 00011 /*! 00012 \class eZSearchEngine ezsearch.php 00013 00014 */ 00015 00016 class eZSearchEngine implements ezpSearchEngine 00017 { 00018 function eZSearchEngine() 00019 { 00020 $generalFilter = array( 'subTreeTable' => '', 00021 'searchDateQuery' => '', 00022 'sectionQuery' => '', 00023 'classQuery' => '', 00024 'classAttributeQuery' => '', 00025 'searchPartText' => '', 00026 'subTreeSQL' => '', 00027 'sqlPermissionChecking' => array( 'from' => '', 00028 'where' => '' ) ); 00029 $this->GeneralFilter = $generalFilter; 00030 } 00031 00032 00033 public function needCommit() 00034 { 00035 //commits are NA 00036 return false; 00037 } 00038 00039 public function needRemoveWithUpdate() 00040 { 00041 return true; 00042 } 00043 00044 /** 00045 * Adds object $contentObject to the search database. 00046 * 00047 * @param eZContentObject $contentObject Object to add to search engine 00048 * @param bool $commit Whether to commit after adding the object 00049 * @return bool True if the operation succeed. 00050 */ 00051 public function addObject( $contentObject, $commit = true ) 00052 { 00053 $contentObjectID = $contentObject->attribute( 'id' ); 00054 $currentVersion = $contentObject->currentVersion(); 00055 00056 if ( !$currentVersion ) 00057 { 00058 $errCurrentVersion = $contentObject->attribute( 'current_version'); 00059 eZDebug::writeError( "Failed to fetch \"current version\" ({$errCurrentVersion})" . 00060 " of content object (ID: {$contentObjectID})", 'eZSearchEngine' ); 00061 return false; 00062 } 00063 00064 $indexArray = array(); 00065 $indexArrayOnlyWords = array(); 00066 00067 $wordCount = 0; 00068 $placement = 0; 00069 $previousWord = ''; 00070 00071 eZContentObject::recursionProtectionStart(); 00072 foreach ( $currentVersion->contentObjectAttributes() as $attribute ) 00073 { 00074 $metaData = array(); 00075 $classAttribute = $attribute->contentClassAttribute(); 00076 if ( $classAttribute->attribute( "is_searchable" ) == 1 ) 00077 { 00078 // Fetch attribute translations 00079 $attributeTranslations = $attribute->fetchAttributeTranslations(); 00080 00081 foreach ( $attributeTranslations as $translation ) 00082 { 00083 $tmpMetaData = $translation->metaData(); 00084 if( ! is_array( $tmpMetaData ) ) 00085 { 00086 $tmpMetaData = array( array( 'id' => $attribute->attribute( 'contentclass_attribute_identifier' ), 00087 'text' => $tmpMetaData ) ); 00088 } 00089 $metaData = array_merge( $metaData, $tmpMetaData ); 00090 } 00091 00092 foreach( $metaData as $metaDataPart ) 00093 { 00094 $text = eZSearchEngine::normalizeText( htmlspecialchars ($metaDataPart['text'], ENT_NOQUOTES, 'UTF-8' ) , true ); 00095 00096 // Split text on whitespace 00097 if ( is_numeric( trim( $text ) ) ) 00098 { 00099 $integerValue = (int) $text; 00100 } 00101 else 00102 { 00103 $integerValue = 0; 00104 } 00105 $wordArray = explode( ' ', $text ); 00106 00107 foreach ( $wordArray as $word ) 00108 { 00109 if ( trim( $word ) != "" ) 00110 { 00111 // words stored in search index are limited to 150 characters 00112 if ( strlen( $word ) > 150 ) 00113 { 00114 $word = substr( $word, 0, 150 ); 00115 } 00116 $indexArray[] = array( 'Word' => $word, 00117 'ContentClassAttributeID' => $attribute->attribute( 'contentclassattribute_id' ), 00118 'identifier' => $metaDataPart['id'], 00119 'integer_value' => $integerValue ); 00120 $indexArrayOnlyWords[$word] = 1; 00121 $wordCount++; 00122 //if we have "www." before word than 00123 //treat it as url and add additional entry to the index 00124 if ( substr( strtolower($word), 0, 4 ) == 'www.' ) 00125 { 00126 $additionalUrlWord = substr( $word, 4 ); 00127 $indexArray[] = array( 'Word' => $additionalUrlWord, 00128 'ContentClassAttributeID' => $attribute->attribute( 'contentclassattribute_id' ), 00129 'identifier' => $metaDataPart['id'], 00130 'integer_value' => $integerValue ); 00131 $indexArrayOnlyWords[$additionalUrlWord] = 1; 00132 $wordCount++; 00133 } 00134 } 00135 } 00136 } 00137 } 00138 } 00139 eZContentObject::recursionProtectionEnd(); 00140 00141 $wordIDArray = $this->buildWordIDArray( array_keys( $indexArrayOnlyWords ) ); 00142 00143 $db = eZDB::instance(); 00144 $db->begin(); 00145 for( $arrayCount = 0; $arrayCount < $wordCount; $arrayCount += 1000 ) 00146 { 00147 $placement = $this->indexWords( $contentObject, array_slice( $indexArray, $arrayCount, 1000 ), $wordIDArray, $placement ); 00148 } 00149 $db->commit(); 00150 00151 return true; 00152 } 00153 00154 /*! 00155 \private 00156 00157 Build WordIDArray and update ezsearch_word table 00158 00159 \params words for object to add 00160 00161 \return wordIDArray 00162 */ 00163 function buildWordIDArray( $indexArrayOnlyWords ) 00164 { 00165 $db = eZDB::instance(); 00166 00167 // Initialize transformation system 00168 $trans = eZCharTransform::instance(); 00169 00170 $wordCount = count( $indexArrayOnlyWords ); 00171 $wordIDArray = array(); 00172 $wordArray = array(); 00173 // store the words in the index and remember the ID 00174 $dbName = $db->databaseName(); 00175 if ( $dbName == 'mysql' ) 00176 { 00177 $db->begin(); 00178 for( $arrayCount = 0; $arrayCount < $wordCount; $arrayCount += 500 ) 00179 { 00180 // Fetch already indexed words from database 00181 $wordArrayChuck = array_slice( $indexArrayOnlyWords, $arrayCount, 500 ); 00182 $wordsString = implode( '\',\'', $wordArrayChuck ); 00183 $wordRes = $db->arrayQuery( "SELECT * FROM ezsearch_word WHERE word IN ( '$wordsString' ) " ); 00184 00185 // Build a has of the existing words 00186 $wordResCount = count( $wordRes ); 00187 $existingWordArray = array(); 00188 for ( $i = 0; $i < $wordResCount; $i++ ) 00189 { 00190 $wordIDArray[] = $wordRes[$i]['id']; 00191 $existingWordArray[] = $wordRes[$i]['word']; 00192 $wordArray[$wordRes[$i]['word']] = $wordRes[$i]['id']; 00193 } 00194 00195 // Update the object count of existing words by one 00196 $wordIDString = implode( ',', $wordIDArray ); 00197 if ( count( $wordIDArray ) > 0 ) 00198 $db->query( "UPDATE ezsearch_word SET object_count=( object_count + 1 ) WHERE id IN ( $wordIDString )" ); 00199 00200 // Insert if there is any news words 00201 $newWordArray = array_diff( $wordArrayChuck, $existingWordArray ); 00202 if ( count ($newWordArray) > 0 ) 00203 { 00204 $newWordString = implode( "', '1' ), ('", $newWordArray ); 00205 $db->query( "INSERT INTO 00206 ezsearch_word ( word, object_count ) 00207 VALUES ('$newWordString', '1' )" ); 00208 $newWordString = implode( "','", $newWordArray ); 00209 $newWordRes = $db->arrayQuery( "select id,word from ezsearch_word where word in ( '$newWordString' ) " ); 00210 $newWordCount = count( $newWordRes ); 00211 for ( $i=0;$i<$newWordCount;++$i ) 00212 { 00213 $wordLowercase = $trans->transformByGroup( $newWordRes[$i]['word'], 'lowercase' ); 00214 $wordArray[$newWordRes[$i]['word']] = $newWordRes[$i]['id']; 00215 } 00216 } 00217 } 00218 $db->commit(); 00219 } 00220 else 00221 { 00222 $db->begin(); 00223 foreach ( $indexArrayOnlyWords as $indexWord ) 00224 { 00225 $indexWord = $trans->transformByGroup( $indexWord, 'lowercase' ); 00226 $indexWord = $db->escapeString( $indexWord ); 00227 00228 // Store word if it does not exist. 00229 $wordRes = array(); 00230 00231 $wordRes = $db->arrayQuery( "SELECT * FROM ezsearch_word WHERE word='$indexWord'" ); 00232 00233 if ( count( $wordRes ) == 1 ) 00234 { 00235 $wordID = $wordRes[0]["id"]; 00236 $db->query( "UPDATE ezsearch_word SET object_count=( object_count + 1 ) WHERE id='$wordID'" ); 00237 } 00238 else 00239 { 00240 $db->query( "INSERT INTO 00241 ezsearch_word ( word, object_count ) 00242 VALUES ( '$indexWord', '1' )" ); 00243 $wordID = $db->lastSerialID( "ezsearch_word", "id" ); 00244 } 00245 00246 $wordArray[$indexWord] = $wordID; 00247 } 00248 $db->commit(); 00249 } 00250 00251 return $wordArray; 00252 } 00253 00254 /*! 00255 \private 00256 00257 \param contentObject 00258 \param indexArray 00259 \param wordIDArray 00260 \param placement 00261 00262 \return last placement 00263 Index wordIndex 00264 */ 00265 function indexWords( $contentObject, $indexArray, $wordIDArray, $placement = 0 ) 00266 { 00267 $db = eZDB::instance(); 00268 00269 $contentObjectID = $contentObject->attribute( 'id' ); 00270 00271 // Count the total words in index text 00272 $totalWordCount = count( $indexArray ); 00273 00274 /* // Needs to be rewritten 00275 00276 // Count the number of instances of each word 00277 $wordCountArray = array_count_values( $indexArray ); 00278 00279 // Strip double words 00280 $uniqueIndexArray = array_unique( $indexArray ); 00281 00282 // Count unique words 00283 $uniqueWordCount = count( $uniqueIndexArray ); 00284 */ 00285 00286 // Initialize transformation system 00287 $trans = eZCharTransform::instance(); 00288 00289 $prevWordID = 0; 00290 $nextWordID = 0; 00291 $classID = $contentObject->attribute( 'contentclass_id' ); 00292 $sectionID = $contentObject->attribute( 'section_id' ); 00293 $published = $contentObject->attribute( 'published' ); 00294 $valuesStringList = array(); 00295 for ( $i = 0; $i < count( $indexArray ); $i++ ) 00296 { 00297 $indexWord = $indexArray[$i]['Word']; 00298 $contentClassAttributeID = $indexArray[$i]['ContentClassAttributeID']; 00299 $identifier = $indexArray[$i]['identifier']; 00300 $integerValue = $indexArray[$i]['integer_value']; 00301 $wordID = $wordIDArray[$indexWord]; 00302 00303 if ( isset( $indexArray[$i+1] ) ) 00304 { 00305 $nextIndexWord = $indexArray[$i+1]['Word']; 00306 $nextWordID = $wordIDArray[$nextIndexWord]; 00307 } 00308 else 00309 $nextWordID = 0; 00310 // print( "indexing word : $indexWord <br> " ); 00311 00312 // Calculate the relevans ranking for this word 00313 // $frequency = ( $wordCountArray[$indexWord] / $totalWordCount ); 00314 $frequency = 0; 00315 $valuesStringList[] = " ( '$wordID', '$contentObjectID', '$frequency', '$placement', '$nextWordID', '$prevWordID', '$classID', '$contentClassAttributeID', '$published', '$sectionID', '$identifier', '$integerValue' ) "; 00316 00317 $prevWordID = $wordID; 00318 $placement++; 00319 } 00320 $dbName = $db->databaseName(); 00321 00322 if ( $dbName == 'mysql' ) 00323 { 00324 if ( count( $valuesStringList ) > 0 ) 00325 { 00326 $valuesString = implode( ',', $valuesStringList ); 00327 $db->query( "INSERT INTO 00328 ezsearch_object_word_link 00329 ( word_id, 00330 contentobject_id, 00331 frequency, 00332 placement, 00333 next_word_id, 00334 prev_word_id, 00335 contentclass_id, 00336 contentclass_attribute_id, 00337 published, 00338 section_id, 00339 identifier, 00340 integer_value ) 00341 VALUES $valuesString" ); 00342 } 00343 } 00344 else 00345 { 00346 $db->begin(); 00347 foreach ( array_keys( $valuesStringList ) as $key ) 00348 { 00349 $valuesString = $valuesStringList[$key]; 00350 $db->query("INSERT INTO 00351 ezsearch_object_word_link 00352 ( word_id, 00353 contentobject_id, 00354 frequency, 00355 placement, 00356 next_word_id, 00357 prev_word_id, 00358 contentclass_id, 00359 contentclass_attribute_id, 00360 published, 00361 section_id, 00362 identifier, 00363 integer_value ) 00364 VALUES $valuesString" ); 00365 } 00366 $db->commit(); 00367 } 00368 00369 return $placement; 00370 } 00371 00372 /** 00373 * Removes object $contentObject from the search database. 00374 * 00375 * @param eZContentObject $contentObject the content object to remove 00376 * @param bool $commit Whether to commit after removing the object 00377 * @return bool True if the operation succeed. 00378 */ 00379 public function removeObject( $contentObject, $commit = true ) 00380 { 00381 $db = eZDB::instance(); 00382 $objectID = $contentObject->attribute( "id" ); 00383 $doDelete = false; 00384 $db->begin(); 00385 00386 if ( $db->databaseName() == 'mysql' ) 00387 { 00388 // fetch all the words and decrease the object count on all the words 00389 $wordArray = $db->arrayQuery( "SELECT word_id FROM ezsearch_object_word_link WHERE contentobject_id='$objectID'" ); 00390 $wordIDList = array(); 00391 foreach ( $wordArray as $word ) 00392 $wordIDList[] = $word["word_id"]; 00393 if ( count( $wordIDList ) > 0 ) 00394 { 00395 $wordIDString = implode( ',', $wordIDList ); 00396 $db->query( "UPDATE ezsearch_word SET object_count=( object_count - 1 ) WHERE id in ( $wordIDString )" ); 00397 $doDelete = true; 00398 } 00399 } 00400 else 00401 { 00402 $cnt = $db->arrayQuery( "SELECT COUNT( word_id ) AS cnt FROM ezsearch_object_word_link WHERE contentobject_id='$objectID'" ); 00403 if ( $cnt[0]['cnt'] > 0 ) 00404 { 00405 $db->query( "UPDATE ezsearch_word SET object_count=( object_count - 1 ) WHERE id in ( SELECT word_id FROM ezsearch_object_word_link WHERE contentobject_id='$objectID' )" ); 00406 $doDelete = true; 00407 } 00408 } 00409 00410 if ( $doDelete ) 00411 { 00412 $db->query( "DELETE FROM ezsearch_word WHERE object_count='0'" ); 00413 $db->query( "DELETE FROM ezsearch_object_word_link WHERE contentobject_id='$objectID'" ); 00414 } 00415 $db->commit(); 00416 } 00417 00418 /*! 00419 Saves name of a temporary that has just been created, 00420 for us to know its name when it's time to drop the table. 00421 */ 00422 function saveCreatedTempTableName( $index, $tableName ) 00423 { 00424 if ( isset( $this->CreatedTempTablesNames[$index] ) ) 00425 { 00426 eZDebug::writeWarning( "CreatedTempTablesNames[\$index] already exists " . 00427 "and contains '" . $this->CreatedTempTablesNames[$index] . "'" ); 00428 } 00429 $this->CreatedTempTablesNames[$index] = $tableName; 00430 } 00431 00432 /*! 00433 \return Given table name from the list of saved temporary tables names by its index. 00434 \see saveCreatedTempTableName() 00435 */ 00436 function getSavedTempTableName( $index ) 00437 { 00438 return $this->CreatedTempTablesNames[$index]; 00439 } 00440 00441 /*! 00442 \return List of saved temporary tables names. 00443 \see saveCreatedTempTableName() 00444 */ 00445 function getSavedTempTablesList() 00446 { 00447 return $this->CreatedTempTablesNames; 00448 } 00449 00450 /*! 00451 Runs a query to the search engine. 00452 */ 00453 public function search( $searchText, $params = array(), $searchTypes = array() ) 00454 { 00455 if ( count( $searchTypes ) == 0 ) 00456 { 00457 $searchTypes['general'] = array(); 00458 $searchTypes['subtype'] = array(); 00459 $searchTypes['and'] = array(); 00460 } 00461 else if ( !isset( $searchTypes['general'] ) ) 00462 { 00463 $searchTypes['general'] = array(); 00464 } 00465 $allowSearch = true; 00466 if ( trim( $searchText ) == '' ) 00467 { 00468 $ini = eZINI::instance(); 00469 if ( $ini->variable( 'SearchSettings', 'AllowEmptySearch' ) != 'enabled' ) 00470 $allowSearch = false; 00471 if ( isset( $params['AllowEmptySearch'] ) ) 00472 $allowSearch = $params['AllowEmptySearch']; 00473 } 00474 if ( $allowSearch ) 00475 { 00476 $searchText = $this->normalizeText( $searchText, false ); 00477 $db = eZDB::instance(); 00478 00479 $nonExistingWordArray = array(); 00480 $searchTypeMap = array( 'class' => 'SearchContentClassID', 00481 'publishdate' => 'SearchDate', 00482 'subtree' => 'SearchSubTreeArray' ); 00483 00484 foreach ( $searchTypes['general'] as $searchType ) 00485 { 00486 $params[$searchTypeMap[$searchType['subtype']]] = $searchType['value']; 00487 } 00488 00489 if ( isset( $params['SearchOffset'] ) ) 00490 $searchOffset = $params['SearchOffset']; 00491 else 00492 $searchOffset = 0; 00493 00494 if ( isset( $params['SearchLimit'] ) ) 00495 $searchLimit = $params['SearchLimit']; 00496 else 00497 $searchLimit = 10; 00498 00499 if ( isset( $params['SearchContentClassID'] ) ) 00500 $searchContentClassID = $params['SearchContentClassID']; 00501 else 00502 $searchContentClassID = -1; 00503 00504 if ( isset( $params['SearchSectionID'] ) ) 00505 $searchSectionID = $params['SearchSectionID']; 00506 else 00507 $searchSectionID = -1; 00508 00509 if ( isset( $params['SearchDate'] ) ) 00510 $searchDate = $params['SearchDate']; 00511 else 00512 $searchDate = -1; 00513 00514 if ( isset( $params['SearchTimestamp'] ) ) 00515 $searchTimestamp = $params['SearchTimestamp']; 00516 else 00517 $searchTimestamp = false; 00518 00519 if ( isset( $params['SearchContentClassAttributeID'] ) ) 00520 $searchContentClassAttributeID = $params['SearchContentClassAttributeID']; 00521 else 00522 $searchContentClassAttributeID = -1; 00523 00524 if ( isset( $params['SearchSubTreeArray'] ) ) 00525 $subTreeArray = $params['SearchSubTreeArray']; 00526 else 00527 $subTreeArray = array(); 00528 00529 if ( isset( $params['SortArray'] ) ) 00530 $sortArray = $params['SortArray']; 00531 else 00532 $sortArray = array(); 00533 00534 $ignoreVisibility = isset( $params['IgnoreVisibility'] ) ? $params['IgnoreVisibility'] : false; 00535 00536 // strip multiple spaces 00537 $searchText = preg_replace( "(\s+)", " ", $searchText ); 00538 00539 // find the phrases 00540 /* $numQuotes = substr_count( $searchText, "\"" ); 00541 00542 $phraseTextArray = array(); 00543 $fullText = $searchText; 00544 $nonPhraseText =''; 00545 // $fullText = ''; 00546 $postPhraseText = $fullText; 00547 $pos = 0; 00548 if ( ( $numQuotes > 0 ) and ( ( $numQuotes % 2 ) == 0 ) ) 00549 { 00550 for ( $i = 0; $i < ( $numQuotes / 2 ); $i ++ ) 00551 { 00552 $quotePosStart = strpos( $searchText, '"', $pos ); 00553 $quotePosEnd = strpos( $searchText, '"', $quotePosStart + 1 ); 00554 00555 $prePhraseText = substr( $searchText, $pos, $quotePosStart - $pos ); 00556 $postPhraseText = substr( $searchText, $quotePosEnd +1 ); 00557 $phraseText = substr( $searchText, $quotePosStart + 1, $quotePosEnd - $quotePosStart - 1 ); 00558 00559 $phraseTextArray[] = $phraseText; 00560 // $fullText .= $prePhraseText; 00561 $nonPhraseText .= $prePhraseText; 00562 $pos = $quotePosEnd + 1; 00563 } 00564 } 00565 $nonPhraseText .= $postPhraseText; 00566 */ 00567 $phrasesResult = $this->getPhrases( $searchText ); 00568 $phraseTextArray = $phrasesResult['phrases']; 00569 $nonPhraseText = $phrasesResult['nonPhraseText']; 00570 $fullText = $phrasesResult['fullText']; 00571 00572 $sectionQuery = ''; 00573 if ( is_numeric( $searchSectionID ) and $searchSectionID > 0 ) 00574 { 00575 $sectionQuery = "ezsearch_object_word_link.section_id = '$searchSectionID' AND "; 00576 } 00577 else if ( is_array( $searchSectionID ) ) 00578 { 00579 // Build query for searching in an array of sections 00580 $sectionQuery = $db->generateSQLINStatement( $searchSectionID, 'ezsearch_object_word_link.section_id', false, false, 'int' ) . " AND "; 00581 } 00582 00583 $searchDateQuery = ''; 00584 if ( ( is_numeric( $searchDate ) and $searchDate > 0 ) or 00585 $searchTimestamp ) 00586 { 00587 $date = new eZDateTime(); 00588 $timestamp = $date->timeStamp(); 00589 $day = $date->attribute('day'); 00590 $month = $date->attribute('month'); 00591 $year = $date->attribute('year'); 00592 $publishedDateStop = false; 00593 if ( $searchTimestamp ) 00594 { 00595 if ( is_array( $searchTimestamp ) ) 00596 { 00597 $publishedDate = (int) $searchTimestamp[0]; 00598 $publishedDateStop = (int) $searchTimestamp[1]; 00599 } 00600 else 00601 $publishedDate = (int) $searchTimestamp; 00602 } 00603 else 00604 { 00605 switch ( $searchDate ) 00606 { 00607 case 1: 00608 { 00609 $adjustment = 24*60*60; //seconds for one day 00610 $publishedDate = $timestamp - $adjustment; 00611 } break; 00612 case 2: 00613 { 00614 $adjustment = 7*24*60*60; //seconds for one week 00615 $publishedDate = $timestamp - $adjustment; 00616 } break; 00617 case 3: 00618 { 00619 $adjustment = 31*24*60*60; //seconds for one month 00620 $publishedDate = $timestamp - $adjustment; 00621 } break; 00622 case 4: 00623 { 00624 $adjustment = 3*31*24*60*60; //seconds for three months 00625 $publishedDate = $timestamp - $adjustment; 00626 } break; 00627 case 5: 00628 { 00629 $adjustment = 365*24*60*60; //seconds for one year 00630 $publishedDate = $timestamp - $adjustment; 00631 } break; 00632 default: 00633 { 00634 $publishedDate = $date->timeStamp(); 00635 } 00636 } 00637 } 00638 $searchDateQuery = "ezsearch_object_word_link.published >= '$publishedDate' AND "; 00639 if ( $publishedDateStop ) 00640 $searchDateQuery .= "ezsearch_object_word_link.published <= '$publishedDateStop' AND "; 00641 $this->GeneralFilter['searchDateQuery'] = $searchDateQuery; 00642 } 00643 00644 $classQuery = ""; 00645 if ( is_numeric( $searchContentClassID ) and $searchContentClassID > 0 ) 00646 { 00647 // Build query for searching in one class 00648 $classQuery = "ezsearch_object_word_link.contentclass_id = '$searchContentClassID' AND "; 00649 $this->GeneralFilter['classAttributeQuery'] = $classQuery; 00650 } 00651 else if ( is_array( $searchContentClassID ) ) 00652 { 00653 // Build query for searching in a number of classes 00654 $classString = $db->generateSQLINStatement( $searchContentClassID, 'ezsearch_object_word_link.contentclass_id', false, false, 'int' ); 00655 $classQuery = "$classString AND "; 00656 $this->GeneralFilter['classAttributeQuery'] = $classQuery; 00657 } 00658 00659 $classAttributeQuery = ""; 00660 if ( is_numeric( $searchContentClassAttributeID ) and $searchContentClassAttributeID > 0 ) 00661 { 00662 $classAttributeQuery = "ezsearch_object_word_link.contentclass_attribute_id = '$searchContentClassAttributeID' AND "; 00663 } 00664 else if ( is_array( $searchContentClassAttributeID ) ) 00665 { 00666 // Build query for searching in a number of attributes 00667 $classAttributeQuery = $db->generateSQLINStatement( $searchContentClassAttributeID , 'ezsearch_object_word_link.contentclass_attribute_id', false, false, 'int' ) . ' AND '; 00668 } 00669 00670 // Get the total number of objects 00671 $totalObjectCount = $this->fetchTotalObjectCount(); 00672 00673 $searchPartsArray = array(); 00674 $wordIDHash = array(); 00675 $wildCardCount = 0; 00676 if ( trim( $searchText ) != '' ) 00677 { 00678 $wordIDArrays = $this->prepareWordIDArrays( $searchText ); 00679 $wordIDArray = $wordIDArrays['wordIDArray']; 00680 $wordIDHash = $wordIDArrays['wordIDHash']; 00681 $wildIDArray = $wordIDArrays['wildIDArray']; 00682 $wildCardCount = $wordIDArrays['wildCardCount']; 00683 $searchPartsArray = $this->buildSearchPartArray( $phraseTextArray, $nonPhraseText, $wordIDHash, $wildIDArray ); 00684 } 00685 00686 /// OR search, not used in this version 00687 $doOrSearch = false; 00688 00689 if ( $doOrSearch == true ) 00690 { 00691 // build fulltext search SQL part 00692 $searchWordArray = $this->splitString( $fullText ); 00693 $fullTextSQL = ""; 00694 if ( count( $searchWordArray ) > 0 ) 00695 { 00696 $i = 0; 00697 // Build the word query string 00698 foreach ( $searchWordArray as $searchWord ) 00699 { 00700 $wordID = null; 00701 if ( isset( $wordIDHash[$searchWord] ) ) 00702 $wordID = $wordIDHash[$searchWord]['id']; 00703 00704 if ( is_numeric( $wordID ) and ( $wordID > 0 ) ) 00705 { 00706 if ( $i == 0 ) 00707 $fullTextSQL .= "ezsearch_object_word_link.word_id='$wordID' "; 00708 else 00709 $fullTextSQL .= " OR ezsearch_object_word_link.word_id='$wordID' "; 00710 } 00711 else 00712 { 00713 $nonExistingWordArray[] = $searchWord; 00714 } 00715 $i++; 00716 } 00717 $fullTextSQL = " ( $fullTextSQL ) AND "; 00718 } 00719 } 00720 00721 // Search only in specific sub trees 00722 $subTreeSQL = ""; 00723 $subTreeTable = ""; 00724 if ( count( $subTreeArray ) > 0 ) 00725 { 00726 // Fetch path_string value to use when searching subtrees 00727 $i = 0; 00728 $doSubTreeSearch = false; 00729 $subTreeNodeSQL = ''; 00730 foreach ( $subTreeArray as $nodeID ) 00731 { 00732 if ( is_numeric( $nodeID ) and ( $nodeID > 0 ) ) 00733 { 00734 $subTreeNodeSQL .= " $nodeID"; 00735 00736 if ( isset( $subTreeArray[$i + 1] ) and 00737 is_numeric( $subTreeArray[$i + 1] ) ) 00738 $subTreeNodeSQL .= ", "; 00739 00740 $doSubTreeSearch = true; 00741 } 00742 $i++; 00743 } 00744 00745 if ( $doSubTreeSearch == true ) 00746 { 00747 00748 $subTreeNodeSQL = "( " . $subTreeNodeSQL; 00749 // $subTreeTable = ", ezcontentobject_tree "; 00750 $subTreeTable = ''; 00751 $subTreeNodeSQL .= " ) "; 00752 $nodeQuery = "SELECT node_id, path_string FROM ezcontentobject_tree WHERE node_id IN $subTreeNodeSQL"; 00753 00754 // Build SQL subtre search query 00755 $subTreeSQL = " ( "; 00756 00757 $nodeArray = $db->arrayQuery( $nodeQuery ); 00758 $i = 0; 00759 foreach ( $nodeArray as $node ) 00760 { 00761 $pathString = $node['path_string']; 00762 $subTreeSQL .= " ezcontentobject_tree.path_string like '$pathString%' "; 00763 00764 if ( $i < ( count( $nodeArray ) -1 ) ) 00765 $subTreeSQL .= " OR "; 00766 $i++; 00767 } 00768 $subTreeSQL .= " ) AND "; 00769 $this->GeneralFilter['subTreeTable'] = $subTreeTable; 00770 $this->GeneralFilter['subTreeSQL'] = $subTreeSQL; 00771 00772 } 00773 } 00774 00775 $limitation = false; 00776 if ( isset( $params['Limitation'] ) ) 00777 { 00778 $limitation = $params['Limitation']; 00779 } 00780 00781 $limitationList = eZContentObjectTreeNode::getLimitationList( $limitation ); 00782 $sqlPermissionChecking = eZContentObjectTreeNode::createPermissionCheckingSQL( $limitationList ); 00783 $this->GeneralFilter['sqlPermissionChecking'] = $sqlPermissionChecking; 00784 00785 $useVersionName = true; 00786 if ( $useVersionName ) 00787 { 00788 $versionNameTables = ' INNER JOIN ezcontentobject_name '; 00789 $versionNameTargets = ', ezcontentobject_name.name as name, ezcontentobject_name.real_translation '; 00790 00791 $versionNameJoins = " and ezcontentobject_tree.contentobject_id = ezcontentobject_name.contentobject_id and 00792 ezcontentobject_tree.contentobject_version = ezcontentobject_name.content_version and "; 00793 $versionNameJoins .= eZContentLanguage::sqlFilter( 'ezcontentobject_name', 'ezcontentobject' ); 00794 } 00795 00796 /// Only support AND search at this time 00797 // build fulltext search SQL part 00798 $searchWordArray = $this->splitString( $fullText ); 00799 $searchWordCount = count( $searchWordArray ); 00800 $fullTextSQL = ""; 00801 $stopWordArray = array( ); 00802 $ini = eZINI::instance(); 00803 00804 $tmpTableCount = 0; 00805 $i = 0; 00806 foreach ( $searchTypes['and'] as $searchType ) 00807 { 00808 $methodName = $this->constructMethodName( $searchType ); 00809 $intermediateResult = $this->callMethod( $methodName, array( $searchType ) ); 00810 if ( $intermediateResult == false ) 00811 { 00812 // cleanup temp tables 00813 $db->dropTempTableList( $sqlPermissionChecking['temp_tables'] ); 00814 00815 return array( "SearchResult" => array(), 00816 "SearchCount" => 0, 00817 "StopWordArray" => array() ); 00818 } 00819 } 00820 00821 // Do not execute search if site.ini:[SearchSettings]->AllowEmptySearch is enabled, but no conditions are set. 00822 if ( !$searchDateQuery && 00823 !$sectionQuery && 00824 !$classQuery && 00825 !$classAttributeQuery && 00826 !$searchPartsArray && 00827 !$subTreeSQL ) 00828 { 00829 // cleanup temp tables 00830 $db->dropTempTableList( $sqlPermissionChecking['temp_tables'] ); 00831 00832 return array( "SearchResult" => array(), 00833 "SearchCount" => 0, 00834 "StopWordArray" => array() ); 00835 } 00836 00837 $i = $this->TempTablesCount; 00838 00839 // Loop every word and insert result in temporary table 00840 00841 // Determine whether we should search invisible nodes. 00842 $showInvisibleNodesCond = eZContentObjectTreeNode::createShowInvisibleSQLString( !$ignoreVisibility ); 00843 00844 foreach ( $searchPartsArray as $searchPart ) 00845 { 00846 $stopWordThresholdValue = 100; 00847 if ( $ini->hasVariable( 'SearchSettings', 'StopWordThresholdValue' ) ) 00848 $stopWordThresholdValue = $ini->variable( 'SearchSettings', 'StopWordThresholdValue' ); 00849 00850 $stopWordThresholdPercent = 60; 00851 if ( $ini->hasVariable( 'SearchSettings', 'StopWordThresholdPercent' ) ) 00852 $stopWordThresholdPercent = $ini->variable( 'SearchSettings', 'StopWordThresholdPercent' ); 00853 00854 $searchThresholdValue = $totalObjectCount; 00855 if ( $totalObjectCount > $stopWordThresholdValue ) 00856 { 00857 $searchThresholdValue = (int)( $totalObjectCount * ( $stopWordThresholdPercent / 100 ) ); 00858 } 00859 00860 // do not search words that are too frequent 00861 if ( $searchPart['object_count'] < $searchThresholdValue ) 00862 { 00863 $tmpTableCount++; 00864 $searchPartText = $searchPart['sql_part']; 00865 if ( $i == 0 ) 00866 { 00867 $table = $db->generateUniqueTempTableName( 'ezsearch_tmp_%', 0 ); 00868 $this->saveCreatedTempTableName( 0, $table ); 00869 $db->createTempTable( "CREATE TEMPORARY TABLE $table ( contentobject_id int primary key not null, published int )" ); 00870 $db->query( "INSERT INTO $table SELECT DISTINCT ezsearch_object_word_link.contentobject_id, ezsearch_object_word_link.published 00871 FROM ezcontentobject 00872 INNER JOIN ezsearch_object_word_link 00873 $subTreeTable 00874 INNER JOIN ezcontentclass 00875 INNER JOIN ezcontentobject_tree 00876 $sqlPermissionChecking[from] 00877 WHERE 00878 $searchDateQuery 00879 $sectionQuery 00880 $classQuery 00881 $classAttributeQuery 00882 $searchPartText 00883 $subTreeSQL 00884 ezcontentobject.id=ezsearch_object_word_link.contentobject_id and 00885 ezcontentobject.contentclass_id = ezcontentclass.id and 00886 ezcontentclass.version = '0' and 00887 ezcontentobject.id = ezcontentobject_tree.contentobject_id and 00888 ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id 00889 $showInvisibleNodesCond 00890 $sqlPermissionChecking[where]", 00891 eZDBInterface::SERVER_SLAVE ); 00892 } 00893 else 00894 { 00895 $table = $db->generateUniqueTempTableName( 'ezsearch_tmp_%', $i ); 00896 $this->saveCreatedTempTableName( $i, $table ); 00897 00898 $tmpTable0 = $this->getSavedTempTableName( 0 ); 00899 $db->createTempTable( "CREATE TEMPORARY TABLE $table ( contentobject_id int primary key not null, published int )" ); 00900 $db->query( "INSERT INTO $table SELECT DISTINCT ezsearch_object_word_link.contentobject_id, ezsearch_object_word_link.published 00901 FROM 00902 ezcontentobject 00903 INNER JOIN ezsearch_object_word_link 00904 $subTreeTable 00905 INNER JOIN ezcontentclass 00906 INNER JOIN ezcontentobject_tree 00907 INNER JOIN $tmpTable0 00908 $sqlPermissionChecking[from] 00909 WHERE 00910 $tmpTable0.contentobject_id=ezsearch_object_word_link.contentobject_id AND 00911 $searchDateQuery 00912 $sectionQuery 00913 $classQuery 00914 $classAttributeQuery 00915 $searchPartText 00916 $subTreeSQL 00917 ezcontentobject.id=ezsearch_object_word_link.contentobject_id and 00918 ezcontentobject.contentclass_id = ezcontentclass.id and 00919 ezcontentclass.version = '0' and 00920 ezcontentobject.id = ezcontentobject_tree.contentobject_id and 00921 ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id 00922 $showInvisibleNodesCond 00923 $sqlPermissionChecking[where]", 00924 eZDBInterface::SERVER_SLAVE ); 00925 } 00926 $i++; 00927 } 00928 else 00929 { 00930 $stopWordArray[] = array( 'word' => $searchPart['text'] ); 00931 } 00932 } 00933 00934 if ( count( $searchPartsArray ) === 0 && $this->TempTablesCount == 0 ) 00935 { 00936 $table = $db->generateUniqueTempTableName( 'ezsearch_tmp_%', 0 ); 00937 $this->saveCreatedTempTableName( 0, $table ); 00938 $db->createTempTable( "CREATE TEMPORARY TABLE $table ( contentobject_id int primary key not null, published int )" ); 00939 $db->query( "INSERT INTO $table SELECT DISTINCT ezsearch_object_word_link.contentobject_id, ezsearch_object_word_link.published 00940 FROM ezcontentobject 00941 INNER JOIN ezsearch_object_word_link 00942 $subTreeTable 00943 INNER JOIN ezcontentclass 00944 INNER JOIN ezcontentobject_tree 00945 $sqlPermissionChecking[from] 00946 WHERE 00947 $searchDateQuery 00948 $sectionQuery 00949 $classQuery 00950 $classAttributeQuery 00951 $subTreeSQL 00952 ezcontentobject.id=ezsearch_object_word_link.contentobject_id and 00953 ezcontentobject.contentclass_id = ezcontentclass.id and 00954 ezcontentclass.version = '0' and 00955 ezcontentobject.id = ezcontentobject_tree.contentobject_id and 00956 ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id 00957 $showInvisibleNodesCond 00958 $sqlPermissionChecking[where]", 00959 eZDBInterface::SERVER_SLAVE ); 00960 $this->TempTablesCount = 1; 00961 $i = $this->TempTablesCount; 00962 } 00963 00964 $nonExistingWordCount = count( array_unique( $searchWordArray ) ) - count( $wordIDHash ) - $wildCardCount; 00965 $excludeWordCount = $searchWordCount - count( $stopWordArray ); 00966 00967 if ( ( count( $stopWordArray ) + $nonExistingWordCount ) == $searchWordCount && $this->TempTablesCount == 0 ) 00968 { 00969 // No words to search for, return empty result 00970 00971 // cleanup temp tables 00972 $db->dropTempTableList( $sqlPermissionChecking['temp_tables'] ); 00973 $db->dropTempTableList( $this->getSavedTempTablesList() ); 00974 00975 return array( "SearchResult" => array(), 00976 "SearchCount" => 0, 00977 "StopWordArray" => $stopWordArray ); 00978 } 00979 $tmpTablesFrom = ""; 00980 $tmpTablesWhere = ""; 00981 /// tmp tables 00982 $tmpTableCount = $i; 00983 for ( $i = 0; $i < $tmpTableCount; $i++ ) 00984 { 00985 $tmpTablesFrom .= $this->getSavedTempTableName( $i ); 00986 if ( $i < ( $tmpTableCount - 1 ) ) 00987 $tmpTablesFrom .= " INNER JOIN "; 00988 00989 } 00990 $tmpTablesSeparator = ''; 00991 if ( $tmpTableCount > 0 ) 00992 { 00993 $tmpTablesSeparator = ' INNER JOIN '; 00994 } 00995 00996 $tmpTable0 = $this->getSavedTempTableName( 0 ); 00997 for ( $i = 1; $i < $tmpTableCount; $i++ ) 00998 { 00999 $tmpTableI = $this->getSavedTempTableName( $i ); 01000 $tmpTablesWhere .= " $tmpTable0.contentobject_id=$tmpTableI.contentobject_id "; 01001 if ( $i < ( $tmpTableCount - 1 ) ) 01002 $tmpTablesWhere .= " AND "; 01003 } 01004 $tmpTablesWhereExtra = ''; 01005 if ( $tmpTableCount > 0 ) 01006 { 01007 $tmpTablesWhereExtra = "ezcontentobject.id=$tmpTable0.contentobject_id AND"; 01008 } 01009 01010 $and = ""; 01011 if ( $tmpTableCount > 1 ) 01012 $and = " AND "; 01013 01014 // Generate ORDER BY SQL 01015 $orderBySQLArray = $this->buildSortSQL( $sortArray ); 01016 $orderByFieldsSQL = $orderBySQLArray['sortingFields']; 01017 $sortWhereSQL = $orderBySQLArray['whereSQL']; 01018 $sortFromSQL = $orderBySQLArray['fromSQL']; 01019 01020 // Fetch data from table 01021 $searchQuery =''; 01022 $dbName = $db->databaseName(); 01023 if ( $dbName == 'mysql' ) 01024 { 01025 $searchQuery = "SELECT DISTINCT ezcontentobject.*, ezcontentclass.serialized_name_list as class_serialized_name_list, ezcontentobject_tree.* 01026 $versionNameTargets 01027 FROM 01028 $tmpTablesFrom $tmpTablesSeparator 01029 ezcontentobject 01030 INNER JOIN ezcontentclass 01031 INNER JOIN ezcontentobject_tree 01032 $versionNameTables 01033 $sortFromSQL 01034 WHERE 01035 $tmpTablesWhere $and 01036 $tmpTablesWhereExtra 01037 ezcontentobject.contentclass_id = ezcontentclass.id and 01038 ezcontentclass.version = '0' and 01039 ezcontentobject.id = ezcontentobject_tree.contentobject_id and 01040 ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id 01041 $versionNameJoins 01042 $showInvisibleNodesCond 01043 $sortWhereSQL 01044 ORDER BY $orderByFieldsSQL"; 01045 } 01046 else 01047 { 01048 $searchQuery = "SELECT DISTINCT ezcontentobject.*, ezcontentclass.serialized_name_list as class_serialized_name_list, ezcontentobject_tree.* 01049 $versionNameTargets 01050 FROM 01051 $tmpTablesFrom $tmpTablesSeparator 01052 ezcontentobject 01053 INNER JOIN ezcontentclass 01054 INNER JOIN ezcontentobject_tree 01055 $versionNameTables 01056 WHERE 01057 $tmpTablesWhere $and 01058 $tmpTablesWhereExtra 01059 ezcontentobject.contentclass_id = ezcontentclass.id and 01060 ezcontentclass.version = '0' and 01061 ezcontentobject.id = ezcontentobject_tree.contentobject_id and 01062 ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id 01063 $versionNameJoins 01064 "; 01065 } 01066 // Count query 01067 $languageCond = eZContentLanguage::languagesSQLFilter( 'ezcontentobject' ); 01068 if ( $tmpTableCount == 0 ) 01069 { 01070 $searchCountQuery = "SELECT count( DISTINCT ezcontentobject.id ) AS count 01071 FROM 01072 ezcontentobject, 01073 ezcontentobject_tree 01074 WHERE 01075 ezcontentobject.id = ezcontentobject_tree.contentobject_id and 01076 ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id 01077 AND $languageCond 01078 $showInvisibleNodesCond"; 01079 } 01080 else 01081 { 01082 $searchCountQuery = "SELECT count( DISTINCT ezcontentobject.id ) AS count 01083 FROM $tmpTablesFrom $tmpTablesSeparator 01084 ezcontentobject 01085 WHERE $tmpTablesWhere $and 01086 $tmpTablesWhereExtra 01087 $languageCond"; 01088 } 01089 01090 $objectRes = array(); 01091 $searchCount = 0; 01092 01093 if ( $nonExistingWordCount <= 0 ) 01094 { 01095 // execute search query 01096 $objectResArray = $db->arrayQuery( $searchQuery, array( "limit" => $searchLimit, "offset" => $searchOffset ), eZDBInterface::SERVER_SLAVE ); 01097 // execute search count query 01098 $objectCountRes = $db->arrayQuery( $searchCountQuery, array(), eZDBInterface::SERVER_SLAVE ); 01099 $objectRes = eZContentObjectTreeNode::makeObjectsArray( $objectResArray ); 01100 $searchCount = $objectCountRes[0]['count']; 01101 } 01102 else 01103 $objectRes = array(); 01104 01105 // Drop tmp tables 01106 $db->dropTempTableList( $sqlPermissionChecking['temp_tables'] ); 01107 $db->dropTempTableList( $this->getSavedTempTablesList() ); 01108 01109 return array( "SearchResult" => $objectRes, 01110 "SearchCount" => $searchCount, 01111 "StopWordArray" => $stopWordArray ); 01112 } 01113 else 01114 { 01115 return array( "SearchResult" => array(), 01116 "SearchCount" => 0, 01117 "StopWordArray" => array() ); 01118 } 01119 } 01120 01121 /*! 01122 \private 01123 \return an array of ORDER BY SQL 01124 */ 01125 function buildSortSQL( $sortArray ) 01126 { 01127 $sortCount = 0; 01128 $sortList = false; 01129 if ( isset( $sortArray ) and 01130 is_array( $sortArray ) and 01131 count( $sortArray ) > 0 ) 01132 { 01133 $sortList = $sortArray; 01134 if ( count( $sortList ) > 1 and 01135 !is_array( $sortList[0] ) ) 01136 { 01137 $sortList = array( $sortList ); 01138 } 01139 } 01140 $attributeJoinCount = 0; 01141 $attributeFromSQL = ""; 01142 $attributeWereSQL = ""; 01143 if ( $sortList !== false ) 01144 { 01145 $sortingFields = ''; 01146 foreach ( $sortList as $sortBy ) 01147 { 01148 if ( is_array( $sortBy ) and count( $sortBy ) > 0 ) 01149 { 01150 if ( $sortCount > 0 ) 01151 $sortingFields .= ', '; 01152 $sortField = $sortBy[0]; 01153 switch ( $sortField ) 01154 { 01155 case 'path': 01156 { 01157 $sortingFields .= 'path_string'; 01158 } break; 01159 case 'published': 01160 { 01161 $sortingFields .= 'ezcontentobject.published'; 01162 } break; 01163 case 'modified': 01164 { 01165 $sortingFields .= 'ezcontentobject.modified'; 01166 } break; 01167 case 'section': 01168 { 01169 $sortingFields .= 'ezcontentobject.section_id'; 01170 } break; 01171 case 'depth': 01172 { 01173 $sortingFields .= 'depth'; 01174 } break; 01175 case 'class_identifier': 01176 { 01177 $sortingFields .= 'ezcontentclass.identifier'; 01178 } break; 01179 case 'class_name': 01180 { 01181 $classNameFilter = eZContentClassName::sqlFilter(); 01182 $sortingFields .= $classNameFilter['nameField']; 01183 $attributeFromSQL .= " INNER JOIN $classNameFilter[from]"; 01184 $attributeWhereSQL .= "$classNameFilter[where] AND "; 01185 } break; 01186 case 'priority': 01187 { 01188 $sortingFields .= 'ezcontentobject_tree.priority'; 01189 } break; 01190 case 'name': 01191 { 01192 $sortingFields .= 'ezcontentobject_name.name'; 01193 } break; 01194 case 'attribute': 01195 { 01196 $sortClassID = $sortBy[2]; 01197 // Look up datatype for sorting 01198 if ( !is_numeric( $sortClassID ) ) 01199 { 01200 $sortClassID = eZContentObjectTreeNode::classAttributeIDByIdentifier( $sortClassID ); 01201 } 01202 01203 $sortDataType = $sortClassID === false ? false : eZContentObjectTreeNode::sortKeyByClassAttributeID( $sortClassID ); 01204 01205 $sortKey = false; 01206 if ( $sortDataType == 'string' ) 01207 { 01208 $sortKey = 'sort_key_string'; 01209 } 01210 else 01211 { 01212 $sortKey = 'sort_key_int'; 01213 } 01214 01215 $sortingFields .= "a$attributeJoinCount.$sortKey"; 01216 $attributeFromSQL .= " INNER JOIN ezcontentobject_attribute as a$attributeJoinCount"; 01217 $attributeWereSQL .= " AND a$attributeJoinCount.contentobject_id = ezcontentobject.id AND 01218 a$attributeJoinCount.contentclassattribute_id = $sortClassID AND 01219 a$attributeJoinCount.version = ezcontentobject_name.content_version"; 01220 01221 $attributeJoinCount++; 01222 }break; 01223 01224 default: 01225 { 01226 eZDebug::writeWarning( 'Unknown sort field: ' . $sortField, __METHOD__ ); 01227 continue; 01228 } 01229 } 01230 $sortOrder = true; // true is ascending 01231 if ( isset( $sortBy[1] ) ) 01232 $sortOrder = $sortBy[1]; 01233 $sortingFields .= $sortOrder ? " ASC" : " DESC"; 01234 ++$sortCount; 01235 } 01236 } 01237 } 01238 01239 // Should we sort? 01240 if ( $sortCount == 0 ) 01241 { 01242 $sortingFields = " ezcontentobject.published ASC"; 01243 } 01244 01245 return array( 'sortingFields' => $sortingFields, 01246 'fromSQL' => $attributeFromSQL, 01247 'whereSQL' => $attributeWereSQL ); 01248 } 01249 01250 /*! 01251 \private 01252 \return Returns an sql query part for one word 01253 */ 01254 function buildSqlPartForWord( $wordID, $identifier = false ) 01255 { 01256 $fullTextSQL = "ezsearch_object_word_link.word_id='$wordID' AND "; 01257 if ( $identifier ) 01258 { 01259 $fullTextSQL .= "ezsearch_object_word_link.identifier='$identifier' AND "; 01260 } 01261 01262 return $fullTextSQL; 01263 } 01264 01265 /*! 01266 \private 01267 \return Returns an sql query part for a phrase 01268 */ 01269 01270 function buildPhraseSqlQueryPart( $phraseIDArray, $identifier = false ) 01271 { 01272 $phraseSearchSQLArray = array(); 01273 $wordCount = count( $phraseIDArray ); 01274 for ( $i = 0; $i < $wordCount; $i++ ) 01275 { 01276 $wordID = $phraseIDArray[$i]; 01277 $phraseSearchSQL = ""; 01278 if ( is_numeric( $wordID ) and ( $wordID > 0 ) ) 01279 { 01280 $phraseSearchSQL = " ( ezsearch_object_word_link.word_id='$wordID' "; 01281 if ( $i < ( $wordCount - 1 ) ) 01282 { 01283 $nextWordID = $phraseIDArray[$i+1]; 01284 $phraseSearchSQL .= " AND ezsearch_object_word_link.next_word_id='$nextWordID' "; 01285 } 01286 if ( $i > 0 ) 01287 { 01288 $prevWordID = $phraseIDArray[$i-1]; 01289 $phraseSearchSQL .= " AND ezsearch_object_word_link.prev_word_id='$prevWordID' "; 01290 } 01291 if ( $identifier ) 01292 { 01293 $phraseSearchSQL .= " AND ezsearch_object_word_link.identifier='$identifier' "; 01294 } 01295 $phraseSearchSQL .= " ) "; 01296 } 01297 else 01298 { 01299 $nonExistingWordArray[] = $searchWord; 01300 } 01301 $prevWord = $wordID; 01302 $phraseSearchSQLArray[] = $phraseSearchSQL; 01303 } 01304 01305 return $phraseSearchSQLArray; 01306 } 01307 01308 /*! 01309 \private 01310 \return Returns an array of words created from the input string. 01311 */ 01312 function splitString( $text ) 01313 { 01314 // strip quotes 01315 $text = preg_replace("#'#", "", $text ); 01316 $text = preg_replace( "#\"#", "", $text ); 01317 01318 // Strip multiple whitespace 01319 $text = trim( $text ); 01320 $text = preg_replace("(\s+)", " ", $text ); 01321 01322 // Split text on whitespace 01323 $wordArray = explode( ' ', $text ); 01324 01325 $retArray = array(); 01326 foreach ( $wordArray as $word ) 01327 { 01328 if ( trim( $word ) != "" ) 01329 { 01330 $retArray[] = trim( $word ); 01331 } 01332 } 01333 01334 return $retArray; 01335 } 01336 01337 /*! 01338 Normalizes the text \a $text so that it is easily parsable 01339 \param $isMetaData If \c true then it expects the text to be meta data from objects, 01340 if not it is the search text and needs special handling. 01341 */ 01342 function normalizeText( $text, $isMetaData = false ) 01343 { 01344 $trans = eZCharTransform::instance(); 01345 $text = $trans->transformByGroup( $text, 'search' ); 01346 01347 // Remove quotes and asterix when not handling search text by end-user 01348 if ( $isMetaData ) 01349 { 01350 $text = str_replace( array( "\"", "*" ), array( " ", " " ), $text ); 01351 } 01352 01353 return $text; 01354 } 01355 01356 /*! 01357 \static 01358 \return Returns an array describing the supported search types in thie search engine. 01359 \note It has been renamed. In eZ Publish 3.4 and older it was (wrongly) named suportedSearchTypes(). 01360 */ 01361 public function supportedSearchTypes() 01362 { 01363 $searchTypes = array( array( 'type' => 'attribute', 01364 'subtype' => 'fulltext', 01365 'params' => array( 'classattribute_id', 'value' ) ), 01366 array( 'type' => 'attribute', 01367 'subtype' => 'patterntext', 01368 'params' => array( 'classattribute_id', 'value' ) ), 01369 array( 'type' => 'attribute', 01370 'subtype' => 'integer', 01371 'params' => array( 'classattribute_id', 'value' ) ), 01372 array( 'type' => 'attribute', 01373 'subtype' => 'integers', 01374 'params' => array( 'classattribute_id', 'values' ) ), 01375 array( 'type' => 'attribute', 01376 'subtype' => 'byrange', 01377 'params' => array( 'classattribute_id', 'from' , 'to' ) ), 01378 array( 'type' => 'attribute', 01379 'subtype' => 'byidentifier', 01380 'params' => array( 'classattribute_id', 'identifier', 'value' ) ), 01381 array( 'type' => 'attribute', 01382 'subtype' => 'byidentifierrange', 01383 'params' => array( 'classattribute_id', 'identifier', 'from', 'to' ) ), 01384 array( 'type' => 'attribute', 01385 'subtype' => 'integersbyidentifier', 01386 'params' => array( 'classattribute_id', 'identifier', 'values' ) ), 01387 array( 'type' => 'fulltext', 01388 'subtype' => 'text', 01389 'params' => array( 'value' ) ) ); 01390 $generalSearchFilter = array( array( 'type' => 'general', 01391 'subtype' => 'class', 01392 'params' => array( array( 'type' => 'array', 01393 'value' => 'value'), 01394 'operator' ) ), 01395 array( 'type' => 'general', 01396 'subtype' => 'publishdate', 01397 'params' => array( 'value', 'operator' ) ), 01398 array( 'type' => 'general', 01399 'subtype' => 'subtree', 01400 'params' => array( array( 'type' => 'array', 01401 'value' => 'value'), 01402 'operator' ) ) ); 01403 return array( 'types' => $searchTypes, 01404 'general_filter' => $generalSearchFilter ); 01405 } 01406 01407 function searchAttributeInteger( $searchParams ) 01408 { 01409 $classAttributeID = $searchParams['classattribute_id']; 01410 $value = $searchParams['value']; 01411 01412 $classAttributeQuery = ""; 01413 if ( is_numeric( $classAttributeID ) and $classAttributeID > 0 ) 01414 { 01415 $classAttributeQuery = "ezsearch_object_word_link.contentclass_attribute_id = '$classAttributeID' AND "; 01416 } 01417 01418 $searchPartSql = " ezsearch_object_word_link.integer_value = $value AND"; 01419 01420 $searchPartText = $classAttributeQuery . $searchPartSql; 01421 $tableResult = $this->createTemporaryTable( $searchPartText ); 01422 01423 if ( $tableResult === false ) 01424 { 01425 return false; 01426 } 01427 else 01428 { 01429 return true; 01430 } 01431 } 01432 01433 function searchAttributeIntegers( $searchParams ) 01434 { 01435 $classAttributeID = $searchParams['classattribute_id']; 01436 $values = $searchParams['values']; 01437 01438 $classAttributeQuery = ""; 01439 if ( is_numeric( $classAttributeID ) and $classAttributeID > 0 ) 01440 { 01441 $classAttributeQuery = "ezsearch_object_word_link.contentclass_attribute_id = '$classAttributeID' AND "; 01442 } 01443 01444 $integerValuesSql = implode( ', ', $values ); 01445 $searchPartSql = " ezsearch_object_word_link.integer_value IN ( $integerValuesSql ) AND"; 01446 01447 $searchPartText = $classAttributeQuery . $searchPartSql; 01448 $tableResult = $this->createTemporaryTable( $searchPartText ); 01449 01450 if ( $tableResult === false ) 01451 { 01452 return false; 01453 } 01454 else 01455 { 01456 return true; 01457 } 01458 } 01459 01460 function searchAttributeByRange( $searchParams ) 01461 { 01462 $classAttributeID = $searchParams['classattribute_id']; 01463 $fromValue = $searchParams['from']; 01464 $toValue = $searchParams['to']; 01465 01466 $classAttributeQuery = ""; 01467 if ( is_numeric( $classAttributeID ) and $classAttributeID > 0 ) 01468 { 01469 $classAttributeQuery = "ezsearch_object_word_link.contentclass_attribute_id = '$classAttributeID' AND "; 01470 } 01471 01472 $searchPartSql = " ezsearch_object_word_link.integer_value BETWEEN $fromValue AND $toValue AND"; 01473 $searchPartText = $classAttributeQuery . $searchPartSql; 01474 $tableResult = $this->createTemporaryTable( $searchPartText ); 01475 01476 if ( $tableResult === false ) 01477 { 01478 return false; 01479 } 01480 else 01481 { 01482 return true; 01483 } 01484 01485 } 01486 01487 function searchAttributeByIdentifier( $searchParams ) 01488 { 01489 $identifier = $searchParams['identifier']; 01490 $textValue = $searchParams['value']; 01491 01492 $searchText = $this->normalizeText( $textValue, false ); 01493 01494 $phrasesResult = $this->getPhrases( $searchText ); 01495 $phraseTextArray = $phrasesResult['phrases']; 01496 $nonPhraseText = $phrasesResult['nonPhraseText']; 01497 $fullText = $phrasesResult['fullText']; 01498 01499 $totalObjectCount = $this->fetchTotalObjectCount(); 01500 01501 $wordIDArrays = $this->prepareWordIDArrays( $searchText ); 01502 $wordIDArray = $wordIDArrays['wordIDArray']; 01503 $wordIDHash = $wordIDArrays['wordIDHash']; 01504 $wildIDArray = $wordIDArrays['wildIDArray']; 01505 01506 $searchWordArray = $this->splitString( $searchText ); 01507 01508 $nonExistingWordCount = count( $searchWordArray ) - count( $wordIDHash ); 01509 if ( $nonExistingWordCount > 0 ) 01510 return false; 01511 01512 $searchPartsArray = $this->buildSearchPartArray( $phraseTextArray, $nonPhraseText, $wordIDHash, 01513 $wildIDArray, $identifier ); 01514 $this->buildTempTablesForFullTextSearch( $searchPartsArray, array() ); 01515 $this->GeneralFilter['classAttributeQuery'] = ''; 01516 return true; 01517 } 01518 01519 function searchAttributeByIdentifierRange( $searchParams ) 01520 { 01521 $identifier = $searchParams['identifier']; 01522 $fromValue = $searchParams['from']; 01523 $toValue = $searchParams['to']; 01524 01525 $searchPartSql = " ezsearch_object_word_link.integer_value BETWEEN $fromValue AND $toValue AND ezsearch_object_word_link.identifier = '$identifier' AND"; 01526 $tableResult = $this->createTemporaryTable( $searchPartSql ); 01527 01528 if ( $tableResult === false ) 01529 { 01530 return false; 01531 } 01532 else 01533 { 01534 return true; 01535 } 01536 } 01537 01538 function searchAttributeIntegersByIdentifier( $searchParams ) 01539 { 01540 $identifier = $searchParams['identifier']; 01541 $values = $searchParams['values']; 01542 01543 $integerValuesSql = implode( ', ', $values ); 01544 $searchPartSql = " ezsearch_object_word_link.integer_value IN ( $integerValuesSql ) AND ezsearch_object_word_link.identifier = '$identifier' AND"; 01545 $tableResult = $this->createTemporaryTable( $searchPartSql ); 01546 01547 if ( $tableResult === false ) 01548 { 01549 return false; 01550 } 01551 else 01552 { 01553 return true; 01554 } 01555 } 01556 01557 function searchAttributePatternText( $searchParams ) 01558 { 01559 $classAttributeID = $searchParams['classattribute_id']; 01560 $textValue = $searchParams['value']; 01561 01562 // $searchText = $this->normalizeText( $textValue ); 01563 $searchText = $textValue; 01564 01565 $classAttributeQuery = ""; 01566 if ( is_numeric( $classAttributeID ) and $classAttributeID > 0 ) 01567 { 01568 $classAttributeQuery = "ezsearch_object_word_link.contentclass_attribute_id = '$classAttributeID' AND "; 01569 $this->GeneralFilter['classAttributeQuery'] = $classAttributeQuery; 01570 } 01571 01572 $wordIDArrays = $this->prepareWordIDArraysForPattern( $searchText ); 01573 $wordIDArray = $wordIDArrays['wordIDArray']; 01574 $wordIDHash = $wordIDArrays['wordIDHash']; 01575 $wildIDArray = array(); 01576 $patternWordIDHash = $wordIDArrays['patternWordIDHash']; 01577 01578 $searchWordArray = $this->splitString( $searchText ); 01579 01580 $nonExistingWordCount = count( $searchWordArray ) - count( $wordIDHash ) - count( $patternWordIDHash ); 01581 if ( $nonExistingWordCount > 0 ) 01582 return false; 01583 01584 preg_replace( "/(\w+\*\s)/", " ", $searchText ); 01585 $nonPhraseText = $this->normalizeText( $searchText, false ); 01586 01587 $searchPartsArray = $this->buildSearchPartArrayForWords( $nonPhraseText, $wordIDHash, $wildIDArray ); 01588 01589 foreach ( $patternWordIDHash as $patternWord ) 01590 { 01591 $searchPart = '( '; 01592 $i = 0; 01593 foreach ( $patternWord as $word ) 01594 { 01595 if ( $i > 0 ) 01596 $searchPart .= ' or '; 01597 $wordID = $word['id']; 01598 $searchPart .= "ezsearch_object_word_link.word_id='$wordID' "; 01599 01600 $i++; 01601 } 01602 $searchPart .= ' ) AND '; 01603 $this->createTemporaryTable( $searchPart ); 01604 } 01605 01606 $this->buildTempTablesForFullTextSearch( $searchPartsArray, array() ); 01607 $this->GeneralFilter['classAttributeQuery'] = ''; 01608 return true; 01609 } 01610 01611 function searchAttributeFulltext( $searchParams ) 01612 { 01613 $classAttributeID = $searchParams['classattribute_id']; 01614 $textValue = $searchParams['value']; 01615 01616 $searchText = $this->normalizeText( $textValue, false ); 01617 01618 $phrasesResult = $this->getPhrases( $searchText ); 01619 $phraseTextArray = $phrasesResult['phrases']; 01620 $nonPhraseText = $phrasesResult['nonPhraseText']; 01621 $fullText = $phrasesResult['fullText']; 01622 01623 01624 $classAttributeQuery = ""; 01625 if ( is_numeric( $classAttributeID ) and $classAttributeID > 0 ) 01626 { 01627 $classAttributeQuery = "ezsearch_object_word_link.contentclass_attribute_id = '$classAttributeID' AND "; 01628 $this->GeneralFilter['classAttributeQuery'] = $classAttributeQuery; 01629 } 01630 01631 $totalObjectCount = $this->fetchTotalObjectCount(); 01632 01633 $wordIDArrays = $this->prepareWordIDArrays( $searchText ); 01634 $wordIDArray = $wordIDArrays['wordIDArray']; 01635 $wordIDHash = $wordIDArrays['wordIDHash']; 01636 $wildIDArray = $wordIDArrays['wildIDArray']; 01637 01638 $searchWordArray = $this->splitString( $searchText ); 01639 01640 $nonExistingWordCount = count( $searchWordArray ) - count( $wordIDHash ); 01641 if ( $nonExistingWordCount > 0 ) 01642 return false; 01643 $searchPartsArray = $this->buildSearchPartArray( $phraseTextArray, $nonPhraseText, 01644 $wordIDHash, $wildIDArray ); 01645 01646 $this->buildTempTablesForFullTextSearch( $searchPartsArray, array() ); 01647 $this->GeneralFilter['classAttributeQuery'] = ''; 01648 return true; 01649 } 01650 01651 function createTemporaryTable( $searchPartText ) 01652 { 01653 01654 $subTreeTable = $this->GeneralFilter['subTreeTable']; 01655 $searchDateQuery = $this->GeneralFilter['searchDateQuery']; 01656 $sectionQuery = $this->GeneralFilter['sectionQuery']; 01657 $classQuery = $this->GeneralFilter['classQuery']; 01658 $classAttributeQuery = $this->GeneralFilter['classAttributeQuery']; 01659 // $searchPartText = $this->GeneralFilter['searchPartText']; 01660 $subTreeSQL = $this->GeneralFilter['subTreeSQL']; 01661 $sqlPermissionChecking = $this->GeneralFilter['sqlPermissionChecking']; 01662 $db = eZDB::instance(); 01663 $i = $this->TempTablesCount; 01664 if ( $i == 0 ) 01665 { 01666 $table = $db->generateUniqueTempTableName( 'ezsearch_tmp_%', 0 ); 01667 $this->saveCreatedTempTableName( 0, $table ); 01668 $db->createTempTable( "CREATE TEMPORARY TABLE $table ( contentobject_id int primary key not null, published int )" ); 01669 $db->query( "INSERT INTO $table SELECT DISTINCT ezsearch_object_word_link.contentobject_id, ezsearch_object_word_link.published 01670 FROM 01671 ezcontentobject 01672 INNER JOIN ezsearch_object_word_link 01673 $subTreeTable 01674 INNER JOIN ezcontentclass 01675 INNER JOIN ezcontentobject_tree 01676 $sqlPermissionChecking[from] 01677 WHERE 01678 $searchDateQuery 01679 $sectionQuery 01680 $classQuery 01681 $classAttributeQuery 01682 $searchPartText 01683 $subTreeSQL 01684 ezcontentobject.id=ezsearch_object_word_link.contentobject_id and 01685 ezcontentobject.contentclass_id = ezcontentclass.id and 01686 ezcontentclass.version = '0' and 01687 ezcontentobject.id = ezcontentobject_tree.contentobject_id and 01688 ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id 01689 $sqlPermissionChecking[where]", 01690 eZDBInterface::SERVER_SLAVE ); 01691 } 01692 else 01693 { 01694 $table = $db->generateUniqueTempTableName( 'ezsearch_tmp_%_0', $i ); 01695 $this->saveCreatedTempTableName( $i, $table ); 01696 $tmpTable0 = $this->getSavedTempTableName( 0 ); 01697 $db->createTempTable( "CREATE TEMPORARY TABLE $table ( contentobject_id int primary key not null, published int )" ); 01698 $db->query( "INSERT INTO $table SELECT DISTINCT ezsearch_object_word_link.contentobject_id, ezsearch_object_word_link.published 01699 FROM 01700 ezcontentobject 01701 INNER JOIN ezsearch_object_word_link 01702 $subTreeTable 01703 INNER JOIN ezcontentclass 01704 INNER JOIN ezcontentobject_tree 01705 INNER JOIN $tmpTable0 01706 $sqlPermissionChecking[from] 01707 WHERE 01708 $tmpTable0.contentobject_id=ezsearch_object_word_link.contentobject_id AND 01709 $searchDateQuery 01710 $sectionQuery 01711 $classQuery 01712 $classAttributeQuery 01713 $searchPartText 01714 $subTreeSQL 01715 ezcontentobject.id=ezsearch_object_word_link.contentobject_id and 01716 ezcontentobject.contentclass_id = ezcontentclass.id and 01717 ezcontentclass.version = '0' and 01718 ezcontentobject.id = ezcontentobject_tree.contentobject_id and 01719 ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id 01720 $sqlPermissionChecking[where]", 01721 eZDBInterface::SERVER_SLAVE ); 01722 } 01723 01724 $tmpTableI = $this->getSavedTempTableName( $i ); 01725 $insertedCountArray = $db->arrayQuery( "SELECT count(*) as count from $tmpTableI " ); 01726 $i++; 01727 $this->TempTablesCount++; 01728 if ( $insertedCountArray[0]['count'] == 0 ) 01729 { 01730 return false; 01731 } 01732 else 01733 { 01734 return $insertedCountArray[0]['count']; 01735 } 01736 } 01737 01738 function buildTempTablesForFullTextSearch( $searchPartsArray, $generalFilterList = array() ) 01739 { 01740 $ini = eZINI::instance(); 01741 $db = eZDB::instance(); 01742 01743 $i = $this->TempTablesCount; 01744 $generalFilterList = $this->GeneralFilter; 01745 01746 if ( isset( $generalFilterList['searchDateQuery'] ) and 01747 isset( $generalFilterList['publish_date'] ) ) 01748 $searchDateQuery = $generalFilterList['publish_date']; 01749 else 01750 $searchDateQuery = ''; 01751 01752 if ( isset( $generalFilterList['sectionQuery'] ) ) 01753 $sectionQuery = $generalFilterList['sectionQuery']; 01754 else 01755 $sectionQuery = ''; 01756 01757 if ( isset( $generalFilterList['classQuery'] ) ) 01758 $classQuery = $generalFilterList['classQuery']; 01759 else 01760 $classQuery = ''; 01761 01762 if ( isset( $generalFilterList['classAttributeQuery'] ) ) 01763 $classAttributeQuery = $generalFilterList[ 'classAttributeQuery']; 01764 else 01765 $classAttributeQuery = ''; 01766 01767 if ( isset( $generalFilterList['sqlPermissionChecking'] ) ) 01768 $sqlPermissionChecking = $generalFilterList['sqlPermissionChecking']; 01769 else 01770 $sqlPermissionChecking = array( 'from' => '', 01771 'where' => '' ); 01772 01773 if ( isset( $generalFilterList['subTreeSQL'] ) ) 01774 { 01775 $subTreeTable = $generalFilterList['subTreeTable']; 01776 $subTreeSQL = $generalFilterList['subTreeSQL']; 01777 } 01778 else 01779 { 01780 $subTreeTable = ''; 01781 $subTreeSQL = ''; 01782 } 01783 01784 $totalObjectCount = $this->fetchTotalObjectCount(); 01785 01786 $stopWordThresholdValue = 100; 01787 if ( $ini->hasVariable( 'SearchSettings', 'StopWordThresholdValue' ) ) 01788 $stopWordThresholdValue = $ini->variable( 'SearchSettings', 'StopWordThresholdValue' ); 01789 01790 $stopWordThresholdPercent = 60; 01791 if ( $ini->hasVariable( 'SearchSettings', 'StopWordThresholdPercent' ) ) 01792 $stopWordThresholdPercent = $ini->variable( 'SearchSettings', 'StopWordThresholdPercent' ); 01793 01794 $searchThresholdValue = $totalObjectCount; 01795 if ( $totalObjectCount > $stopWordThresholdValue ) 01796 { 01797 $searchThresholdValue = (int)( $totalObjectCount * ( $stopWordThresholdPercent / 100 ) ); 01798 } 01799 01800 foreach ( $searchPartsArray as $searchPart ) 01801 { 01802 // $wordID = $searchWord['id']; 01803 01804 01805 // do not search words that are too frequent 01806 if ( $searchPart['object_count'] < $searchThresholdValue ) 01807 { 01808 // $tmpTableCount++; 01809 $searchPartText = $searchPart['sql_part']; 01810 if ( $i == 0 ) 01811 { 01812 $table = $db->generateUniqueTempTableName( 'ezsearch_tmp_%', 0 ); 01813 $this->saveCreatedTempTableName( 0, $table ); 01814 $db->createTempTable( "CREATE TEMPORARY TABLE $table ( contentobject_id int primary key not null, published int )" ); 01815 $db->query( "INSERT INTO $table SELECT DISTINCT ezsearch_object_word_link.contentobject_id, ezsearch_object_word_link.published 01816 FROM 01817 ezcontentobject 01818 INNER JOIN ezsearch_object_word_link 01819 $subTreeTable 01820 INNER JOIN ezcontentclass 01821 INNER JOIN ezcontentobject_tree 01822 $sqlPermissionChecking[from] 01823 WHERE 01824 $searchDateQuery 01825 $sectionQuery 01826 $classQuery 01827 $classAttributeQuery 01828 $searchPartText 01829 $subTreeSQL 01830 ezcontentobject.id=ezsearch_object_word_link.contentobject_id and 01831 ezcontentobject.contentclass_id = ezcontentclass.id and 01832 ezcontentclass.version = '0' and 01833 ezcontentobject.id = ezcontentobject_tree.contentobject_id and 01834 ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id 01835 $sqlPermissionChecking[where]", 01836 eZDBInterface::SERVER_SLAVE ); 01837 } 01838 else 01839 { 01840 $table = $db->generateUniqueTempTableName( 'ezsearch_tmp_%', $i ); 01841 $this->saveCreatedTempTableName( $i, $table ); 01842 $tmpTable0 = $this->getSavedTempTableName( 0 ); 01843 $db->createTempTable( "CREATE TEMPORARY TABLE $table ( contentobject_id int primary key not null, published int )" ); 01844 $db->query( "INSERT INTO $table SELECT DISTINCT ezsearch_object_word_link.contentobject_id, ezsearch_object_word_link.published 01845 FROM 01846 ezcontentobject 01847 INNER JOIN ezsearch_object_word_link 01848 $subTreeTable 01849 INNER JOIN ezcontentclass 01850 INNER JOIN ezcontentobject_tree 01851 INNER JOIN $tmpTable0 01852 $sqlPermissionChecking[from] 01853 WHERE 01854 $tmpTable0.contentobject_id=ezsearch_object_word_link.contentobject_id AND 01855 $searchDateQuery 01856 $sectionQuery 01857 $classQuery 01858 $classAttributeQuery 01859 $searchPartText 01860 $subTreeSQL 01861 ezcontentobject.id=ezsearch_object_word_link.contentobject_id and 01862 ezcontentobject.contentclass_id = ezcontentclass.id and 01863 ezcontentclass.version = '0' and 01864 ezcontentobject.id = ezcontentobject_tree.contentobject_id and 01865 ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id 01866 $sqlPermissionChecking[where]", 01867 eZDBInterface::SERVER_SLAVE ); 01868 } 01869 $i++; 01870 } 01871 else 01872 { 01873 $stopWordArray[] = array( 'word' => $searchPart['word'] ); 01874 } 01875 } 01876 $this->TempTablesCount = $i; 01877 } 01878 01879 01880 function getPhrases( $searchText ) 01881 { 01882 $numQuotes = substr_count( $searchText, "\"" ); 01883 01884 $phraseTextArray = array(); 01885 $fullText = $searchText; 01886 $nonPhraseText =''; 01887 01888 $postPhraseText = $fullText; 01889 $pos = 0; 01890 if ( ( $numQuotes > 0 ) and ( ( $numQuotes % 2 ) == 0 ) ) 01891 { 01892 for ( $i = 0; $i < ( $numQuotes / 2 ); $i ++ ) 01893 { 01894 $quotePosStart = strpos( $searchText, '"', $pos ); 01895 $quotePosEnd = strpos( $searchText, '"', $quotePosStart + 1 ); 01896 01897 $prePhraseText = substr( $searchText, $pos, $quotePosStart - $pos ); 01898 $postPhraseText = substr( $searchText, $quotePosEnd +1 ); 01899 $phraseText = substr( $searchText, $quotePosStart + 1, $quotePosEnd - $quotePosStart - 1 ); 01900 01901 $phraseTextArray[] = $phraseText; 01902 $nonPhraseText .= $prePhraseText; 01903 $pos = $quotePosEnd + 1; 01904 } 01905 } 01906 $nonPhraseText .= $postPhraseText; 01907 return array( 'phrases' => $phraseTextArray, 01908 'nonPhraseText' => $nonPhraseText, 01909 'fullText' => $fullText ); 01910 } 01911 01912 function buildSearchPartArray( $phraseTextArray, $nonPhraseText, $wordIDHash, $wildIDArray, 01913 $identifier = false ) 01914 { 01915 $searchPartsArrayForPhrases = $this->buildSearchPartArrayForPhrases( $phraseTextArray, $wordIDHash, 01916 $identifier ); 01917 $searchPartsArrayForWords = $this->buildSearchPartArrayForWords( $nonPhraseText, $wordIDHash, 01918 $wildIDArray, $identifier ); 01919 $searchPartsArray = array_merge( $searchPartsArrayForPhrases, $searchPartsArrayForWords ); 01920 return $searchPartsArray; 01921 } 01922 01923 function buildSearchPartArrayForWords( $nonPhraseText, $wordIDHash, $wildIDArray, $identifier = false ) 01924 { 01925 $searchPartsArray = array(); 01926 $nonPhraseWordArray = $this->splitString( $nonPhraseText ); 01927 $uniqueWordArray = array(); 01928 01929 $searchPart = array(); 01930 if ( isset( $wildIDArray ) && count( $wildIDArray ) > 0 ) 01931 { 01932 $searchPart['sql_part'] = '( '; 01933 $i = 0; 01934 $objectCount = -1; 01935 01936 foreach( $wildIDArray as $wordInfo ) 01937 { 01938 01939 if ( $i > 0 ) 01940 $searchPart['sql_part'] .= ' or '; 01941 $searchPart['sql_part'] .= "ezsearch_object_word_link.word_id='". $wordInfo['id'] ."'"; 01942 if ( $objectCount < intval($wordInfo['object_count']) ) 01943 $objectCount = intval($wordInfo['object_count']); 01944 $i++; 01945 } 01946 $searchPart['sql_part'] .= ' ) AND '; 01947 $searchPart['object_count'] = $objectCount; 01948 $searchPart['is_phrase'] = 0; 01949 $searchPartsArray[] = $searchPart; 01950 unset ( $searchPart ); 01951 } 01952 01953 foreach( array_keys( $wordIDHash ) as $word ) 01954 { 01955 $searchPart = array(); 01956 $searchPart['text'] = $word; 01957 $wordID = $wordIDHash[$word]['id']; 01958 $searchPart['sql_part'] = $this->buildSqlPartForWord( $wordID, $identifier ); 01959 $searchPart['is_phrase'] = 0; 01960 $searchPart['object_count'] = $wordIDHash[$word]['object_count']; 01961 $searchPartsArray[] = $searchPart; 01962 unset ( $searchPart ); 01963 } 01964 return $searchPartsArray; 01965 } 01966 01967 function buildSearchPartArrayForPhrases( $phraseTextArray, $wordIDHash, $identifier = false ) 01968 { 01969 // build an array of the word id's for each phrase 01970 $phraseIDArrayArray = array(); 01971 foreach ( $phraseTextArray as $phraseText ) 01972 { 01973 $wordArray = $this->splitString( $phraseText ); 01974 $wordIDArray = array(); 01975 foreach ( $wordArray as $word ) 01976 { 01977 if ( !isset( $wordIDHash[$word] ) ) return array(); 01978 $wordIDArray[] = $wordIDHash[$word]['id']; 01979 } 01980 $phraseIDArrayArray[] = $wordIDArray; 01981 } 01982 01983 // build phrase SQL query part(s) 01984 $phraseSearchSQLArray = array(); 01985 foreach ( $phraseIDArrayArray as $phraseIDArray ) 01986 { 01987 $phraseSearchSQL = $this->buildPhraseSqlQueryPart( $phraseIDArray, $identifier ); 01988 $phraseSearchSQLArray[] = $phraseSearchSQL; 01989 } 01990 01991 ///Build search parts array for phrases and normal words 01992 $searchPartsArray = array(); 01993 $i = 0; 01994 foreach ( $phraseTextArray as $phraseText ) 01995 { 01996 foreach ( $phraseSearchSQLArray[$i] as $phraseSearchSubSQL ) 01997 { 01998 $searchPart = array(); 01999 $searchPart['text'] = $phraseText; 02000 $searchPart['sql_part'] = ' ( ' . $phraseSearchSubSQL . ' ) AND'; 02001 $searchPart['is_phrase'] = 1; 02002 $searchPart['object_count'] = 0; 02003 $searchPartsArray[] = $searchPart; 02004 } 02005 unset( $searchPart ); 02006 $i++; 02007 } 02008 02009 return $searchPartsArray; 02010 } 02011 02012 function prepareWordIDArraysForPattern( $searchText ) 02013 { 02014 $db = eZDB::instance(); 02015 $searchWordArray = $this->splitString( $searchText ); 02016 02017 02018 // fetch the word id 02019 $wordQueryString = ''; 02020 $patternWordsCount = 0; 02021 $patternWordArray = array(); 02022 $wordsCount = 0; 02023 $patternWordQueryString = ''; 02024 foreach ( $searchWordArray as $searchWord ) 02025 { 02026 if ( preg_match ( "/(\w+)(\*)/", $searchWord, $matches ) ) 02027 { 02028 if ( $patternWordsCount > 0 ) 02029 $patternWordQueryString .= " or "; 02030 $patternWordArray[] = $matches[1]; 02031 $patternWordsCount++; 02032 } 02033 else 02034 { 02035 if ( $wordsCount > 0 ) 02036 $wordQueryString .= " or "; 02037 $wordQueryString .= " word='$searchWord' "; 02038 $wordsCount++; 02039 } 02040 } 02041 02042 // create the word hash 02043 $wordIDArray = array(); 02044 $wordIDHash = array(); 02045 if ( $wordsCount > 0 ) 02046 { 02047 $wordIDArrayRes = $db->arrayQuery( "SELECT id, word, object_count FROM ezsearch_word where $wordQueryString ORDER BY object_count" ); 02048 02049 foreach ( $wordIDArrayRes as $wordRes ) 02050 { 02051 $wordIDArray[] = $wordRes['id']; 02052 $wordIDHash[$wordRes['word']] = array( 'id' => $wordRes['id'], 'word' => $wordRes['word'], 'object_count' => $wordRes['object_count'] ); 02053 } 02054 } 02055 02056 $patternWordIDHash = array(); 02057 foreach ( $patternWordArray as $word ) 02058 { 02059 $patternWordIDRes = $db->arrayQuery( "SELECT id, word, object_count FROM ezsearch_word where word like '" . $word . "%' order by object_count" ); 02060 $matchedWords = array(); 02061 foreach ( $patternWordIDRes as $wordRes ) 02062 { 02063 $matchedWords[] = array( 'id' => $wordRes['id'], 'word' => $wordRes['word'], 'object_count' => $wordRes['object_count'] ); 02064 } 02065 $patternWordIDHash[$word] = $matchedWords; 02066 } 02067 02068 02069 return array( 'wordIDArray' => $wordIDArray, 02070 'wordIDHash' => $wordIDHash, 02071 'patternWordIDHash' => $patternWordIDHash ); 02072 } 02073 02074 function prepareWordIDArrays( $searchText ) 02075 { 02076 if ( trim( $searchText ) == "" ) 02077 { 02078 $ini = eZINI::instance(); 02079 if ( $ini->hasVariable( 'SearchSettings', 'AllowEmptySearch' ) and 02080 $ini->variable( 'SearchSettings', 'AllowEmptySearch' ) != 'enabled' ) 02081 return array(); 02082 } 02083 02084 $db = eZDB::instance(); 02085 02086 //extend search words for urls, by extracting parts without www. and add to the end of search text 02087 $matches = array(); 02088 if ( preg_match_all("/www\.([^\.]+\.[^\s]+)\s?/i", $searchText, $matches ) ); 02089 { 02090 if (isset($matches[1]) ) 02091 { 02092 $searchText.= ' '.join(' ', $matches[1] ); 02093 } 02094 } 02095 02096 $searchWordArray = $this->splitString( $searchText ); 02097 02098 $wildCardWordArray = array(); 02099 $i = 0; 02100 $wildCardQueryString = array(); 02101 $wordQueryString = ''; 02102 02103 $ini = eZINI::instance(); 02104 $wildSearchEnabled = ( $ini->variable( 'SearchSettings', 'EnableWildcard' ) == 'true' ); 02105 if ( $wildSearchEnabled ) 02106 { 02107 $minCharacters = $ini->variable( 'SearchSettings', 'MinCharacterWildcard' ); 02108 } 02109 02110 foreach ( $searchWordArray as $searchWord ) 02111 { 02112 $wordLength = strlen( $searchWord ) - 1; 02113 02114 if ( $wildSearchEnabled && ( $wordLength >= $minCharacters ) ) 02115 { 02116 if ( $searchWord[$wordLength] == '*' ) 02117 { 02118 $baseWord = substr( $searchWord, 0, $wordLength ); 02119 $wildCardQueryString[] = " word LIKE '". $baseWord ."%' "; 02120 continue; 02121 } 02122 else if ( $searchWord[0] == '*' ) /* Change this to allow searching for shorter/longer words using wildcard */ 02123 { 02124 $baseWord = substr( $searchWord, 1, $wordLength ); 02125 $wildCardQueryString[] = " word LIKE '%". $baseWord ."' "; 02126 continue; 02127 } 02128 } 02129 if ( $i > 0 ) 02130 $wordQueryString .= " or "; 02131 02132 $wordQueryString .= " word='$searchWord' "; 02133 $i++; 02134 } 02135 02136 if ( strlen( $wordQueryString ) > 0 ) 02137 $wordIDArrayRes = $db->arrayQuery( "SELECT id, word, object_count FROM ezsearch_word where $wordQueryString ORDER BY object_count" ); 02138 foreach ( $wildCardQueryString as $wildCardQuery ) 02139 { 02140 $wildCardWordArray[] = $db->arrayQuery( "SELECT id, word, object_count FROM ezsearch_word where $wildCardQuery order by object_count" ); 02141 } 02142 02143 // create the word hash 02144 $wordIDArray = array(); 02145 $wordIDHash = array(); 02146 if ( isset( $wordIDArrayRes ) && is_array( $wordIDArrayRes ) ) 02147 { 02148 foreach ( $wordIDArrayRes as $wordRes ) 02149 { 02150 $wordIDArray[] = $wordRes['id']; 02151 $wordIDHash[$wordRes['word']] = array( 'id' => $wordRes['id'], 'word' => $wordRes['word'], 'object_count' => $wordRes['object_count'] ); 02152 } 02153 } 02154 02155 $wildIDArray = array(); 02156 $wildCardCount = 0; 02157 foreach ( array_keys( $wildCardWordArray ) as $key ) 02158 { 02159 if ( is_array( $wildCardWordArray[$key] ) && count( $wildCardWordArray[$key] ) > 0 ) 02160 { 02161 $wildCardCount++; 02162 foreach ( $wildCardWordArray[$key] as $wordRes ) 02163 { 02164 $wildIDArray[] = array( 'id' => $wordRes['id'], 'object_count' => $wordRes['object_count'] ); 02165 } 02166 } 02167 } 02168 return array( 'wordIDArray' => $wordIDArray, 02169 'wordIDHash' => $wordIDHash, 02170 'wildIDArray' => $wildIDArray, 02171 'wildCardCount' => $wildCardCount ); 02172 } 02173 02174 function fetchTotalObjectCount() 02175 { 02176 // Get the total number of objects 02177 $db = eZDB::instance(); 02178 $objectCount = array(); 02179 $objectCount = $db->arrayQuery( "SELECT COUNT(*) AS count FROM ezcontentobject" ); 02180 $totalObjectCount = $objectCount[0]["count"]; 02181 return $totalObjectCount; 02182 } 02183 02184 function constructMethodName( $searchTypeData ) 02185 { 02186 $type = $searchTypeData['type']; 02187 $subtype = $searchTypeData['subtype']; 02188 $methodName = 'search' . $type . $subtype; 02189 return $methodName; 02190 02191 } 02192 02193 function callMethod( $methodName, $parameterArray ) 02194 { 02195 if ( !method_exists( $this, $methodName ) ) 02196 { 02197 eZDebug::writeError( $methodName, "Method does not exist in ez search engine" ); 02198 return false; 02199 } 02200 02201 return call_user_func_array( array( $this, $methodName ), $parameterArray ); 02202 } 02203 02204 /*! 02205 Will remove all search words and object/word relations. 02206 */ 02207 function cleanup() 02208 { 02209 $db = eZDB::instance(); 02210 $db->begin(); 02211 $db->query( "DELETE FROM ezsearch_word" ); 02212 $db->query( "DELETE FROM ezsearch_object_word_link" ); 02213 $db->commit(); 02214 } 02215 02216 /*! 02217 \return true if the search part is incomplete. 02218 */ 02219 function isSearchPartIncomplete( $part ) 02220 { 02221 switch ( $part['subtype'] ) 02222 { 02223 case 'fulltext': 02224 { 02225 if ( !isset( $part['value'] ) || $part['value'] == '' ) 02226 return true; 02227 } 02228 break; 02229 02230 case 'patterntext': 02231 { 02232 if ( !isset( $part['value'] ) || $part['value'] == '' ) 02233 return true; 02234 } 02235 break; 02236 02237 case 'integer': 02238 { 02239 if ( !isset( $part['value'] ) || $part['value'] == '' ) 02240 return true; 02241 } 02242 break; 02243 02244 case 'integers': 02245 { 02246 if ( !isset( $part['values'] ) || count( $part['values'] ) == 0 ) 02247 return true; 02248 } 02249 break; 02250 02251 case 'byrange': 02252 { 02253 if ( !isset( $part['from'] ) || $part['from'] == '' || 02254 !isset( $part['to'] ) || $part['to'] == '' ) 02255 return true; 02256 } 02257 break; 02258 02259 case 'byidentifier': 02260 { 02261 if ( !isset( $part['value'] ) || $part['value'] == '' ) 02262 return true; 02263 } 02264 break; 02265 02266 case 'byidentifierrange': 02267 { 02268 if ( !isset( $part['from'] ) || $part['from'] == '' || 02269 !isset( $part['to'] ) || $part['to'] == '' ) 02270 return true; 02271 } 02272 break; 02273 02274 case 'integersbyidentifier': 02275 { 02276 if ( !isset( $part['values'] ) || count( $part['values'] ) == 0 ) 02277 return true; 02278 } 02279 break; 02280 02281 case 'byarea': 02282 { 02283 if ( !isset( $part['from'] ) || $part['from'] == '' || 02284 !isset( $part['to'] ) || $part['to'] == '' || 02285 !isset( $part['minvalue'] ) || $part['minvalue'] == '' || 02286 !isset( $part['maxvalue'] ) || $part['maxvalue'] == '' ) 02287 { 02288 return true; 02289 } 02290 } 02291 } 02292 return false; 02293 } 02294 02295 /** 02296 * Commit the changes to the search engine 02297 */ 02298 public function commit() 02299 { 02300 } 02301 02302 /** 02303 * Update the section in the search engine 02304 * 02305 * @param array $objectID 02306 * @param int $sectionID 02307 * @return void 02308 * @see eZSearch::updateObjectsSection() 02309 */ 02310 public function updateObjectsSection( array $objectIDs, $sectionID ) 02311 { 02312 $db = eZDB::instance(); 02313 $db->query( "UPDATE ezsearch_object_word_link SET section_id='$sectionID' WHERE " . 02314 $db->generateSQLINStatement( $objectIDs, 'contentobject_id', false, true, 'int' ) 02315 ); 02316 } 02317 02318 02319 public $TempTablesCount = 0; 02320 public $CreatedTempTablesNames = array(); 02321 } 02322 02323 ?>