PageRenderTime 31ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

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

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