eZ Publish  [trunk]
ezsearchengine.php
Go to the documentation of this file.
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 ?>