PageRenderTime 50ms CodeModel.GetById 18ms 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

Large files files are truncated, but you can click here to view the full file

  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. $s

Large files files are truncated, but you can click here to view the full file