PageRenderTime 58ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/kernel/search/plugins/ezsearchengine/ezsearchengine.php

https://github.com/granitegreg/ezpublish
PHP | 2310 lines | 1928 code | 180 blank | 202 comment | 202 complexity | 6df2b768928742d8d1aebcc682e7e6ff MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. //
  3. // Definition of eZSearchEngine class
  4. //
  5. // Created on: <25-Jun-2002 13:09:57 bf>
  6. //
  7. // ## BEGIN COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
  8. // SOFTWARE NAME: eZ Publish
  9. // SOFTWARE RELEASE: 4.1.x
  10. // COPYRIGHT NOTICE: Copyright (C) 1999-2011 eZ Systems AS
  11. // SOFTWARE LICENSE: GNU General Public License v2.0
  12. // NOTICE: >
  13. // This program is free software; you can redistribute it and/or
  14. // modify it under the terms of version 2.0 of the GNU General
  15. // Public License as published by the Free Software Foundation.
  16. //
  17. // This program is distributed in the hope that it will be useful,
  18. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. // GNU General Public License for more details.
  21. //
  22. // You should have received a copy of version 2.0 of the GNU General
  23. // Public License along with this program; if not, write to the Free
  24. // Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  25. // MA 02110-1301, USA.
  26. //
  27. //
  28. // ## END COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
  29. //
  30. /*!
  31. \class eZSearchEngine ezsearch.php
  32. */
  33. class eZSearchEngine
  34. {
  35. function eZSearchEngine()
  36. {
  37. $generalFilter = array( 'subTreeTable' => '',
  38. 'searchDateQuery' => '',
  39. 'sectionQuery' => '',
  40. 'classQuery' => '',
  41. 'classAttributeQuery' => '',
  42. 'searchPartText' => '',
  43. 'subTreeSQL' => '',
  44. 'sqlPermissionChecking' => array( 'from' => '',
  45. 'where' => '' ) );
  46. $this->GeneralFilter = $generalFilter;
  47. }
  48. static function needCommit()
  49. {
  50. //commits are NA
  51. return false;
  52. }
  53. static function needRemoveWithUpdate()
  54. {
  55. return true;
  56. }
  57. /*!
  58. Adds an object to the search database.
  59. */
  60. function addObject( $contentObject, $commit )
  61. {
  62. $contentObjectID = $contentObject->attribute( 'id' );
  63. $currentVersion = $contentObject->currentVersion();
  64. if ( !$currentVersion )
  65. {
  66. $errCurrentVersion = $contentObject->attribute( 'current_version');
  67. eZDebug::writeError( "Failed to fetch \"current version\" ({$errCurrentVersion})" .
  68. " of content object (ID: {$contentObjectID})", 'eZSearchEngine' );
  69. return;
  70. }
  71. $indexArray = array();
  72. $indexArrayOnlyWords = array();
  73. $wordCount = 0;
  74. $placement = 0;
  75. $previousWord = '';
  76. eZContentObject::recursionProtectionStart();
  77. foreach ( $currentVersion->contentObjectAttributes() as $attribute )
  78. {
  79. $metaData = array();
  80. $classAttribute = $attribute->contentClassAttribute();
  81. if ( $classAttribute->attribute( "is_searchable" ) == 1 )
  82. {
  83. // Fetch attribute translations
  84. $attributeTranslations = $attribute->fetchAttributeTranslations();
  85. foreach ( $attributeTranslations as $translation )
  86. {
  87. $tmpMetaData = $translation->metaData();
  88. if( ! is_array( $tmpMetaData ) )
  89. {
  90. $tmpMetaData = array( array( 'id' => $attribute->attribute( 'contentclass_attribute_identifier' ),
  91. 'text' => $tmpMetaData ) );
  92. }
  93. $metaData = array_merge( $metaData, $tmpMetaData );
  94. }
  95. foreach( $metaData as $metaDataPart )
  96. {
  97. $text = eZSearchEngine::normalizeText( htmlspecialchars ($metaDataPart['text'], ENT_NOQUOTES, 'UTF-8' ) , true );
  98. // Split text on whitespace
  99. if ( is_numeric( trim( $text ) ) )
  100. {
  101. $integerValue = (int) $text;
  102. }
  103. else
  104. {
  105. $integerValue = 0;
  106. }
  107. $wordArray = explode( ' ', $text );
  108. foreach ( $wordArray as $word )
  109. {
  110. if ( trim( $word ) != "" )
  111. {
  112. // words stored in search index are limited to 150 characters
  113. if ( strlen( $word ) > 150 )
  114. {
  115. $word = substr( $word, 0, 150 );
  116. }
  117. $indexArray[] = array( 'Word' => $word,
  118. 'ContentClassAttributeID' => $attribute->attribute( 'contentclassattribute_id' ),
  119. 'identifier' => $metaDataPart['id'],
  120. 'integer_value' => $integerValue );
  121. $indexArrayOnlyWords[$word] = 1;
  122. $wordCount++;
  123. //if we have "www." before word than
  124. //treat it as url and add additional entry to the index
  125. if ( substr( strtolower($word), 0, 4 ) == 'www.' )
  126. {
  127. $additionalUrlWord = substr( $word, 4 );
  128. $indexArray[] = array( 'Word' => $additionalUrlWord,
  129. 'ContentClassAttributeID' => $attribute->attribute( 'contentclassattribute_id' ),
  130. 'identifier' => $metaDataPart['id'],
  131. 'integer_value' => $integerValue );
  132. $indexArrayOnlyWords[$additionalUrlWord] = 1;
  133. $wordCount++;
  134. }
  135. }
  136. }
  137. }
  138. }
  139. }
  140. eZContentObject::recursionProtectionEnd();
  141. $wordIDArray = $this->buildWordIDArray( array_keys( $indexArrayOnlyWords ) );
  142. $db = eZDB::instance();
  143. $db->begin();
  144. for( $arrayCount = 0; $arrayCount < $wordCount; $arrayCount += 1000 )
  145. {
  146. $placement = $this->indexWords( $contentObject, array_slice( $indexArray, $arrayCount, 1000 ), $wordIDArray, $placement );
  147. }
  148. $db->commit();
  149. }
  150. /*!
  151. \private
  152. Build WordIDArray and update ezsearch_word table
  153. \params words for object to add
  154. \return wordIDArray
  155. */
  156. function buildWordIDArray( $indexArrayOnlyWords )
  157. {
  158. $db = eZDB::instance();
  159. // Initialize transformation system
  160. $trans = eZCharTransform::instance();
  161. $wordCount = count( $indexArrayOnlyWords );
  162. $wordIDArray = array();
  163. $wordArray = array();
  164. // store the words in the index and remember the ID
  165. $dbName = $db->databaseName();
  166. if ( $dbName == 'mysql' )
  167. {
  168. $db->begin();
  169. for( $arrayCount = 0; $arrayCount < $wordCount; $arrayCount += 500 )
  170. {
  171. // Fetch already indexed words from database
  172. $wordArrayChuck = array_slice( $indexArrayOnlyWords, $arrayCount, 500 );
  173. $wordsString = implode( '\',\'', $wordArrayChuck );
  174. $wordRes = $db->arrayQuery( "SELECT * FROM ezsearch_word WHERE word IN ( '$wordsString' ) " );
  175. // Build a has of the existing words
  176. $wordResCount = count( $wordRes );
  177. $existingWordArray = array();
  178. for ( $i = 0; $i < $wordResCount; $i++ )
  179. {
  180. $wordIDArray[] = $wordRes[$i]['id'];
  181. $existingWordArray[] = $wordRes[$i]['word'];
  182. $wordArray[$wordRes[$i]['word']] = $wordRes[$i]['id'];
  183. }
  184. // Update the object count of existing words by one
  185. $wordIDString = implode( ',', $wordIDArray );
  186. if ( count( $wordIDArray ) > 0 )
  187. $db->query( "UPDATE ezsearch_word SET object_count=( object_count + 1 ) WHERE id IN ( $wordIDString )" );
  188. // Insert if there is any news words
  189. $newWordArray = array_diff( $wordArrayChuck, $existingWordArray );
  190. if ( count ($newWordArray) > 0 )
  191. {
  192. $newWordString = implode( "', '1' ), ('", $newWordArray );
  193. $db->query( "INSERT INTO
  194. ezsearch_word ( word, object_count )
  195. VALUES ('$newWordString', '1' )" );
  196. $newWordString = implode( "','", $newWordArray );
  197. $newWordRes = $db->arrayQuery( "select id,word from ezsearch_word where word in ( '$newWordString' ) " );
  198. $newWordCount = count( $newWordRes );
  199. for ( $i=0;$i<$newWordCount;++$i )
  200. {
  201. $wordLowercase = $trans->transformByGroup( $newWordRes[$i]['word'], 'lowercase' );
  202. $wordArray[$newWordRes[$i]['word']] = $newWordRes[$i]['id'];
  203. }
  204. }
  205. }
  206. $db->commit();
  207. }
  208. else
  209. {
  210. $db->begin();
  211. foreach ( $indexArrayOnlyWords as $indexWord )
  212. {
  213. $indexWord = $trans->transformByGroup( $indexWord, 'lowercase' );
  214. $indexWord = $db->escapeString( $indexWord );
  215. // Store word if it does not exist.
  216. $wordRes = array();
  217. $wordRes = $db->arrayQuery( "SELECT * FROM ezsearch_word WHERE word='$indexWord'" );
  218. if ( count( $wordRes ) == 1 )
  219. {
  220. $wordID = $wordRes[0]["id"];
  221. $db->query( "UPDATE ezsearch_word SET object_count=( object_count + 1 ) WHERE id='$wordID'" );
  222. }
  223. else
  224. {
  225. $db->query( "INSERT INTO
  226. ezsearch_word ( word, object_count )
  227. VALUES ( '$indexWord', '1' )" );
  228. $wordID = $db->lastSerialID( "ezsearch_word", "id" );
  229. }
  230. $wordArray[$indexWord] = $wordID;
  231. }
  232. $db->commit();
  233. }
  234. return $wordArray;
  235. }
  236. /*!
  237. \private
  238. \param contentObject
  239. \param indexArray
  240. \param wordIDArray
  241. \param placement
  242. \return last placement
  243. Index wordIndex
  244. */
  245. function indexWords( $contentObject, $indexArray, $wordIDArray, $placement = 0 )
  246. {
  247. $db = eZDB::instance();
  248. $contentObjectID = $contentObject->attribute( 'id' );
  249. // Count the total words in index text
  250. $totalWordCount = count( $indexArray );
  251. /* // Needs to be rewritten
  252. // Count the number of instances of each word
  253. $wordCountArray = array_count_values( $indexArray );
  254. // Strip double words
  255. $uniqueIndexArray = array_unique( $indexArray );
  256. // Count unique words
  257. $uniqueWordCount = count( $uniqueIndexArray );
  258. */
  259. // Initialize transformation system
  260. $trans = eZCharTransform::instance();
  261. $prevWordID = 0;
  262. $nextWordID = 0;
  263. $classID = $contentObject->attribute( 'contentclass_id' );
  264. $sectionID = $contentObject->attribute( 'section_id' );
  265. $published = $contentObject->attribute( 'published' );
  266. $valuesStringList = array();
  267. for ( $i = 0; $i < count( $indexArray ); $i++ )
  268. {
  269. $indexWord = $indexArray[$i]['Word'];
  270. $contentClassAttributeID = $indexArray[$i]['ContentClassAttributeID'];
  271. $identifier = $indexArray[$i]['identifier'];
  272. $integerValue = $indexArray[$i]['integer_value'];
  273. $wordID = $wordIDArray[$indexWord];
  274. if ( isset( $indexArray[$i+1] ) )
  275. {
  276. $nextIndexWord = $indexArray[$i+1]['Word'];
  277. $nextWordID = $wordIDArray[$nextIndexWord];
  278. }
  279. else
  280. $nextWordID = 0;
  281. // print( "indexing word : $indexWord <br> " );
  282. // Calculate the relevans ranking for this word
  283. // $frequency = ( $wordCountArray[$indexWord] / $totalWordCount );
  284. $frequency = 0;
  285. $valuesStringList[] = " ( '$wordID', '$contentObjectID', '$frequency', '$placement', '$nextWordID', '$prevWordID', '$classID', '$contentClassAttributeID', '$published', '$sectionID', '$identifier', '$integerValue' ) ";
  286. $prevWordID = $wordID;
  287. $placement++;
  288. }
  289. $dbName = $db->databaseName();
  290. if ( $dbName == 'mysql' )
  291. {
  292. if ( count( $valuesStringList ) > 0 )
  293. {
  294. $valuesString = implode( ',', $valuesStringList );
  295. $db->query( "INSERT INTO
  296. ezsearch_object_word_link
  297. ( word_id,
  298. contentobject_id,
  299. frequency,
  300. placement,
  301. next_word_id,
  302. prev_word_id,
  303. contentclass_id,
  304. contentclass_attribute_id,
  305. published,
  306. section_id,
  307. identifier,
  308. integer_value )
  309. VALUES $valuesString" );
  310. }
  311. }
  312. else
  313. {
  314. $db->begin();
  315. foreach ( array_keys( $valuesStringList ) as $key )
  316. {
  317. $valuesString = $valuesStringList[$key];
  318. $db->query("INSERT INTO
  319. ezsearch_object_word_link
  320. ( word_id,
  321. contentobject_id,
  322. frequency,
  323. placement,
  324. next_word_id,
  325. prev_word_id,
  326. contentclass_id,
  327. contentclass_attribute_id,
  328. published,
  329. section_id,
  330. identifier,
  331. integer_value )
  332. VALUES $valuesString" );
  333. }
  334. $db->commit();
  335. }
  336. return $placement;
  337. }
  338. /*!
  339. \static
  340. */
  341. function removeObject( $contentObject, $commit )
  342. {
  343. $db = eZDB::instance();
  344. $objectID = $contentObject->attribute( "id" );
  345. $doDelete = false;
  346. $db->begin();
  347. if ( $db->databaseName() == 'mysql' )
  348. {
  349. // fetch all the words and decrease the object count on all the words
  350. $wordArray = $db->arrayQuery( "SELECT word_id FROM ezsearch_object_word_link WHERE contentobject_id='$objectID'" );
  351. $wordIDList = array();
  352. foreach ( $wordArray as $word )
  353. $wordIDList[] = $word["word_id"];
  354. if ( count( $wordIDList ) > 0 )
  355. {
  356. $wordIDString = implode( ',', $wordIDList );
  357. $db->query( "UPDATE ezsearch_word SET object_count=( object_count - 1 ) WHERE id in ( $wordIDString )" );
  358. $doDelete = true;
  359. }
  360. }
  361. else
  362. {
  363. $cnt = $db->arrayQuery( "SELECT COUNT( word_id ) AS cnt FROM ezsearch_object_word_link WHERE contentobject_id='$objectID'" );
  364. if ( $cnt[0]['cnt'] > 0 )
  365. {
  366. $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' )" );
  367. $doDelete = true;
  368. }
  369. }
  370. if ( $doDelete )
  371. {
  372. $db->query( "DELETE FROM ezsearch_word WHERE object_count='0'" );
  373. $db->query( "DELETE FROM ezsearch_object_word_link WHERE contentobject_id='$objectID'" );
  374. }
  375. $db->commit();
  376. }
  377. /*!
  378. Saves name of a temporary that has just been created,
  379. for us to know its name when it's time to drop the table.
  380. */
  381. function saveCreatedTempTableName( $index, $tableName )
  382. {
  383. if ( isset( $this->CreatedTempTablesNames[$index] ) )
  384. {
  385. eZDebug::writeWarning( "CreatedTempTablesNames[\$index] already exists " .
  386. "and contains '" . $this->CreatedTempTablesNames[$index] . "'" );
  387. }
  388. $this->CreatedTempTablesNames[$index] = $tableName;
  389. }
  390. /*!
  391. \return Given table name from the list of saved temporary tables names by its index.
  392. \see saveCreatedTempTableName()
  393. */
  394. function getSavedTempTableName( $index )
  395. {
  396. return $this->CreatedTempTablesNames[$index];
  397. }
  398. /*!
  399. \return List of saved temporary tables names.
  400. \see saveCreatedTempTableName()
  401. */
  402. function getSavedTempTablesList()
  403. {
  404. return $this->CreatedTempTablesNames;
  405. }
  406. /*!
  407. Runs a query to the search engine.
  408. */
  409. function search( $searchText, $params = array(), $searchTypes = array() )
  410. {
  411. if ( count( $searchTypes ) == 0 )
  412. {
  413. $searchTypes['general'] = array();
  414. $searchTypes['subtype'] = array();
  415. $searchTypes['and'] = array();
  416. }
  417. else if ( !isset( $searchTypes['general'] ) )
  418. {
  419. $searchTypes['general'] = array();
  420. }
  421. $allowSearch = true;
  422. if ( trim( $searchText ) == '' )
  423. {
  424. $ini = eZINI::instance();
  425. if ( $ini->variable( 'SearchSettings', 'AllowEmptySearch' ) != 'enabled' )
  426. $allowSearch = false;
  427. if ( isset( $params['AllowEmptySearch'] ) )
  428. $allowSearch = $params['AllowEmptySearch'];
  429. }
  430. if ( $allowSearch )
  431. {
  432. $searchText = $this->normalizeText( $searchText, false );
  433. $db = eZDB::instance();
  434. $nonExistingWordArray = array();
  435. $searchTypeMap = array( 'class' => 'SearchContentClassID',
  436. 'publishdate' => 'SearchDate',
  437. 'subtree' => 'SearchSubTreeArray' );
  438. foreach ( $searchTypes['general'] as $searchType )
  439. {
  440. $params[$searchTypeMap[$searchType['subtype']]] = $searchType['value'];
  441. }
  442. if ( isset( $params['SearchOffset'] ) )
  443. $searchOffset = $params['SearchOffset'];
  444. else
  445. $searchOffset = 0;
  446. if ( isset( $params['SearchLimit'] ) )
  447. $searchLimit = $params['SearchLimit'];
  448. else
  449. $searchLimit = 10;
  450. if ( isset( $params['SearchContentClassID'] ) )
  451. $searchContentClassID = $params['SearchContentClassID'];
  452. else
  453. $searchContentClassID = -1;
  454. if ( isset( $params['SearchSectionID'] ) )
  455. $searchSectionID = $params['SearchSectionID'];
  456. else
  457. $searchSectionID = -1;
  458. if ( isset( $params['SearchDate'] ) )
  459. $searchDate = $params['SearchDate'];
  460. else
  461. $searchDate = -1;
  462. if ( isset( $params['SearchTimestamp'] ) )
  463. $searchTimestamp = $params['SearchTimestamp'];
  464. else
  465. $searchTimestamp = false;
  466. if ( isset( $params['SearchContentClassAttributeID'] ) )
  467. $searchContentClassAttributeID = $params['SearchContentClassAttributeID'];
  468. else
  469. $searchContentClassAttributeID = -1;
  470. if ( isset( $params['SearchSubTreeArray'] ) )
  471. $subTreeArray = $params['SearchSubTreeArray'];
  472. else
  473. $subTreeArray = array();
  474. if ( isset( $params['SortArray'] ) )
  475. $sortArray = $params['SortArray'];
  476. else
  477. $sortArray = array();
  478. $ignoreVisibility = isset( $params['IgnoreVisibility'] ) ? $params['IgnoreVisibility'] : false;
  479. // strip multiple spaces
  480. $searchText = preg_replace( "(\s+)", " ", $searchText );
  481. // find the phrases
  482. /* $numQuotes = substr_count( $searchText, "\"" );
  483. $phraseTextArray = array();
  484. $fullText = $searchText;
  485. $nonPhraseText ='';
  486. // $fullText = '';
  487. $postPhraseText = $fullText;
  488. $pos = 0;
  489. if ( ( $numQuotes > 0 ) and ( ( $numQuotes % 2 ) == 0 ) )
  490. {
  491. for ( $i = 0; $i < ( $numQuotes / 2 ); $i ++ )
  492. {
  493. $quotePosStart = strpos( $searchText, '"', $pos );
  494. $quotePosEnd = strpos( $searchText, '"', $quotePosStart + 1 );
  495. $prePhraseText = substr( $searchText, $pos, $quotePosStart - $pos );
  496. $postPhraseText = substr( $searchText, $quotePosEnd +1 );
  497. $phraseText = substr( $searchText, $quotePosStart + 1, $quotePosEnd - $quotePosStart - 1 );
  498. $phraseTextArray[] = $phraseText;
  499. // $fullText .= $prePhraseText;
  500. $nonPhraseText .= $prePhraseText;
  501. $pos = $quotePosEnd + 1;
  502. }
  503. }
  504. $nonPhraseText .= $postPhraseText;
  505. */
  506. $phrasesResult = $this->getPhrases( $searchText );
  507. $phraseTextArray = $phrasesResult['phrases'];
  508. $nonPhraseText = $phrasesResult['nonPhraseText'];
  509. $fullText = $phrasesResult['fullText'];
  510. $sectionQuery = '';
  511. if ( is_numeric( $searchSectionID ) and $searchSectionID > 0 )
  512. {
  513. $sectionQuery = "ezsearch_object_word_link.section_id = '$searchSectionID' AND ";
  514. }
  515. else if ( is_array( $searchSectionID ) )
  516. {
  517. // Build query for searching in an array of sections
  518. $sectionQuery = $db->generateSQLINStatement( $searchSectionID, 'ezsearch_object_word_link.section_id', false, false, 'int' ) . " AND ";
  519. }
  520. $searchDateQuery = '';
  521. if ( ( is_numeric( $searchDate ) and $searchDate > 0 ) or
  522. $searchTimestamp )
  523. {
  524. $date = new eZDateTime();
  525. $timestamp = $date->timeStamp();
  526. $day = $date->attribute('day');
  527. $month = $date->attribute('month');
  528. $year = $date->attribute('year');
  529. $publishedDateStop = false;
  530. if ( $searchTimestamp )
  531. {
  532. if ( is_array( $searchTimestamp ) )
  533. {
  534. $publishedDate = (int) $searchTimestamp[0];
  535. $publishedDateStop = (int) $searchTimestamp[1];
  536. }
  537. else
  538. $publishedDate = (int) $searchTimestamp;
  539. }
  540. else
  541. {
  542. switch ( $searchDate )
  543. {
  544. case 1:
  545. {
  546. $adjustment = 24*60*60; //seconds for one day
  547. $publishedDate = $timestamp - $adjustment;
  548. } break;
  549. case 2:
  550. {
  551. $adjustment = 7*24*60*60; //seconds for one week
  552. $publishedDate = $timestamp - $adjustment;
  553. } break;
  554. case 3:
  555. {
  556. $adjustment = 31*24*60*60; //seconds for one month
  557. $publishedDate = $timestamp - $adjustment;
  558. } break;
  559. case 4:
  560. {
  561. $adjustment = 3*31*24*60*60; //seconds for three months
  562. $publishedDate = $timestamp - $adjustment;
  563. } break;
  564. case 5:
  565. {
  566. $adjustment = 365*24*60*60; //seconds for one year
  567. $publishedDate = $timestamp - $adjustment;
  568. } break;
  569. default:
  570. {
  571. $publishedDate = $date->timeStamp();
  572. }
  573. }
  574. }
  575. $searchDateQuery = "ezsearch_object_word_link.published >= '$publishedDate' AND ";
  576. if ( $publishedDateStop )
  577. $searchDateQuery .= "ezsearch_object_word_link.published <= '$publishedDateStop' AND ";
  578. $this->GeneralFilter['searchDateQuery'] = $searchDateQuery;
  579. }
  580. $classQuery = "";
  581. if ( is_numeric( $searchContentClassID ) and $searchContentClassID > 0 )
  582. {
  583. // Build query for searching in one class
  584. $classQuery = "ezsearch_object_word_link.contentclass_id = '$searchContentClassID' AND ";
  585. $this->GeneralFilter['classAttributeQuery'] = $classQuery;
  586. }
  587. else if ( is_array( $searchContentClassID ) )
  588. {
  589. // Build query for searching in a number of classes
  590. $classString = $db->generateSQLINStatement( $searchContentClassID, 'ezsearch_object_word_link.contentclass_id', false, false, 'int' );
  591. $classQuery = "$classString AND ";
  592. $this->GeneralFilter['classAttributeQuery'] = $classQuery;
  593. }
  594. $classAttributeQuery = "";
  595. if ( is_numeric( $searchContentClassAttributeID ) and $searchContentClassAttributeID > 0 )
  596. {
  597. $classAttributeQuery = "ezsearch_object_word_link.contentclass_attribute_id = '$searchContentClassAttributeID' AND ";
  598. }
  599. else if ( is_array( $searchContentClassAttributeID ) )
  600. {
  601. // Build query for searching in a number of attributes
  602. $classAttributeQuery = $db->generateSQLINStatement( $searchContentClassAttributeID , 'ezsearch_object_word_link.contentclass_attribute_id', false, false, 'int' ) . ' AND ';
  603. }
  604. // Get the total number of objects
  605. $totalObjectCount = $this->fetchTotalObjectCount();
  606. $searchPartsArray = array();
  607. $wordIDHash = array();
  608. $wildCardCount = 0;
  609. if ( trim( $searchText ) != '' )
  610. {
  611. $wordIDArrays = $this->prepareWordIDArrays( $searchText );
  612. $wordIDArray = $wordIDArrays['wordIDArray'];
  613. $wordIDHash = $wordIDArrays['wordIDHash'];
  614. $wildIDArray = $wordIDArrays['wildIDArray'];
  615. $wildCardCount = $wordIDArrays['wildCardCount'];
  616. $searchPartsArray = $this->buildSearchPartArray( $phraseTextArray, $nonPhraseText, $wordIDHash, $wildIDArray );
  617. }
  618. /// OR search, not used in this version
  619. $doOrSearch = false;
  620. if ( $doOrSearch == true )
  621. {
  622. // build fulltext search SQL part
  623. $searchWordArray = $this->splitString( $fullText );
  624. $fullTextSQL = "";
  625. if ( count( $searchWordArray ) > 0 )
  626. {
  627. $i = 0;
  628. // Build the word query string
  629. foreach ( $searchWordArray as $searchWord )
  630. {
  631. $wordID = null;
  632. if ( isset( $wordIDHash[$searchWord] ) )
  633. $wordID = $wordIDHash[$searchWord]['id'];
  634. if ( is_numeric( $wordID ) and ( $wordID > 0 ) )
  635. {
  636. if ( $i == 0 )
  637. $fullTextSQL .= "ezsearch_object_word_link.word_id='$wordID' ";
  638. else
  639. $fullTextSQL .= " OR ezsearch_object_word_link.word_id='$wordID' ";
  640. }
  641. else
  642. {
  643. $nonExistingWordArray[] = $searchWord;
  644. }
  645. $i++;
  646. }
  647. $fullTextSQL = " ( $fullTextSQL ) AND ";
  648. }
  649. }
  650. // Search only in specific sub trees
  651. $subTreeSQL = "";
  652. $subTreeTable = "";
  653. if ( count( $subTreeArray ) > 0 )
  654. {
  655. // Fetch path_string value to use when searching subtrees
  656. $i = 0;
  657. $doSubTreeSearch = false;
  658. $subTreeNodeSQL = '';
  659. foreach ( $subTreeArray as $nodeID )
  660. {
  661. if ( is_numeric( $nodeID ) and ( $nodeID > 0 ) )
  662. {
  663. $subTreeNodeSQL .= " $nodeID";
  664. if ( isset( $subTreeArray[$i + 1] ) and
  665. is_numeric( $subTreeArray[$i + 1] ) )
  666. $subTreeNodeSQL .= ", ";
  667. $doSubTreeSearch = true;
  668. }
  669. $i++;
  670. }
  671. if ( $doSubTreeSearch == true )
  672. {
  673. $subTreeNodeSQL = "( " . $subTreeNodeSQL;
  674. // $subTreeTable = ", ezcontentobject_tree ";
  675. $subTreeTable = '';
  676. $subTreeNodeSQL .= " ) ";
  677. $nodeQuery = "SELECT node_id, path_string FROM ezcontentobject_tree WHERE node_id IN $subTreeNodeSQL";
  678. // Build SQL subtre search query
  679. $subTreeSQL = " ( ";
  680. $nodeArray = $db->arrayQuery( $nodeQuery );
  681. $i = 0;
  682. foreach ( $nodeArray as $node )
  683. {
  684. $pathString = $node['path_string'];
  685. $subTreeSQL .= " ezcontentobject_tree.path_string like '$pathString%' ";
  686. if ( $i < ( count( $nodeArray ) -1 ) )
  687. $subTreeSQL .= " OR ";
  688. $i++;
  689. }
  690. $subTreeSQL .= " ) AND ";
  691. $this->GeneralFilter['subTreeTable'] = $subTreeTable;
  692. $this->GeneralFilter['subTreeSQL'] = $subTreeSQL;
  693. }
  694. }
  695. $limitation = false;
  696. if ( isset( $params['Limitation'] ) )
  697. {
  698. $limitation = $params['Limitation'];
  699. }
  700. $limitationList = eZContentObjectTreeNode::getLimitationList( $limitation );
  701. $sqlPermissionChecking = eZContentObjectTreeNode::createPermissionCheckingSQL( $limitationList );
  702. $this->GeneralFilter['sqlPermissionChecking'] = $sqlPermissionChecking;
  703. $useVersionName = true;
  704. if ( $useVersionName )
  705. {
  706. $versionNameTables = ', ezcontentobject_name ';
  707. $versionNameTargets = ', ezcontentobject_name.name as name, ezcontentobject_name.real_translation ';
  708. $versionNameJoins = " and ezcontentobject_tree.contentobject_id = ezcontentobject_name.contentobject_id and
  709. ezcontentobject_tree.contentobject_version = ezcontentobject_name.content_version and ";
  710. $versionNameJoins .= eZContentLanguage::sqlFilter( 'ezcontentobject_name', 'ezcontentobject' );
  711. }
  712. /// Only support AND search at this time
  713. // build fulltext search SQL part
  714. $searchWordArray = $this->splitString( $fullText );
  715. $searchWordCount = count( $searchWordArray );
  716. $fullTextSQL = "";
  717. $stopWordArray = array( );
  718. $ini = eZINI::instance();
  719. $tmpTableCount = 0;
  720. $i = 0;
  721. foreach ( $searchTypes['and'] as $searchType )
  722. {
  723. $methodName = $this->constructMethodName( $searchType );
  724. $intermediateResult = $this->callMethod( $methodName, array( $searchType ) );
  725. if ( $intermediateResult == false )
  726. {
  727. // cleanup temp tables
  728. $db->dropTempTableList( $sqlPermissionChecking['temp_tables'] );
  729. return array( "SearchResult" => array(),
  730. "SearchCount" => 0,
  731. "StopWordArray" => array() );
  732. }
  733. }
  734. // Do not execute search if site.ini:[SearchSettings]->AllowEmptySearch is enabled, but no conditions are set.
  735. if ( !$searchDateQuery &&
  736. !$sectionQuery &&
  737. !$classQuery &&
  738. !$classAttributeQuery &&
  739. !$searchPartsArray &&
  740. !$subTreeSQL )
  741. {
  742. // cleanup temp tables
  743. $db->dropTempTableList( $sqlPermissionChecking['temp_tables'] );
  744. return array( "SearchResult" => array(),
  745. "SearchCount" => 0,
  746. "StopWordArray" => array() );
  747. }
  748. $i = $this->TempTablesCount;
  749. // Loop every word and insert result in temporary table
  750. // Determine whether we should search invisible nodes.
  751. $showInvisibleNodesCond = eZContentObjectTreeNode::createShowInvisibleSQLString( !$ignoreVisibility );
  752. foreach ( $searchPartsArray as $searchPart )
  753. {
  754. $stopWordThresholdValue = 100;
  755. if ( $ini->hasVariable( 'SearchSettings', 'StopWordThresholdValue' ) )
  756. $stopWordThresholdValue = $ini->variable( 'SearchSettings', 'StopWordThresholdValue' );
  757. $stopWordThresholdPercent = 60;
  758. if ( $ini->hasVariable( 'SearchSettings', 'StopWordThresholdPercent' ) )
  759. $stopWordThresholdPercent = $ini->variable( 'SearchSettings', 'StopWordThresholdPercent' );
  760. $searchThresholdValue = $totalObjectCount;
  761. if ( $totalObjectCount > $stopWordThresholdValue )
  762. {
  763. $searchThresholdValue = (int)( $totalObjectCount * ( $stopWordThresholdPercent / 100 ) );
  764. }
  765. // do not search words that are too frequent
  766. if ( $searchPart['object_count'] < $searchThresholdValue )
  767. {
  768. $tmpTableCount++;
  769. $searchPartText = $searchPart['sql_part'];
  770. if ( $i == 0 )
  771. {
  772. $table = $db->generateUniqueTempTableName( 'ezsearch_tmp_%', 0 );
  773. $this->saveCreatedTempTableName( 0, $table );
  774. $db->createTempTable( "CREATE TEMPORARY TABLE $table ( contentobject_id int primary key not null, published int )" );
  775. $db->query( "INSERT INTO $table SELECT DISTINCT ezsearch_object_word_link.contentobject_id, ezsearch_object_word_link.published
  776. FROM ezcontentobject,
  777. ezsearch_object_word_link
  778. $subTreeTable,
  779. ezcontentclass,
  780. ezcontentobject_tree
  781. $sqlPermissionChecking[from]
  782. WHERE
  783. $searchDateQuery
  784. $sectionQuery
  785. $classQuery
  786. $classAttributeQuery
  787. $searchPartText
  788. $subTreeSQL
  789. ezcontentobject.id=ezsearch_object_word_link.contentobject_id and
  790. ezcontentobject.contentclass_id = ezcontentclass.id and
  791. ezcontentclass.version = '0' and
  792. ezcontentobject.id = ezcontentobject_tree.contentobject_id and
  793. ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id
  794. $showInvisibleNodesCond
  795. $sqlPermissionChecking[where]",
  796. eZDBInterface::SERVER_SLAVE );
  797. }
  798. else
  799. {
  800. $table = $db->generateUniqueTempTableName( 'ezsearch_tmp_%', $i );
  801. $this->saveCreatedTempTableName( $i, $table );
  802. $tmpTable0 = $this->getSavedTempTableName( 0 );
  803. $db->createTempTable( "CREATE TEMPORARY TABLE $table ( contentobject_id int primary key not null, published int )" );
  804. $db->query( "INSERT INTO $table SELECT DISTINCT ezsearch_object_word_link.contentobject_id, ezsearch_object_word_link.published
  805. FROM
  806. ezcontentobject,
  807. ezsearch_object_word_link
  808. $subTreeTable,
  809. ezcontentclass,
  810. ezcontentobject_tree,
  811. $tmpTable0
  812. $sqlPermissionChecking[from]
  813. WHERE
  814. $tmpTable0.contentobject_id=ezsearch_object_word_link.contentobject_id AND
  815. $searchDateQuery
  816. $sectionQuery
  817. $classQuery
  818. $classAttributeQuery
  819. $searchPartText
  820. $subTreeSQL
  821. ezcontentobject.id=ezsearch_object_word_link.contentobject_id and
  822. ezcontentobject.contentclass_id = ezcontentclass.id and
  823. ezcontentclass.version = '0' and
  824. ezcontentobject.id = ezcontentobject_tree.contentobject_id and
  825. ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id
  826. $showInvisibleNodesCond
  827. $sqlPermissionChecking[where]",
  828. eZDBInterface::SERVER_SLAVE );
  829. }
  830. $i++;
  831. }
  832. else
  833. {
  834. $stopWordArray[] = array( 'word' => $searchPart['text'] );
  835. }
  836. }
  837. if ( count( $searchPartsArray ) === 0 && $this->TempTablesCount == 0 )
  838. {
  839. $table = $db->generateUniqueTempTableName( 'ezsearch_tmp_%', 0 );
  840. $this->saveCreatedTempTableName( 0, $table );
  841. $db->createTempTable( "CREATE TEMPORARY TABLE $table ( contentobject_id int primary key not null, published int )" );
  842. $db->query( "INSERT INTO $table SELECT DISTINCT ezsearch_object_word_link.contentobject_id, ezsearch_object_word_link.published
  843. FROM ezcontentobject,
  844. ezsearch_object_word_link
  845. $subTreeTable,
  846. ezcontentclass,
  847. ezcontentobject_tree
  848. $sqlPermissionChecking[from]
  849. WHERE
  850. $searchDateQuery
  851. $sectionQuery
  852. $classQuery
  853. $classAttributeQuery
  854. $subTreeSQL
  855. ezcontentobject.id=ezsearch_object_word_link.contentobject_id and
  856. ezcontentobject.contentclass_id = ezcontentclass.id and
  857. ezcontentclass.version = '0' and
  858. ezcontentobject.id = ezcontentobject_tree.contentobject_id and
  859. ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id
  860. $showInvisibleNodesCond
  861. $sqlPermissionChecking[where]",
  862. eZDBInterface::SERVER_SLAVE );
  863. $this->TempTablesCount = 1;
  864. $i = $this->TempTablesCount;
  865. }
  866. $nonExistingWordCount = count( array_unique( $searchWordArray ) ) - count( $wordIDHash ) - $wildCardCount;
  867. $excludeWordCount = $searchWordCount - count( $stopWordArray );
  868. if ( ( count( $stopWordArray ) + $nonExistingWordCount ) == $searchWordCount && $this->TempTablesCount == 0 )
  869. {
  870. // No words to search for, return empty result
  871. // cleanup temp tables
  872. $db->dropTempTableList( $sqlPermissionChecking['temp_tables'] );
  873. $db->dropTempTableList( $this->getSavedTempTablesList() );
  874. return array( "SearchResult" => array(),
  875. "SearchCount" => 0,
  876. "StopWordArray" => $stopWordArray );
  877. }
  878. $tmpTablesFrom = "";
  879. $tmpTablesWhere = "";
  880. /// tmp tables
  881. $tmpTableCount = $i;
  882. for ( $i = 0; $i < $tmpTableCount; $i++ )
  883. {
  884. $tmpTablesFrom .= $this->getSavedTempTableName( $i );
  885. if ( $i < ( $tmpTableCount - 1 ) )
  886. $tmpTablesFrom .= ", ";
  887. }
  888. $tmpTablesSeparator = '';
  889. if ( $tmpTableCount > 0 )
  890. {
  891. $tmpTablesSeparator = ',';
  892. }
  893. $tmpTable0 = $this->getSavedTempTableName( 0 );
  894. for ( $i = 1; $i < $tmpTableCount; $i++ )
  895. {
  896. $tmpTableI = $this->getSavedTempTableName( $i );
  897. $tmpTablesWhere .= " $tmpTable0.contentobject_id=$tmpTableI.contentobject_id ";
  898. if ( $i < ( $tmpTableCount - 1 ) )
  899. $tmpTablesWhere .= " AND ";
  900. }
  901. $tmpTablesWhereExtra = '';
  902. if ( $tmpTableCount > 0 )
  903. {
  904. $tmpTablesWhereExtra = "ezcontentobject.id=$tmpTable0.contentobject_id AND";
  905. }
  906. $and = "";
  907. if ( $tmpTableCount > 1 )
  908. $and = " AND ";
  909. // Generate ORDER BY SQL
  910. $orderBySQLArray = $this->buildSortSQL( $sortArray );
  911. $orderByFieldsSQL = $orderBySQLArray['sortingFields'];
  912. $sortWhereSQL = $orderBySQLArray['whereSQL'];
  913. $sortFromSQL = $orderBySQLArray['fromSQL'];
  914. // Fetch data from table
  915. $searchQuery ='';
  916. $dbName = $db->databaseName();
  917. if ( $dbName == 'mysql' )
  918. {
  919. $searchQuery = "SELECT DISTINCT ezcontentobject.*, ezcontentclass.serialized_name_list as class_serialized_name_list, ezcontentobject_tree.*
  920. $versionNameTargets
  921. FROM
  922. $tmpTablesFrom $tmpTablesSeparator
  923. ezcontentobject,
  924. ezcontentclass,
  925. ezcontentobject_tree
  926. $versionNameTables
  927. $sortFromSQL
  928. WHERE
  929. $tmpTablesWhere $and
  930. $tmpTablesWhereExtra
  931. ezcontentobject.contentclass_id = ezcontentclass.id and
  932. ezcontentclass.version = '0' and
  933. ezcontentobject.id = ezcontentobject_tree.contentobject_id and
  934. ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id
  935. $versionNameJoins
  936. $showInvisibleNodesCond
  937. $sortWhereSQL
  938. ORDER BY $orderByFieldsSQL";
  939. }
  940. else
  941. {
  942. $searchQuery = "SELECT DISTINCT ezcontentobject.*, ezcontentclass.serialized_name_list as class_serialized_name_list, ezcontentobject_tree.*
  943. $versionNameTargets
  944. FROM
  945. $tmpTablesFrom $tmpTablesSeparator
  946. ezcontentobject,
  947. ezcontentclass,
  948. ezcontentobject_tree
  949. $versionNameTables
  950. WHERE
  951. $tmpTablesWhere $and
  952. $tmpTablesWhereExtra
  953. ezcontentobject.contentclass_id = ezcontentclass.id and
  954. ezcontentclass.version = '0' and
  955. ezcontentobject.id = ezcontentobject_tree.contentobject_id and
  956. ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id
  957. $versionNameJoins
  958. ";
  959. }
  960. // Count query
  961. $languageCond = eZContentLanguage::languagesSQLFilter( 'ezcontentobject' );
  962. if ( $tmpTableCount == 0 )
  963. {
  964. $searchCountQuery = "SELECT count( DISTINCT ezcontentobject.id ) AS count
  965. FROM
  966. ezcontentobject,
  967. ezcontentobject_tree
  968. WHERE
  969. ezcontentobject.id = ezcontentobject_tree.contentobject_id and
  970. ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id
  971. AND $languageCond
  972. $showInvisibleNodesCond";
  973. }
  974. else
  975. {
  976. $searchCountQuery = "SELECT count( DISTINCT ezcontentobject.id ) AS count
  977. FROM $tmpTablesFrom $tmpTablesSeparator
  978. ezcontentobject
  979. WHERE $tmpTablesWhere $and
  980. $tmpTablesWhereExtra
  981. $languageCond";
  982. }
  983. $objectRes = array();
  984. $searchCount = 0;
  985. if ( $nonExistingWordCount <= 0 )
  986. {
  987. // execute search query
  988. $objectResArray = $db->arrayQuery( $searchQuery, array( "limit" => $searchLimit, "offset" => $searchOffset ), eZDBInterface::SERVER_SLAVE );
  989. // execute search count query
  990. $objectCountRes = $db->arrayQuery( $searchCountQuery, array(), eZDBInterface::SERVER_SLAVE );
  991. $objectRes = eZContentObjectTreeNode::makeObjectsArray( $objectResArray );
  992. $searchCount = $objectCountRes[0]['count'];
  993. }
  994. else
  995. $objectRes = array();
  996. // Drop tmp tables
  997. $db->dropTempTableList( $sqlPermissionChecking['temp_tables'] );
  998. $db->dropTempTableList( $this->getSavedTempTablesList() );
  999. return array( "SearchResult" => $objectRes,
  1000. "SearchCount" => $searchCount,
  1001. "StopWordArray" => $stopWordArray );
  1002. }
  1003. else
  1004. {
  1005. return array( "SearchResult" => array(),
  1006. "SearchCount" => 0,
  1007. "StopWordArray" => array() );
  1008. }
  1009. }
  1010. /*!
  1011. \private
  1012. \return an array of ORDER BY SQL
  1013. */
  1014. function buildSortSQL( $sortArray )
  1015. {
  1016. $sortCount = 0;
  1017. $sortList = false;
  1018. if ( isset( $sortArray ) and
  1019. is_array( $sortArray ) and
  1020. count( $sortArray ) > 0 )
  1021. {
  1022. $sortList = $sortArray;
  1023. if ( count( $sortList ) > 1 and
  1024. !is_array( $sortList[0] ) )
  1025. {
  1026. $sortList = array( $sortList );
  1027. }
  1028. }
  1029. $attributeJoinCount = 0;
  1030. $attributeFromSQL = "";
  1031. $attributeWereSQL = "";
  1032. if ( $sortList !== false )
  1033. {
  1034. $sortingFields = '';
  1035. foreach ( $sortList as $sortBy )
  1036. {
  1037. if ( is_array( $sortBy ) and count( $sortBy ) > 0 )
  1038. {
  1039. if ( $sortCount > 0 )
  1040. $sortingFields .= ', ';
  1041. $sortField = $sortBy[0];
  1042. switch ( $sortField )
  1043. {
  1044. case 'path':
  1045. {
  1046. $sortingFields .= 'path_string';
  1047. } break;
  1048. case 'published':
  1049. {
  1050. $sortingFields .= 'ezcontentobject.published';
  1051. } break;
  1052. case 'modified':
  1053. {
  1054. $sortingFields .= 'ezcontentobject.modified';
  1055. } break;
  1056. case 'section':
  1057. {
  1058. $sortingFields .= 'ezcontentobject.section_id';
  1059. } break;
  1060. case 'depth':
  1061. {
  1062. $sortingFields .= 'depth';
  1063. } break;
  1064. case 'class_identifier':
  1065. {
  1066. $sortingFields .= 'ezcontentclass.identifier';
  1067. } break;
  1068. case 'class_name':
  1069. {
  1070. $classNameFilter = eZContentClassName::sqlFilter();
  1071. $sortingFields .= $classNameFilter['nameField'];
  1072. $attributeFromSQL .= ", $classNameFilter[from]";
  1073. $attributeWhereSQL .= "$classNameFilter[where] AND ";
  1074. } break;
  1075. case 'priority':
  1076. {
  1077. $sortingFields .= 'ezcontentobject_tree.priority';
  1078. } break;
  1079. case 'name':
  1080. {
  1081. $sortingFields .= 'ezcontentobject_name.name';
  1082. } break;
  1083. case 'attribute':
  1084. {
  1085. $sortClassID = $sortBy[2];
  1086. // Look up datatype for sorting
  1087. if ( !is_numeric( $sortClassID ) )
  1088. {
  1089. $sortClassID = eZContentObjectTreeNode::classAttributeIDByIdentifier( $sortClassID );
  1090. }
  1091. $sortDataType = $sortClassID === false ? false : eZContentObjectTreeNode::sortKeyByClassAttributeID( $sortClassID );
  1092. $sortKey = false;
  1093. if ( $sortDataType == 'string' )
  1094. {
  1095. $sortKey = 'sort_key_string';
  1096. }
  1097. else
  1098. {
  1099. $sortKey = 'sort_key_int';
  1100. }
  1101. $sortingFields .= "a$attributeJoinCount.$sortKey";
  1102. $attributeFromSQL .= ", ezcontentobject_attribute as a$attributeJoinCount";
  1103. $attributeWereSQL .= " AND a$attributeJoinCount.contentobject_id = ezcontentobject.id AND
  1104. a$attributeJoinCount.contentclassattribute_id = $sortClassID AND
  1105. a$attributeJoinCount.version = ezcontentobject_name.content_version";
  1106. $attributeJoinCount++;
  1107. }break;
  1108. default:
  1109. {
  1110. eZDebug::writeWarning( 'Unknown sort field: ' . $sortField, __METHOD__ );
  1111. continue;
  1112. }
  1113. }
  1114. $sortOrder = true; // true is ascending
  1115. if ( isset( $sortBy[1] ) )
  1116. $sortOrder = $sortBy[1];
  1117. $sortingFields .= $sortOrder ? " ASC" : " DESC";
  1118. ++$sortCount;
  1119. }
  1120. }
  1121. }
  1122. // Should we sort?
  1123. if ( $sortCount == 0 )
  1124. {
  1125. $sortingFields = " ezcontentobject.published ASC";
  1126. }
  1127. return array( 'sortingFields' => $sortingFields,
  1128. 'fromSQL' => $attributeFromSQL,
  1129. 'whereSQL' => $attributeWereSQL );
  1130. }
  1131. /*!
  1132. \private
  1133. \return Returns an sql query part for one word
  1134. */
  1135. function buildSqlPartForWord( $wordID, $identifier = false )
  1136. {
  1137. $fullTextSQL = "ezsearch_object_word_link.word_id='$wordID' AND ";
  1138. if ( $identifier )
  1139. {
  1140. $fullTextSQL .= "ezsearch_object_word_link.identifier='$identifier' AND ";
  1141. }
  1142. return $fullTextSQL;
  1143. }
  1144. /*!
  1145. \private
  1146. \return Returns an sql query part for a phrase
  1147. */
  1148. function buildPhraseSqlQueryPart( $phraseIDArray, $identifier = false )
  1149. {
  1150. $phraseSearchSQLArray = array();
  1151. $wordCount = count( $phraseIDArray );
  1152. for ( $i = 0; $i < $wordCount; $i++ )
  1153. {
  1154. $wordID = $phraseIDArray[$i];
  1155. $phraseSearchSQL = "";
  1156. if ( is_numeric( $wordID ) and ( $wordID > 0 ) )
  1157. {
  1158. $phraseSearchSQL = " ( ezsearch_object_word_link.word_id='$wordID' ";
  1159. if ( $i < ( $wordCount - 1 ) )
  1160. {
  1161. $nextWordID = $phraseIDArray[$i+1];
  1162. $phraseSearchSQL .= " AND ezsearch_object_word_link.next_word_id='$nextWordID' ";
  1163. }
  1164. if ( $i > 0 )
  1165. {
  1166. $prevWordID = $phraseIDArray[$i-1];
  1167. $phraseSearchSQL .= " AND ezsearch_object_word_link.prev_word_id='$prevWordID' ";
  1168. }
  1169. if ( $identifier )
  1170. {
  1171. $phraseSearchSQL .= " AND ezsearch_object_word_link.identifier='$identifier' ";
  1172. }
  1173. $phraseSearchSQL .= " ) ";
  1174. }
  1175. else
  1176. {
  1177. $nonExistingWordArray[] = $searchWord;
  1178. }
  1179. $prevWord = $wordID;
  1180. $phraseSearchSQLArray[] = $phraseSearchSQL;
  1181. }
  1182. return $phraseSearchSQLArray;
  1183. }
  1184. /*!
  1185. \private
  1186. \return Returns an array of words created from the input string.
  1187. */
  1188. function splitString( $text )
  1189. {
  1190. // strip quotes
  1191. $text = preg_replace("#'#", "", $text );
  1192. $text = preg_replace( "#\"#", "", $text );
  1193. // Strip multiple whitespace
  1194. $text = trim( $text );
  1195. $text = preg_replace("(\s+)", " ", $text );
  1196. // Split text on whitespace
  1197. $wordArray = explode( ' ', $text );
  1198. $retArray = array();
  1199. foreach ( $wordArray as $word )
  1200. {
  1201. if ( trim( $word ) != "" )
  1202. {
  1203. $retArray[] = trim( $word );
  1204. }
  1205. }
  1206. return $retArray;
  1207. }
  1208. /*!
  1209. Normalizes the text \a $text so that it is easily parsable
  1210. \param $isMetaData If \c true then it expects the text to be meta data from objects,
  1211. if not it is the search text and needs special handling.
  1212. */
  1213. function normalizeText( $text, $isMetaData = false )
  1214. {
  1215. $trans = eZCharTransform::instance();
  1216. $text = $trans->transformByGroup( $text, 'search' );
  1217. // Remove quotes and asterix when not handling search text by end-user
  1218. if ( $isMetaData )
  1219. {
  1220. $text = str_replace( array( "\"", "*" ), array( " ", " " ), $text );
  1221. }
  1222. return $text;
  1223. }
  1224. /*!
  1225. \static
  1226. \return Returns an array describing the supported search types in thie search engine.
  1227. \note It has been renamed. In eZ Publish 3.4 and older it was (wrongly) named suportedSearchTypes().
  1228. */
  1229. function supportedSearchTypes()
  1230. {
  1231. $searchTypes = array( array( 'type' => 'attribute',
  1232. 'subtype' => 'fulltext',
  1233. 'params' => array( 'classattribute_id', 'value' ) ),
  1234. array( 'type' => 'attribute',
  1235. 'subtype' => 'patterntext',
  1236. 'params' => array( 'classattribute_id', 'value' ) ),
  1237. array( 'type' => 'attribute',
  1238. 'subtype' => 'integer',
  1239. 'params' => array( 'classattribute_id', 'value' ) ),
  1240. array( 'type' => 'attribute',
  1241. 'subtype' => 'integers',
  1242. 'params' => array( 'classattribute_id', 'values' ) ),
  1243. array( 'type' => 'attribute',
  1244. 'subtype' => 'byrange',
  1245. 'params' => array( 'classattribute_id', 'from' , 'to' ) ),
  1246. array( 'type' => 'attribute',
  1247. 'subtype' => 'byidentifier',
  1248. 'params' => array( 'classattribute_id', 'identifier', 'value' ) ),
  1249. array( 'type' => 'attribute',
  1250. 'subtype' => 'byidentifierrange',
  1251. 'params' => array( 'classattribute_id', 'identifier', 'from', 'to' ) ),
  1252. array( 'type' => 'attribute',
  1253. 'subtype' => 'integersbyidentifier',
  1254. 'params' => array( 'classattribute_id', 'identifier', 'values' ) ),
  1255. array( 'type' => 'fulltext',
  1256. 'subtype' => 'text',
  1257. 'params' => array( 'value' ) ) );
  1258. $generalSearchFilter = array( array( 'type' => 'general',
  1259. 'subtype' => 'class',
  1260. 'params' => array( array( 'type' => 'array',
  1261. 'value' => 'value'),
  1262. 'operator' ) ),
  1263. array( 'type' => 'general',
  1264. 'subtype' => 'publishdate',
  1265. 'params' => array( 'value', 'operator' ) ),
  1266. array( 'type' => 'general',
  1267. 'subtype' => 'subtree',
  1268. 'params' => array( array( 'type' => 'array',
  1269. 'value' => 'value'),
  1270. 'operator' ) ) );
  1271. return array( 'types' => $searchTypes,
  1272. 'general_filter' => $generalSearchFilter );
  1273. }
  1274. function searchAttributeInteger( $searchParams )
  1275. {
  1276. $classAttributeID = $searchParams['classattribute_id'];
  1277. $value = $searchParams['value'];
  1278. $classAttributeQuery = "";
  1279. if ( is_numeric( $classAttributeID ) and $classAttributeID > 0 )
  1280. {
  1281. $classAttributeQuery = "ezsearch_object_word_link.contentclass_attribute_id = '$classAttributeID' AND ";
  1282. }
  1283. $searchPartSql = " ezsearch_object_word_link.integer_value = $value AND";
  1284. $searchPartText = $classAttributeQuery . $searchPartSql;
  1285. $tableResult = $this->createTemporaryTable( $searchPartText );
  1286. if ( $tableResult === false )
  1287. {
  1288. return false;
  1289. }
  1290. else
  1291. {
  1292. return true;
  1293. }
  1294. }
  1295. function searchAttributeIntegers( $searchParams )
  1296. {
  1297. $classAttributeID = $searchParams['classattribute_id'];
  1298. $values = $searchParams['values'];
  1299. $classAttributeQuery = "";
  1300. if ( is_numeric( $classAttributeID ) and $classAttributeID > 0 )
  1301. {
  1302. $classAttributeQuery = "ezsearch_object_word_link.contentclass_attribute_id = '$classAttributeID' AND ";
  1303. }
  1304. $integerValuesSql = implode( ', ', $values );
  1305. $searchPartSql = " ezsearch_object_word_link.integer_value IN ( $integerValuesSql ) AND";
  1306. $searchPartText = $classAttributeQuery . $searchPartSql;
  1307. $tableResult = $this->createTemporaryTable( $searchPartText );
  1308. if ( $tableResult === false )
  1309. {
  1310. return false;
  1311. }
  1312. else
  1313. {
  1314. return true;
  1315. }
  1316. }
  1317. function searchAttributeByRange( $searchParams )
  1318. {
  1319. $classAttributeID = $searchParams['classattribute_id'];
  1320. $fromValue = $searchParams['from'];
  1321. $toValue = $searchParams['to'];
  1322. $classAttributeQuery = "";
  1323. if ( is_numeric( $classAttributeID ) and $classAttributeID > 0 )
  1324. {
  1325. $classAttributeQuery = "ezsearch_object_word_link.contentclass_attribute_id = '$classAttributeID' AND ";
  1326. }
  1327. $searchPartSql = " ezsearch_object_word_link.integer_value BETWEEN $fromValue AND $toValue AND";
  1328. $searchPartText = $classAttributeQuery . $searchPartSql;
  1329. $tableResult = $this->createTemporaryTable( $searchPartText );
  1330. if ( $tableResult === false )
  1331. {
  1332. return false;
  1333. }
  1334. else
  1335. {
  1336. return true;
  1337. }
  1338. }
  1339. function searchAttributeByIdentifier( $searchParams )
  1340. {
  1341. $identifier = $searchParams['identifier'];
  1342. $textValue = $searchParams['value'];
  1343. $searchText = $this->normalizeText( $textValue, false );
  1344. $phrasesResult = $this->getPhrases( $searchText );
  1345. $phraseTextArray = $phrasesResult['phrases'];
  1346. $nonPhraseText = $phrasesResult['nonPhraseText'];
  1347. $fullText = $phrasesResult['fullText'];
  1348. $totalObjectCount = $this->fetchTotalObjectCount();
  1349. $wordIDArrays = $this->prepareWordIDArrays( $searchText );
  1350. $wordIDArray = $wordIDArrays['wordIDArray'];
  1351. $wordIDHash = $wordIDArrays['wordIDHash'];
  1352. $wildIDArray = $wordIDArrays['wildIDArray'];
  1353. $searchWordArray = $this->splitString( $searchText );
  1354. $nonExistingWordCount = count( $searchWordArray ) - count( $wordIDHash );
  1355. if ( $nonExistingWordCount > 0 )
  1356. return false;
  1357. $searchPartsArray = $this->buildSearchPartArray( $phraseTextArray, $nonPhraseText, $wordIDHash,
  1358. $wildIDArray, $identifier );
  1359. $this->buildTempTablesForFullTextSearch( $searchPartsArray, array() );
  1360. $this->GeneralFilter['classAttributeQuery'] = '';
  1361. return true;
  1362. }
  1363. function searchAttributeByIdentifierRange( $searchParams )
  1364. {
  1365. $identifier = $searchParams['identifier'];
  1366. $fromValue = $searchParams['from'];
  1367. $toValue = $searchParams['to'];
  1368. $searchPartSql = " ezsearch_object_word_link.integer_value BETWEEN $fromValue AND $toValue AND ezsearch_object_word_link.identifier = '$identifier' AND";
  1369. $tableResult = $this->createTemporaryTable( $searchPartSql );
  1370. if ( $tableResult === false )
  1371. {
  1372. return false;
  1373. }
  1374. else
  1375. {
  1376. return true;
  1377. }
  1378. }
  1379. function searchAttributeIntegersByIdentifier( $searchParams )
  1380. {
  1381. $identifier = $searchParams['identifier'];
  1382. $values = $searchParams['values'];
  1383. $integerValuesSql = implode( ', ', $values );
  1384. $searchPartSql = " ezsearch_object_word_link.integer_value IN ( $integerValuesSql ) AND ezsearch_object_word_link.identifier = '$identifier' AND";
  1385. $tableResult = $this->createTemporaryTable( $searchPartSql );
  1386. if ( $tableResult === false )
  1387. {
  1388. return false;
  1389. }
  1390. else
  1391. {
  1392. return true;
  1393. }
  1394. }
  1395. function searchAttributePatternText( $searchParams )
  1396. {
  1397. $classAttributeID = $searchParams['classattribute_id'];
  1398. $textValue = $searchParams['value'];
  1399. // $searchText = $this->normalizeText( $textValue );
  1400. $searchText = $textValue;
  1401. $classAttributeQuery = "";
  1402. if ( is_numeric( $classAttributeID ) and $classAttributeID > 0 )
  1403. {
  1404. $classAttributeQuery = "ezsearch_object_word_link.contentclass_attribute_id = '$classAttributeID' AND ";
  1405. $this->GeneralFilter['classAttributeQuery'] = $classAttributeQuery;
  1406. }
  1407. $wordIDArrays = $this->prepareWordIDArraysForPattern( $searchText );
  1408. $wordIDArray = $wordIDArrays['wordIDArray'];
  1409. $wordIDHash = $wordIDArrays['wordIDHash'];
  1410. $wildIDArray = array();
  1411. $patternWordIDHash = $wordIDArrays['patternWordIDHash'];
  1412. $searchWordArray = $this->splitString( $searchText );
  1413. $nonExistingWordCount = count( $searchWordArray ) - count( $wordIDHash ) - count( $patternWordIDHash );
  1414. if ( $nonExistingWordCount > 0 )
  1415. return false;
  1416. preg_replace( "/(\w+\*\s)/", " ", $searchText );
  1417. $nonPhraseText = $this->normalizeText( $searchText, false );
  1418. $searchPartsArray = $this->buildSearchPartArrayForWords( $nonPhraseText, $wordIDHash, $wildIDArray );
  1419. foreach ( $patternWordIDHash as $patternWord )
  1420. {
  1421. $searchPart = '( ';
  1422. $i = 0;
  1423. foreach ( $patternWord as $word )
  1424. {
  1425. if ( $i > 0 )
  1426. $searchPart .= ' or ';
  1427. $wordID = $word['id'];
  1428. $searchPart .= "ezsearch_object_word_link.word_id='$wordID' ";
  1429. $i++;
  1430. }
  1431. $searchPart .= ' ) AND ';
  1432. $this->createTemporaryTable( $searchPart );
  1433. }
  1434. $this->buildTempTablesForFullTextSearch( $searchPartsArray, array() );
  1435. $this->GeneralFilter['classAttributeQuery'] = '';
  1436. return true;
  1437. }
  1438. function searchAttributeFulltext( $searchParams )
  1439. {
  1440. $classAttributeID = $searchParams['classattribute_id'];
  1441. $textValue = $searchParams['value'];
  1442. $searchText = $this->normalizeText( $textValue, false );
  1443. $phrasesResult = $this->getPhrases( $searchText );
  1444. $phraseTextArray = $phrasesResult['phrases'];
  1445. $nonPhraseText = $phrasesResult['nonPhraseText'];
  1446. $fullText = $phrasesResult['fullText'];
  1447. $classAttributeQuery = "";
  1448. if ( is_numeric( $classAttributeID ) and $classAttributeID > 0 )
  1449. {
  1450. $classAttributeQuery = "ezsearch_object_word_link.contentclass_attribute_id = '$classAttributeID' AND ";
  1451. $this->GeneralFilter['classAttributeQuery'] = $classAttributeQuery;
  1452. }
  1453. $totalObjectCount = $this->fetchTotalObjectCount();
  1454. $wordIDArrays = $this->prepareWordIDArrays( $searchText );
  1455. $wordIDArray = $wordIDArrays['wordIDArray'];
  1456. $wordIDHash = $wordIDArrays['wordIDHash'];
  1457. $wildIDArray = $wordIDArrays['wildIDArray'];
  1458. $searchWordArray = $this->splitString( $searchText );
  1459. $nonExistingWordCount = count( $searchWordArray ) - count( $wordIDHash );
  1460. if ( $nonExistingWordCount > 0 )
  1461. return false;
  1462. $searchPartsArray = $this->buildSearchPartArray( $phraseTextArray, $nonPhraseText,
  1463. $wordIDHash, $wildIDArray );
  1464. $this->buildTempTablesForFullTextSearch( $searchPartsArray, array() );
  1465. $this->GeneralFilter['classAttributeQuery'] = '';
  1466. return true;
  1467. }
  1468. function createTemporaryTable( $searchPartText )
  1469. {
  1470. $subTreeTable = $this->GeneralFilter['subTreeTable'];
  1471. $searchDateQuery = $this->GeneralFilter['searchDateQuery'];
  1472. $sectionQuery = $this->GeneralFilter['sectionQuery'];
  1473. $classQuery = $this->GeneralFilter['classQuery'];
  1474. $classAttributeQuery = $this->GeneralFilter['classAttributeQuery'];
  1475. // $searchPartText = $this->GeneralFilter['searchPartText'];
  1476. $subTreeSQL = $this->GeneralFilter['subTreeSQL'];
  1477. $sqlPermissionChecking = $this->GeneralFilter['sqlPermissionChecking'];
  1478. $db = eZDB::instance();
  1479. $i = $this->TempTablesCount;
  1480. if ( $i == 0 )
  1481. {
  1482. $table = $db->generateUniqueTempTableName( 'ezsearch_tmp_%', 0 );
  1483. $this->saveCreatedTempTableName( 0, $table );
  1484. $db->createTempTable( "CREATE TEMPORARY TABLE $table ( contentobject_id int primary key not null, published int )" );
  1485. $db->query( "INSERT INTO $table SELECT DISTINCT ezsearch_object_word_link.contentobject_id, ezsearch_object_word_link.published
  1486. FROM
  1487. ezcontentobject,
  1488. ezsearch_object_word_link
  1489. $subTreeTable,
  1490. ezcontentclass,
  1491. ezcontentobject_tree
  1492. $sqlPermissionChecking[from]
  1493. WHERE
  1494. $searchDateQuery
  1495. $sectionQuery
  1496. $classQuery
  1497. $classAttributeQuery
  1498. $searchPartText
  1499. $subTreeSQL
  1500. ezcontentobject.id=ezsearch_object_word_link.contentobject_id and
  1501. ezcontentobject.contentclass_id = ezcontentclass.id and
  1502. ezcontentclass.version = '0' and
  1503. ezcontentobject.id = ezcontentobject_tree.contentobject_id and
  1504. ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id
  1505. $sqlPermissionChecking[where]",
  1506. eZDBInterface::SERVER_SLAVE );
  1507. }
  1508. else
  1509. {
  1510. $table = $db->generateUniqueTempTableName( 'ezsearch_tmp_%_0', $i );
  1511. $this->saveCreatedTempTableName( $i, $table );
  1512. $tmpTable0 = $this->getSavedTempTableName( 0 );
  1513. $db->createTempTable( "CREATE TEMPORARY TABLE $table ( contentobject_id int primary key not null, published int )" );
  1514. $db->query( "INSERT INTO $table SELECT DISTINCT ezsearch_object_word_link.contentobject_id, ezsearch_object_word_link.published
  1515. FROM
  1516. ezcontentobject,
  1517. ezsearch_object_word_link
  1518. $subTreeTable,
  1519. ezcontentclass,
  1520. ezcontentobject_tree,
  1521. $tmpTable0
  1522. $sqlPermissionChecking[from]
  1523. WHERE
  1524. $tmpTable0.contentobject_id=ezsearch_object_word_link.contentobject_id AND
  1525. $searchDateQuery
  1526. $sectionQuery
  1527. $classQuery
  1528. $classAttributeQuery
  1529. $searchPartText
  1530. $subTreeSQL
  1531. ezcontentobject.id=ezsearch_object_word_link.contentobject_id and
  1532. ezcontentobject.contentclass_id = ezcontentclass.id and
  1533. ezcontentclass.version = '0' and
  1534. ezcontentobject.id = ezcontentobject_tree.contentobject_id and
  1535. ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id
  1536. $sqlPermissionChecking[where]",
  1537. eZDBInterface::SERVER_SLAVE );
  1538. }
  1539. $tmpTableI = $this->getSavedTempTableName( $i );
  1540. $insertedCountArray = $db->arrayQuery( "SELECT count(*) as count from $tmpTableI " );
  1541. $i++;
  1542. $this->TempTablesCount++;
  1543. if ( $insertedCountArray[0]['count'] == 0 )
  1544. {
  1545. return false;
  1546. }
  1547. else
  1548. {
  1549. return $insertedCountArray[0]['count'];
  1550. }
  1551. }
  1552. function buildTempTablesForFullTextSearch( $searchPartsArray, $generalFilterList = array() )
  1553. {
  1554. $ini = eZINI::instance();
  1555. $db = eZDB::instance();
  1556. $i = $this->TempTablesCount;
  1557. $generalFilterList = $this->GeneralFilter;
  1558. if ( isset( $generalFilterList['searchDateQuery'] ) and
  1559. isset( $generalFilterList['publish_date'] ) )
  1560. $searchDateQuery = $generalFilterList['publish_date'];
  1561. else
  1562. $searchDateQuery = '';
  1563. if ( isset( $generalFilterList['sectionQuery'] ) )
  1564. $sectionQuery = $generalFilterList['sectionQuery'];
  1565. else
  1566. $sectionQuery = '';
  1567. if ( isset( $generalFilterList['classQuery'] ) )
  1568. $classQuery = $generalFilterList['classQuery'];
  1569. else
  1570. $classQuery = '';
  1571. if ( isset( $generalFilterList['classAttributeQuery'] ) )
  1572. $classAttributeQuery = $generalFilterList[ 'classAttributeQuery'];
  1573. else
  1574. $classAttributeQuery = '';
  1575. if ( isset( $generalFilterList['sqlPermissionChecking'] ) )
  1576. $sqlPermissionChecking = $generalFilterList['sqlPermissionChecking'];
  1577. else
  1578. $sqlPermissionChecking = array( 'from' => '',
  1579. 'where' => '' );
  1580. if ( isset( $generalFilterList['subTreeSQL'] ) )
  1581. {
  1582. $subTreeTable = $generalFilterList['subTreeTable'];
  1583. $subTreeSQL = $generalFilterList['subTreeSQL'];
  1584. }
  1585. else
  1586. {
  1587. $subTreeTable = '';
  1588. $subTreeSQL = '';
  1589. }
  1590. $totalObjectCount = $this->fetchTotalObjectCount();
  1591. $stopWordThresholdValue = 100;
  1592. if ( $ini->hasVariable( 'SearchSettings', 'StopWordThresholdValue' ) )
  1593. $stopWordThresholdValue = $ini->variable( 'SearchSettings', 'StopWordThresholdValue' );
  1594. $stopWordThresholdPercent = 60;
  1595. if ( $ini->hasVariable( 'SearchSettings', 'StopWordThresholdPercent' ) )
  1596. $stopWordThresholdPercent = $ini->variable( 'SearchSettings', 'StopWordThresholdPercent' );
  1597. $searchThresholdValue = $totalObjectCount;
  1598. if ( $totalObjectCount > $stopWordThresholdValue )
  1599. {
  1600. $searchThresholdValue = (int)( $totalObjectCount * ( $stopWordThresholdPercent / 100 ) );
  1601. }
  1602. foreach ( $searchPartsArray as $searchPart )
  1603. {
  1604. // $wordID = $searchWord['id'];
  1605. // do not search words that are too frequent
  1606. if ( $searchPart['object_count'] < $searchThresholdValue )
  1607. {
  1608. // $tmpTableCount++;
  1609. $searchPartText = $searchPart['sql_part'];
  1610. if ( $i == 0 )
  1611. {
  1612. $table = $db->generateUniqueTempTableName( 'ezsearch_tmp_%', 0 );
  1613. $this->saveCreatedTempTableName( 0, $table );
  1614. $db->createTempTable( "CREATE TEMPORARY TABLE $table ( contentobject_id int primary key not null, published int )" );
  1615. $db->query( "INSERT INTO $table SELECT DISTINCT ezsearch_object_word_link.contentobject_id, ezsearch_object_word_link.published
  1616. FROM
  1617. ezcontentobject,
  1618. ezsearch_object_word_link
  1619. $subTreeTable,
  1620. ezcontentclass,
  1621. ezcontentobject_tree
  1622. $sqlPermissionChecking[from]
  1623. WHERE
  1624. $searchDateQuery
  1625. $sectionQuery
  1626. $classQuery
  1627. $classAttributeQuery
  1628. $searchPartText
  1629. $subTreeSQL
  1630. ezcontentobject.id=ezsearch_object_word_link.contentobject_id and
  1631. ezcontentobject.contentclass_id = ezcontentclass.id and
  1632. ezcontentclass.version = '0' and
  1633. ezcontentobject.id = ezcontentobject_tree.contentobject_id and
  1634. ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id
  1635. $sqlPermissionChecking[where]",
  1636. eZDBInterface::SERVER_SLAVE );
  1637. }
  1638. else
  1639. {
  1640. $table = $db->generateUniqueTempTableName( 'ezsearch_tmp_%', $i );
  1641. $this->saveCreatedTempTableName( $i, $table );
  1642. $tmpTable0 = $this->getSavedTempTableName( 0 );
  1643. $db->createTempTable( "CREATE TEMPORARY TABLE $table ( contentobject_id int primary key not null, published int )" );
  1644. $db->query( "INSERT INTO $table SELECT DISTINCT ezsearch_object_word_link.contentobject_id, ezsearch_object_word_link.published
  1645. FROM
  1646. ezcontentobject,
  1647. ezsearch_object_word_link
  1648. $subTreeTable,
  1649. ezcontentclass,
  1650. ezcontentobject_tree,
  1651. $tmpTable0
  1652. $sqlPermissionChecking[from]
  1653. WHERE
  1654. $tmpTable0.contentobject_id=ezsearch_object_word_link.contentobject_id AND
  1655. $searchDateQuery
  1656. $sectionQuery
  1657. $classQuery
  1658. $classAttributeQuery
  1659. $searchPartText
  1660. $subTreeSQL
  1661. ezcontentobject.id=ezsearch_object_word_link.contentobject_id and
  1662. ezcontentobject.contentclass_id = ezcontentclass.id and
  1663. ezcontentclass.version = '0' and
  1664. ezcontentobject.id = ezcontentobject_tree.contentobject_id and
  1665. ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id
  1666. $sqlPermissionChecking[where]",
  1667. eZDBInterface::SERVER_SLAVE );
  1668. }
  1669. $i++;
  1670. }
  1671. else
  1672. {
  1673. $stopWordArray[] = array( 'word' => $searchPart['word'] );
  1674. }
  1675. }
  1676. $this->TempTablesCount = $i;
  1677. }
  1678. function getPhrases( $searchText )
  1679. {
  1680. $numQuotes = substr_count( $searchText, "\"" );
  1681. $phraseTextArray = array();
  1682. $fullText = $searchText;
  1683. $nonPhraseText ='';
  1684. $postPhraseText = $fullText;
  1685. $pos = 0;
  1686. if ( ( $numQuotes > 0 ) and ( ( $numQuotes % 2 ) == 0 ) )
  1687. {
  1688. for ( $i = 0; $i < ( $numQuotes / 2 ); $i ++ )
  1689. {
  1690. $quotePosStart = strpos( $searchText, '"', $pos );
  1691. $quotePosEnd = strpos( $searchText, '"', $quotePosStart + 1 );
  1692. $prePhraseText = substr( $searchText, $pos, $quotePosStart - $pos );
  1693. $postPhraseText = substr( $searchText, $quotePosEnd +1 );
  1694. $phraseText = substr( $searchText, $quotePosStart + 1, $quotePosEnd - $quotePosStart - 1 );
  1695. $phraseTextArray[] = $phraseText;
  1696. $nonPhraseText .= $prePhraseText;
  1697. $pos = $quotePosEnd + 1;
  1698. }
  1699. }
  1700. $nonPhraseText .= $postPhraseText;
  1701. return array( 'phrases' => $phraseTextArray,
  1702. 'nonPhraseText' => $nonPhraseText,
  1703. 'fullText' => $fullText );
  1704. }
  1705. function buildSearchPartArray( $phraseTextArray, $nonPhraseText, $wordIDHash, $wildIDArray,
  1706. $identifier = false )
  1707. {
  1708. $searchPartsArrayForPhrases = $this->buildSearchPartArrayForPhrases( $phraseTextArray, $wordIDHash,
  1709. $identifier );
  1710. $searchPartsArrayForWords = $this->buildSearchPartArrayForWords( $nonPhraseText, $wordIDHash,
  1711. $wildIDArray, $identifier );
  1712. $searchPartsArray = array_merge( $searchPartsArrayForPhrases, $searchPartsArrayForWords );
  1713. return $searchPartsArray;
  1714. }
  1715. function buildSearchPartArrayForWords( $nonPhraseText, $wordIDHash, $wildIDArray, $identifier = false )
  1716. {
  1717. $searchPartsArray = array();
  1718. $nonPhraseWordArray = $this->splitString( $nonPhraseText );
  1719. $uniqueWordArray = array();
  1720. $searchPart = array();
  1721. if ( isset( $wildIDArray ) && count( $wildIDArray ) > 0 )
  1722. {
  1723. $searchPart['sql_part'] = '( ';
  1724. $i = 0;
  1725. $objectCount = -1;
  1726. foreach( $wildIDArray as $wordInfo )
  1727. {
  1728. if ( $i > 0 )
  1729. $searchPart['sql_part'] .= ' or ';
  1730. $searchPart['sql_part'] .= "ezsearch_object_word_link.word_id='". $wordInfo['id'] ."'";
  1731. if ( $objectCount < intval($wordInfo['object_count']) )
  1732. $objectCount = intval($wordInfo['object_count']);
  1733. $i++;
  1734. }
  1735. $searchPart['sql_part'] .= ' ) AND ';
  1736. $searchPart['object_count'] = $objectCount;
  1737. $searchPart['is_phrase'] = 0;
  1738. $searchPartsArray[] = $searchPart;
  1739. unset ( $searchPart );
  1740. }
  1741. foreach( array_keys( $wordIDHash ) as $word )
  1742. {
  1743. $searchPart = array();
  1744. $searchPart['text'] = $word;
  1745. $wordID = $wordIDHash[$word]['id'];
  1746. $searchPart['sql_part'] = $this->buildSqlPartForWord( $wordID, $identifier );
  1747. $searchPart['is_phrase'] = 0;
  1748. $searchPart['object_count'] = $wordIDHash[$word]['object_count'];
  1749. $searchPartsArray[] = $searchPart;
  1750. unset ( $searchPart );
  1751. }
  1752. return $searchPartsArray;
  1753. }
  1754. function buildSearchPartArrayForPhrases( $phraseTextArray, $wordIDHash, $identifier = false )
  1755. {
  1756. // build an array of the word id's for each phrase
  1757. $phraseIDArrayArray = array();
  1758. foreach ( $phraseTextArray as $phraseText )
  1759. {
  1760. $wordArray = $this->splitString( $phraseText );
  1761. $wordIDArray = array();
  1762. foreach ( $wordArray as $word )
  1763. {
  1764. if ( !isset( $wordIDHash[$word] ) ) return array();
  1765. $wordIDArray[] = $wordIDHash[$word]['id'];
  1766. }
  1767. $phraseIDArrayArray[] = $wordIDArray;
  1768. }
  1769. // build phrase SQL query part(s)
  1770. $phraseSearchSQLArray = array();
  1771. foreach ( $phraseIDArrayArray as $phraseIDArray )
  1772. {
  1773. $phraseSearchSQL = $this->buildPhraseSqlQueryPart( $phraseIDArray, $identifier );
  1774. $phraseSearchSQLArray[] = $phraseSearchSQL;
  1775. }
  1776. ///Build search parts array for phrases and normal words
  1777. $searchPartsArray = array();
  1778. $i = 0;
  1779. foreach ( $phraseTextArray as $phraseText )
  1780. {
  1781. foreach ( $phraseSearchSQLArray[$i] as $phraseSearchSubSQL )
  1782. {
  1783. $searchPart = array();
  1784. $searchPart['text'] = $phraseText;
  1785. $searchPart['sql_part'] = ' ( ' . $phraseSearchSubSQL . ' ) AND';
  1786. $searchPart['is_phrase'] = 1;
  1787. $searchPart['object_count'] = 0;
  1788. $searchPartsArray[] = $searchPart;
  1789. }
  1790. unset( $searchPart );
  1791. $i++;
  1792. }
  1793. return $searchPartsArray;
  1794. }
  1795. function prepareWordIDArraysForPattern( $searchText )
  1796. {
  1797. $db = eZDB::instance();
  1798. $searchWordArray = $this->splitString( $searchText );
  1799. // fetch the word id
  1800. $wordQueryString = '';
  1801. $patternWordsCount = 0;
  1802. $patternWordArray = array();
  1803. $wordsCount = 0;
  1804. $patternWordQueryString = '';
  1805. foreach ( $searchWordArray as $searchWord )
  1806. {
  1807. if ( preg_match ( "/(\w+)(\*)/", $searchWord, $matches ) )
  1808. {
  1809. if ( $patternWordsCount > 0 )
  1810. $patternWordQueryString .= " or ";
  1811. $patternWordArray[] = $matches[1];
  1812. $patternWordsCount++;
  1813. }
  1814. else
  1815. {
  1816. if ( $wordsCount > 0 )
  1817. $wordQueryString .= " or ";
  1818. $wordQueryString .= " word='$searchWord' ";
  1819. $wordsCount++;
  1820. }
  1821. }
  1822. // create the word hash
  1823. $wordIDArray = array();
  1824. $wordIDHash = array();
  1825. if ( $wordsCount > 0 )
  1826. {
  1827. $wordIDArrayRes = $db->arrayQuery( "SELECT id, word, object_count FROM ezsearch_word where $wordQueryString ORDER BY object_count" );
  1828. foreach ( $wordIDArrayRes as $wordRes )
  1829. {
  1830. $wordIDArray[] = $wordRes['id'];
  1831. $wordIDHash[$wordRes['word']] = array( 'id' => $wordRes['id'], 'word' => $wordRes['word'], 'object_count' => $wordRes['object_count'] );
  1832. }
  1833. }
  1834. $patternWordIDHash = array();
  1835. foreach ( $patternWordArray as $word )
  1836. {
  1837. $patternWordIDRes = $db->arrayQuery( "SELECT id, word, object_count FROM ezsearch_word where word like '" . $word . "%' order by object_count" );
  1838. $matchedWords = array();
  1839. foreach ( $patternWordIDRes as $wordRes )
  1840. {
  1841. $matchedWords[] = array( 'id' => $wordRes['id'], 'word' => $wordRes['word'], 'object_count' => $wordRes['object_count'] );
  1842. }
  1843. $patternWordIDHash[$word] = $matchedWords;
  1844. }
  1845. return array( 'wordIDArray' => $wordIDArray,
  1846. 'wordIDHash' => $wordIDHash,
  1847. 'patternWordIDHash' => $patternWordIDHash );
  1848. }
  1849. function prepareWordIDArrays( $searchText )
  1850. {
  1851. if ( trim( $searchText ) == "" )
  1852. {
  1853. $ini = eZINI::instance();
  1854. if ( $ini->hasVariable( 'SearchSettings', 'AllowEmptySearch' ) and
  1855. $ini->variable( 'SearchSettings', 'AllowEmptySearch' ) != 'enabled' )
  1856. return array();
  1857. }
  1858. $db = eZDB::instance();
  1859. //extend search words for urls, by extracting parts without www. and add to the end of search text
  1860. $matches = array();
  1861. if ( preg_match_all("/www\.([^\.]+\.[^\s]+)\s?/i", $searchText, $matches ) );
  1862. {
  1863. if (isset($matches[1]) )
  1864. {
  1865. $searchText.= ' '.join(' ', $matches[1] );
  1866. }
  1867. }
  1868. $searchWordArray = $this->splitString( $searchText );
  1869. $wildCardWordArray = array();
  1870. $i = 0;
  1871. $wildCardQueryString = array();
  1872. $wordQueryString = '';
  1873. $ini = eZINI::instance();
  1874. $wildSearchEnabled = ( $ini->variable( 'SearchSettings', 'EnableWildcard' ) == 'true' );
  1875. if ( $wildSearchEnabled )
  1876. {
  1877. $minCharacters = $ini->variable( 'SearchSettings', 'MinCharacterWildcard' );
  1878. }
  1879. foreach ( $searchWordArray as $searchWord )
  1880. {
  1881. $wordLength = strlen( $searchWord ) - 1;
  1882. if ( $wildSearchEnabled && ( $wordLength >= $minCharacters ) )
  1883. {
  1884. if ( $searchWord[$wordLength] == '*' )
  1885. {
  1886. $baseWord = substr( $searchWord, 0, $wordLength );
  1887. $wildCardQueryString[] = " word LIKE '". $baseWord ."%' ";
  1888. continue;
  1889. }
  1890. else if ( $searchWord[0] == '*' ) /* Change this to allow searching for shorter/longer words using wildcard */
  1891. {
  1892. $baseWord = substr( $searchWord, 1, $wordLength );
  1893. $wildCardQueryString[] = " word LIKE '%". $baseWord ."' ";
  1894. continue;
  1895. }
  1896. }
  1897. if ( $i > 0 )
  1898. $wordQueryString .= " or ";
  1899. $wordQueryString .= " word='$searchWord' ";
  1900. $i++;
  1901. }
  1902. if ( strlen( $wordQueryString ) > 0 )
  1903. $wordIDArrayRes = $db->arrayQuery( "SELECT id, word, object_count FROM ezsearch_word where $wordQueryString ORDER BY object_count" );
  1904. foreach ( $wildCardQueryString as $wildCardQuery )
  1905. {
  1906. $wildCardWordArray[] = $db->arrayQuery( "SELECT id, word, object_count FROM ezsearch_word where $wildCardQuery order by object_count" );
  1907. }
  1908. // create the word hash
  1909. $wordIDArray = array();
  1910. $wordIDHash = array();
  1911. if ( isset( $wordIDArrayRes ) && is_array( $wordIDArrayRes ) )
  1912. {
  1913. foreach ( $wordIDArrayRes as $wordRes )
  1914. {
  1915. $wordIDArray[] = $wordRes['id'];
  1916. $wordIDHash[$wordRes['word']] = array( 'id' => $wordRes['id'], 'word' => $wordRes['word'], 'object_count' => $wordRes['object_count'] );
  1917. }
  1918. }
  1919. $wildIDArray = array();
  1920. $wildCardCount = 0;
  1921. foreach ( array_keys( $wildCardWordArray ) as $key )
  1922. {
  1923. if ( is_array( $wildCardWordArray[$key] ) && count( $wildCardWordArray[$key] ) > 0 )
  1924. {
  1925. $wildCardCount++;
  1926. foreach ( $wildCardWordArray[$key] as $wordRes )
  1927. {
  1928. $wildIDArray[] = array( 'id' => $wordRes['id'], 'object_count' => $wordRes['object_count'] );
  1929. }
  1930. }
  1931. }
  1932. return array( 'wordIDArray' => $wordIDArray,
  1933. 'wordIDHash' => $wordIDHash,
  1934. 'wildIDArray' => $wildIDArray,
  1935. 'wildCardCount' => $wildCardCount );
  1936. }
  1937. function fetchTotalObjectCount()
  1938. {
  1939. // Get the total number of objects
  1940. $db = eZDB::instance();
  1941. $objectCount = array();
  1942. $objectCount = $db->arrayQuery( "SELECT COUNT(*) AS count FROM ezcontentobject" );
  1943. $totalObjectCount = $objectCount[0]["count"];
  1944. return $totalObjectCount;
  1945. }
  1946. function constructMethodName( $searchTypeData )
  1947. {
  1948. $type = $searchTypeData['type'];
  1949. $subtype = $searchTypeData['subtype'];
  1950. $methodName = 'search' . $type . $subtype;
  1951. return $methodName;
  1952. }
  1953. function callMethod( $methodName, $parameterArray )
  1954. {
  1955. if ( !method_exists( $this, $methodName ) )
  1956. {
  1957. eZDebug::writeError( $methodName, "Method does not exist in ez search engine" );
  1958. return false;
  1959. }
  1960. return call_user_func_array( array( $this, $methodName ), $parameterArray );
  1961. }
  1962. /*!
  1963. Will remove all search words and object/word relations.
  1964. */
  1965. function cleanup()
  1966. {
  1967. $db = eZDB::instance();
  1968. $db->begin();
  1969. $db->query( "DELETE FROM ezsearch_word" );
  1970. $db->query( "DELETE FROM ezsearch_object_word_link" );
  1971. $db->commit();
  1972. }
  1973. /*!
  1974. \return true if the search part is incomplete.
  1975. */
  1976. function isSearchPartIncomplete( $part )
  1977. {
  1978. switch ( $part['subtype'] )
  1979. {
  1980. case 'fulltext':
  1981. {
  1982. if ( !isset( $part['value'] ) || $part['value'] == '' )
  1983. return true;
  1984. }
  1985. break;
  1986. case 'patterntext':
  1987. {
  1988. if ( !isset( $part['value'] ) || $part['value'] == '' )
  1989. return true;
  1990. }
  1991. break;
  1992. case 'integer':
  1993. {
  1994. if ( !isset( $part['value'] ) || $part['value'] == '' )
  1995. return true;
  1996. }
  1997. break;
  1998. case 'integers':
  1999. {
  2000. if ( !isset( $part['values'] ) || count( $part['values'] ) == 0 )
  2001. return true;
  2002. }
  2003. break;
  2004. case 'byrange':
  2005. {
  2006. if ( !isset( $part['from'] ) || $part['from'] == '' ||
  2007. !isset( $part['to'] ) || $part['to'] == '' )
  2008. return true;
  2009. }
  2010. break;
  2011. case 'byidentifier':
  2012. {
  2013. if ( !isset( $part['value'] ) || $part['value'] == '' )
  2014. return true;
  2015. }
  2016. break;
  2017. case 'byidentifierrange':
  2018. {
  2019. if ( !isset( $part['from'] ) || $part['from'] == '' ||
  2020. !isset( $part['to'] ) || $part['to'] == '' )
  2021. return true;
  2022. }
  2023. break;
  2024. case 'integersbyidentifier':
  2025. {
  2026. if ( !isset( $part['values'] ) || count( $part['values'] ) == 0 )
  2027. return true;
  2028. }
  2029. break;
  2030. case 'byarea':
  2031. {
  2032. if ( !isset( $part['from'] ) || $part['from'] == '' ||
  2033. !isset( $part['to'] ) || $part['to'] == '' ||
  2034. !isset( $part['minvalue'] ) || $part['minvalue'] == '' ||
  2035. !isset( $part['maxvalue'] ) || $part['maxvalue'] == '' )
  2036. {
  2037. return true;
  2038. }
  2039. }
  2040. }
  2041. return false;
  2042. }
  2043. public $TempTablesCount = 0;
  2044. public $CreatedTempTablesNames = array();
  2045. }
  2046. ?>