PageRenderTime 39ms CodeModel.GetById 4ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://github.com/granitegreg/ezpublish
PHP | 2310 lines | 1928 code | 180 blank | 202 comment | 202 complexity | 6df2b768928742d8d1aebcc682e7e6ff MD5 | raw file
Possible License(s): GPL-2.0

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

  1. <?php
  2. //
  3. // Definition of eZSearchEngine class
  4. //
  5. // Created on: <25-Jun-2002 13:09:57 bf>
  6. //
  7. // ## BEGIN COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
  8. // SOFTWARE NAME: eZ Publish
  9. // SOFTWARE RELEASE: 4.1.x
  10. // COPYRIGHT NOTICE: Copyright (C) 1999-2011 eZ Systems AS
  11. // SOFTWARE LICENSE: GNU General Public License v2.0
  12. // NOTICE: >
  13. // This program is free software; you can redistribute it and/or
  14. // modify it under the terms of version 2.0 of the GNU General
  15. // Public License as published by the Free Software Foundation.
  16. //
  17. // This program is distributed in the hope that it will be useful,
  18. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. // GNU General Public License for more details.
  21. //
  22. // You should have received a copy of version 2.0 of the GNU General
  23. // Public License along with this program; if not, write to the Free
  24. // Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  25. // MA 02110-1301, USA.
  26. //
  27. //
  28. // ## END COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
  29. //
  30. /*!
  31. \class eZSearchEngine ezsearch.php
  32. */
  33. class eZSearchEngine
  34. {
  35. function eZSearchEngine()
  36. {
  37. $generalFilter = array( 'subTreeTable' => '',
  38. 'searchDateQuery' => '',
  39. 'sectionQuery' => '',
  40. 'classQuery' => '',
  41. 'classAttributeQuery' => '',
  42. 'searchPartText' => '',
  43. 'subTreeSQL' => '',
  44. 'sqlPermissionChecking' => array( 'from' => '',
  45. 'where' => '' ) );
  46. $this->GeneralFilter = $generalFilter;
  47. }
  48. static function needCommit()
  49. {
  50. //commits are NA
  51. return false;
  52. }
  53. static function needRemoveWithUpdate()
  54. {
  55. return true;
  56. }
  57. /*!
  58. Adds an object to the search database.
  59. */
  60. function addObject( $contentObject, $commit )
  61. {
  62. $contentObjectID = $contentObject->attribute( 'id' );
  63. $currentVersion = $contentObject->currentVersion();
  64. if ( !$currentVersion )
  65. {
  66. $errCurrentVersion = $contentObject->attribute( 'current_version');
  67. eZDebug::writeError( "Failed to fetch \"current version\" ({$errCurrentVersion})" .
  68. " of content object (ID: {$contentObjectID})", 'eZSearchEngine' );
  69. return;
  70. }
  71. $indexArray = array();
  72. $indexArrayOnlyWords = array();
  73. $wordCount = 0;
  74. $placement = 0;
  75. $previousWord = '';
  76. eZContentObject::recursionProtectionStart();
  77. foreach ( $currentVersion->contentObjectAttributes() as $attribute )
  78. {
  79. $metaData = array();
  80. $classAttribute = $attribute->contentClassAttribute();
  81. if ( $classAttribute->attribute( "is_searchable" ) == 1 )
  82. {
  83. // Fetch attribute translations
  84. $attributeTranslations = $attribute->fetchAttributeTranslations();
  85. foreach ( $attributeTranslations as $translation )
  86. {
  87. $tmpMetaData = $translation->metaData();
  88. if( ! is_array( $tmpMetaData ) )
  89. {
  90. $tmpMetaData = array( array( 'id' => $attribute->attribute( 'contentclass_attribute_identifier' ),
  91. 'text' => $tmpMetaData ) );
  92. }
  93. $metaData = array_merge( $metaData, $tmpMetaData );
  94. }
  95. foreach( $metaData as $metaDataPart )
  96. {
  97. $text = eZSearchEngine::normalizeText( htmlspecialchars ($metaDataPart['text'], ENT_NOQUOTES, 'UTF-8' ) , true );
  98. // Split text on whitespace
  99. if ( is_numeric( trim( $text ) ) )
  100. {
  101. $integerValue = (int) $text;
  102. }
  103. else
  104. {
  105. $integerValue = 0;
  106. }
  107. $wordArray = explode( ' ', $text );
  108. foreach ( $wordArray as $word )
  109. {
  110. if ( trim( $word ) != "" )
  111. {
  112. // words stored in search index are limited to 150 characters
  113. if ( strlen( $word ) > 150 )
  114. {
  115. $word = substr( $word, 0, 150 );
  116. }
  117. $indexArray[] = array( 'Word' => $word,
  118. 'ContentClassAttributeID' => $attribute->attribute( 'contentclassattribute_id' ),
  119. 'identifier' => $metaDataPart['id'],
  120. 'integer_value' => $integerValue );
  121. $indexArrayOnlyWords[$word] = 1;
  122. $wordCount++;
  123. //if we have "www." before word than
  124. //treat it as url and add additional entry to the index
  125. if ( substr( strtolower($word), 0, 4 ) == 'www.' )
  126. {
  127. $additionalUrlWord = substr( $word, 4 );
  128. $indexArray[] = array( 'Word' => $additionalUrlWord,
  129. 'ContentClassAttributeID' => $attribute->attribute( 'contentclassattribute_id' ),
  130. 'identifier' => $metaDataPart['id'],
  131. 'integer_value' => $integerValue );
  132. $indexArrayOnlyWords[$additionalUrlWord] = 1;
  133. $wordCount++;
  134. }
  135. }
  136. }
  137. }
  138. }
  139. }
  140. eZContentObject::recursionProtectionEnd();
  141. $wordIDArray = $this->buildWordIDArray( array_keys( $indexArrayOnlyWords ) );
  142. $db = eZDB::instance();
  143. $db->begin();
  144. for( $arrayCount = 0; $arrayCount < $wordCount; $arrayCount += 1000 )
  145. {
  146. $placement = $this->indexWords( $contentObject, array_slice( $indexArray, $arrayCount, 1000 ), $wordIDArray, $placement );
  147. }
  148. $db->commit();
  149. }
  150. /*!
  151. \private
  152. Build WordIDArray and update ezsearch_word table
  153. \params words for object to add
  154. \return wordIDArray
  155. */
  156. function buildWordIDArray( $indexArrayOnlyWords )
  157. {
  158. $db = eZDB::instance();
  159. // Initialize transformation system
  160. $trans = eZCharTransform::instance();
  161. $wordCount = count( $indexArrayOnlyWords );
  162. $wordIDArray = array();
  163. $wordArray = array();
  164. // store the words in the index and remember the ID
  165. $dbName = $db->databaseName();
  166. if ( $dbName == 'mysql' )
  167. {
  168. $db->begin();
  169. for( $arrayCount = 0; $arrayCount < $wordCount; $arrayCount += 500 )
  170. {
  171. // Fetch already indexed words from database
  172. $wordArrayChuck = array_slice( $indexArrayOnlyWords, $arrayCount, 500 );
  173. $wordsString = implode( '\',\'', $wordArrayChuck );
  174. $wordRes = $db->arrayQuery( "SELECT * FROM ezsearch_word WHERE word IN ( '$wordsString' ) " );
  175. // Build a has of the existing words
  176. $wordResCount = count( $wordRes );
  177. $existingWordArray = array();
  178. for ( $i = 0; $i < $wordResCount; $i++ )
  179. {
  180. $wordIDArray[] = $wordRes[$i]['id'];
  181. $existingWordArray[] = $wordRes[$i]['word'];
  182. $wordArray[$wordRes[$i]['word']] = $wordRes[$i]['id'];
  183. }
  184. // Update the object count of existing words by one
  185. $wordIDString = implode( ',', $wordIDArray );
  186. if ( count( $wordIDArray ) > 0 )
  187. $db->query( "UPDATE ezsearch_word SET object_count=( object_count + 1 ) WHERE id IN ( $wordIDString )" );
  188. // Insert if there is any news words
  189. $newWordArray = array_diff( $wordArrayChuck, $existingWordArray );
  190. if ( count ($newWordArray) > 0 )
  191. {
  192. $newWordString = implode( "', '1' ), ('", $newWordArray );
  193. $db->query( "INSERT INTO
  194. ezsearch_word ( word, object_count )
  195. VALUES ('$newWordString', '1' )" );
  196. $newWordString = implode( "','", $newWordArray );
  197. $newWordRes = $db->arrayQuery( "select id,word from ezsearch_word where word in ( '$newWordString' ) " );
  198. $newWordCount = count( $newWordRes );
  199. for ( $i=0;$i<$newWordCount;++$i )
  200. {
  201. $wordLowercase = $trans->transformByGroup( $newWordRes[$i]['word'], 'lowercase' );
  202. $wordArray[$newWordRes[$i]['word']] = $newWordRes[$i]['id'];
  203. }
  204. }
  205. }
  206. $db->commit();
  207. }
  208. else
  209. {
  210. $db->begin();
  211. foreach ( $indexArrayOnlyWords as $indexWord )
  212. {
  213. $indexWord = $trans->transformByGroup( $indexWord, 'lowercase' );
  214. $indexWord = $db->escapeString( $indexWord );
  215. // Store word if it does not exist.
  216. $wordRes = array();
  217. $wordRes = $db->arrayQuery( "SELECT * FROM ezsearch_word WHERE word='$indexWord'" );
  218. if ( count( $wordRes ) == 1 )
  219. {
  220. $wordID = $wordRes[0]["id"];
  221. $db->query( "UPDATE ezsearch_word SET object_count=( object_count + 1 ) WHERE id='$wordID'" );
  222. }
  223. else
  224. {
  225. $db->query( "INSERT INTO
  226. ezsearch_word ( word, object_count )
  227. VALUES ( '$indexWord', '1' )" );
  228. $wordID = $db->lastSerialID( "ezsearch_word", "id" );
  229. }
  230. $wordArray[$indexWord] = $wordID;
  231. }
  232. $db->commit();
  233. }
  234. return $wordArray;
  235. }
  236. /*!
  237. \private
  238. \param contentObject
  239. \param indexArray
  240. \param wordIDArray
  241. \param placement
  242. \return last placement
  243. Index wordIndex
  244. */
  245. function indexWords( $contentObject, $indexArray, $wordIDArray, $placement = 0 )
  246. {
  247. $db = eZDB::instance();
  248. $contentObjectID = $contentObject->attribute( 'id' );
  249. // Count the total words in index text
  250. $totalWordCount = count( $indexArray );
  251. /* // Needs to be rewritten
  252. // Count the number of instances of each word
  253. $wordCountArray = array_count_values( $indexArray );
  254. // Strip double words
  255. $uniqueIndexArray = array_unique( $indexArray );
  256. // Count unique words
  257. $uniqueWordCount = count( $uniqueIndexArray );
  258. */
  259. // Initialize transformation system
  260. $trans = eZCharTransform::instance();
  261. $prevWordID = 0;
  262. $nextWordID = 0;
  263. $classID = $contentObject->attribute( 'contentclass_id' );
  264. $sectionID = $contentObject->attribute( 'section_id' );
  265. $published = $contentObject->attribute( 'published' );
  266. $valuesStringList = array();
  267. for ( $i = 0; $i < count( $indexArray ); $i++ )
  268. {
  269. $indexWord = $indexArray[$i]['Word'];
  270. $contentClassAttributeID = $indexArray[$i]['ContentClassAttributeID'];
  271. $identifier = $indexArray[$i]['identifier'];
  272. $integerValue = $indexArray[$i]['integer_value'];
  273. $wordID = $wordIDArray[$indexWord];
  274. if ( isset( $indexArray[$i+1] ) )
  275. {
  276. $nextIndexWord = $indexArray[$i+1]['Word'];
  277. $nextWordID = $wordIDArray[$nextIndexWord];
  278. }
  279. else
  280. $nextWordID = 0;
  281. // print( "indexing word : $indexWord <br> " );
  282. // Calculate the relevans ranking for this word
  283. // $frequency = ( $wordCountArray[$indexWord] / $totalWordCount );
  284. $frequency = 0;
  285. $valuesStringList[] = " ( '$wordID', '$contentObjectID', '$frequency', '$placement', '$nextWordID', '$prevWordID', '$classID', '$contentClassAttributeID', '$published', '$sectionID', '$identifier', '$integerValue' ) ";
  286. $prevWordID = $wordID;
  287. $placement++;
  288. }
  289. $dbName = $db->databaseName();
  290. if ( $dbName == 'mysql' )
  291. {
  292. if ( count( $valuesStringList ) > 0 )
  293. {
  294. $valuesString = implode( ',', $valuesStringList );
  295. $db->query( "INSERT INTO
  296. ezsearch_object_word_link
  297. ( word_id,
  298. contentobject_id,
  299. frequency,
  300. placement,
  301. next_word_id,
  302. prev_word_id,
  303. contentclass_id,
  304. contentclass_attribute_id,
  305. published,
  306. section_id,
  307. identifier,
  308. integer_value )
  309. VALUES $valuesString" );
  310. }
  311. }
  312. else
  313. {
  314. $db->begin();
  315. foreach ( array_keys( $valuesStringList ) as $key )
  316. {
  317. $valuesString = $valuesStringList[$key];
  318. $db->query("INSERT INTO
  319. ezsearch_object_word_link
  320. ( word_id,
  321. contentobject_id,
  322. frequency,
  323. placement,
  324. next_word_id,
  325. prev_word_id,
  326. contentclass_id,
  327. contentclass_attribute_id,
  328. published,
  329. section_id,
  330. identifier,
  331. integer_value )
  332. VALUES $valuesString" );
  333. }
  334. $db->commit();
  335. }
  336. return $placement;
  337. }
  338. /*!
  339. \static
  340. */
  341. function removeObject( $contentObject, $commit )
  342. {
  343. $db = eZDB::instance();
  344. $objectID = $contentObject->attribute( "id" );
  345. $doDelete = false;
  346. $db->begin();
  347. if ( $db->databaseName() == 'mysql' )
  348. {
  349. // fetch all the words and decrease the object count on all the words
  350. $wordArray = $db->arrayQuery( "SELECT word_id FROM ezsearch_object_word_link WHERE contentobject_id='$objectID'" );
  351. $wordIDList = array();
  352. foreach ( $wordArray as $word )
  353. $wordIDList[] = $word["word_id"];
  354. if ( count( $wordIDList ) > 0 )
  355. {
  356. $wordIDString = implode( ',', $wordIDList );
  357. $db->query( "UPDATE ezsearch_word SET object_count=( object_count - 1 ) WHERE id in ( $wordIDString )" );
  358. $doDelete = true;
  359. }
  360. }
  361. else
  362. {
  363. $cnt = $db->arrayQuery( "SELECT COUNT( word_id ) AS cnt FROM ezsearch_object_word_link WHERE contentobject_id='$objectID'" );
  364. if ( $cnt[0]['cnt'] > 0 )
  365. {
  366. $db->query( "UPDATE ezsearch_word SET object_count=( object_count - 1 ) WHERE id in ( SELECT word_id FROM ezsearch_object_word_link WHERE contentobject_id='$objectID' )" );
  367. $doDelete = true;
  368. }
  369. }
  370. if ( $doDelete )
  371. {
  372. $db->query( "DELETE FROM ezsearch_word WHERE object_count='0'" );
  373. $db->query( "DELETE FROM ezsearch_object_word_link WHERE contentobject_id='$objectID'" );
  374. }
  375. $db->commit();
  376. }
  377. /*!
  378. Saves name of a temporary that has just been created,
  379. for us to know its name when it's time to drop the table.
  380. */
  381. function saveCreatedTempTableName( $index, $tableName )
  382. {
  383. if ( isset( $this->CreatedTempTablesNames[$index] ) )
  384. {
  385. eZDebug::writeWarning( "CreatedTempTablesNames[\$index] already exists " .
  386. "and contains '" . $this->CreatedTempTablesNames[$index] . "'" );
  387. }
  388. $this->CreatedTempTablesNames[$index] = $tableName;
  389. }
  390. /*!
  391. \return Given table name from the list of saved temporary tables names by its index.
  392. \see saveCreatedTempTableName()
  393. */
  394. function getSavedTempTableName( $index )
  395. {
  396. return $this->CreatedTempTablesNames[$index];
  397. }
  398. /*!
  399. \return List of saved temporary tables names.
  400. \see saveCreatedTempTableName()
  401. */
  402. function getSavedTempTablesList()
  403. {
  404. return $this->CreatedTempTablesNames;
  405. }
  406. /*!
  407. Runs a query to the search engine.
  408. */
  409. function search( $searchText, $params = array(), $searchTypes = array() )
  410. {
  411. if ( count( $searchTypes ) == 0 )
  412. {
  413. $searchTypes['general'] = array();
  414. $searchTypes['subtype'] = array();
  415. $searchTypes['and'] = array();
  416. }
  417. else if ( !isset( $searchTypes['general'] ) )
  418. {
  419. $searchTypes['general'] = array();
  420. }
  421. $allowSearch = true;
  422. if ( trim( $searchText ) == '' )
  423. {
  424. $ini = eZINI::instance();
  425. if ( $ini->variable( 'SearchSettings', 'AllowEmptySearch' ) != 'enabled' )
  426. $allowSearch = false;
  427. if ( isset( $params['AllowEmptySearch'] ) )
  428. $allowSearch = $params['AllowEmptySearch'];
  429. }
  430. if ( $allowSearch )
  431. {
  432. $searchText = $this->normalizeText( $searchText, false );
  433. $db = eZDB::instance();
  434. $nonExistingWordArray = array();
  435. $searchTypeMap = array( 'class' => 'SearchContentClassID',
  436. 'publishdate' => 'SearchDate',
  437. 'subtree' => 'SearchSubTreeArray' );
  438. foreach ( $searchTypes['general'] as $searchType )
  439. {
  440. $params[$searchTypeMap[$searchType['subtype']]] = $searchType['value'];
  441. }
  442. if ( isset( $params['SearchOffset'] ) )
  443. $searchOffset = $params['SearchOffset'];
  444. else
  445. $searchOffset = 0;
  446. if ( isset( $params['SearchLimit'] ) )
  447. $searchLimit = $params['SearchLimit'];
  448. else
  449. $searchLimit = 10;
  450. if ( isset( $params['SearchContentClassID'] ) )
  451. $searchContentClassID = $params['SearchContentClassID'];
  452. else
  453. $searchContentClassID = -1;
  454. if ( isset( $params['SearchSectionID'] ) )
  455. $searchSectionID = $params['SearchSectionID'];
  456. else
  457. $searchSectionID = -1;
  458. if ( isset( $params['SearchDate'] ) )
  459. $searchDate = $params['SearchDate'];
  460. else
  461. $searchDate = -1;
  462. if ( isset( $params['SearchTimestamp'] ) )
  463. $searchTimestamp = $params['SearchTimestamp'];
  464. else
  465. $searchTimestamp = false;
  466. if ( isset( $params['SearchContentClassAttributeID'] ) )
  467. $searchContentClassAttributeID = $params['SearchContentClassAttributeID'];
  468. else
  469. $searchContentClassAttributeID = -1;
  470. if ( isset( $params['SearchSubTreeArray'] ) )
  471. $subTreeArray = $params['SearchSubTreeArray'];
  472. else
  473. $subTreeArray = array();
  474. if ( isset( $params['SortArray'] ) )
  475. $sortArray = $params['SortArray'];
  476. else
  477. $sortArray = array();
  478. $ignoreVisibility = isset( $params['IgnoreVisibility'] ) ? $params['IgnoreVisibility'] : false;
  479. // strip multiple spaces
  480. $searchText = preg_replace( "(\s+)", " ", $searchText );
  481. // find the phrases
  482. /* $numQuotes = substr_count( $searchText, "\"" );
  483. $phraseTextArray = array();
  484. $fullText = $searchText;
  485. $nonPhraseText ='';
  486. // $fullText = '';
  487. $postPhraseText = $fullText;
  488. $pos = 0;
  489. if ( ( $numQuotes > 0 ) and ( ( $numQuotes % 2 ) == 0 ) )
  490. {
  491. for ( $i = 0; $i < ( $numQuotes / 2 ); $i ++ )
  492. {
  493. $quotePosStart = strpos( $searchText, '"', $pos );
  494. $quotePosEnd = strpos( $searchText, '"', $quotePosStart + 1 );
  495. $prePhraseText = substr( $searchText, $pos, $quotePosStart - $pos );
  496. $postPhraseText = substr( $searchText, $quotePosEnd +1 );
  497. $phraseText = substr( $searchText, $quotePosStart + 1, $quotePosEnd - $quotePosStart - 1 );
  498. $phraseTextArray[] = $phraseText;
  499. // $fullText .= $prePhraseText;
  500. $nonPhraseText .= $prePhraseText;
  501. $pos = $quotePosEnd + 1;
  502. }
  503. }
  504. $nonPhraseText .= $postPhraseText;
  505. */
  506. $phrasesResult = $this->getPhrases( $searchText );
  507. $phraseTextArray = $phrasesResult['phrases'];
  508. $nonPhraseText = $phrasesResult['nonPhraseText'];
  509. $fullText = $phrasesResult['fullText'];
  510. $sectionQuery = '';
  511. if ( is_numeric( $searchSectionID ) and $searchSectionID > 0 )
  512. {
  513. $sectionQuery = "ezsearch_object_word_link.section_id = '$searchSectionID' AND ";
  514. }
  515. else if ( is_array( $searchSectionID ) )
  516. {
  517. // Build query for searching in an array of sections
  518. $sectionQuery = $db->generateSQLINStatement( $searchSectionID, 'ezsearch_object_word_link.section_id', false, false, 'int' ) . " AND ";
  519. }
  520. $searchDateQuery = '';
  521. if ( ( is_numeric( $searchDate ) and $searchDate > 0 ) or
  522. $searchTimestamp )
  523. {
  524. $date = new eZDateTime();
  525. $timestamp = $date->timeStamp();
  526. $day = $date->attribute('day');
  527. $month = $date->attribute('month');
  528. $year = $date->attribute('year');
  529. $publishedDateStop = false;
  530. if ( $searchTimestamp )
  531. {
  532. if ( is_array( $searchTimestamp ) )
  533. {
  534. $publishedDate = (int) $searchTimestamp[0];
  535. $publishedDateStop = (int) $searchTimestamp[1];
  536. }
  537. else
  538. $publishedDate = (int) $searchTimestamp;
  539. }
  540. else
  541. {
  542. switch ( $searchDate )
  543. {
  544. case 1:
  545. {
  546. $adjustment = 24*60*60; //seconds for one day
  547. $publishedDate = $timestamp - $adjustment;
  548. } break;
  549. case 2:
  550. {
  551. $adjustment = 7*24*60*60; //seconds for one week
  552. $publishedDate = $timestamp - $adjustment;
  553. } break;
  554. case 3:
  555. {
  556. $adjustment = 31*24*60*60; //seconds for one month
  557. $publishedDate = $timestamp - $adjustment;
  558. } break;
  559. case 4:
  560. {
  561. $adjustment = 3*31*24*60*60; //seconds for three months
  562. $publishedDate = $timestamp - $adjustment;
  563. } break;
  564. case 5:
  565. {
  566. $adjustment = 365*24*60*60; //seconds for one year
  567. $publishedDate = $timestamp - $adjustment;
  568. } break;
  569. default:
  570. {
  571. $publishedDate = $date->timeStamp();
  572. }
  573. }
  574. }
  575. $searchDateQuery = "ezsearch_object_word_link.published >= '$publishedDate' AND ";
  576. if ( $publishedDateStop )
  577. $searchDateQuery .= "ezsearch_object_word_link.published <= '$publishedDateStop' AND ";
  578. $this->GeneralFilter['searchDateQuery'] = $searchDateQuery;
  579. }
  580. $classQuery = "";
  581. if ( is_numeric( $searchContentClassID ) and $searchContentClassID > 0 )
  582. {
  583. // Build query for searching in one class
  584. $classQuery = "ezsearch_object_word_link.contentclass_id = '$searchContentClassID' AND ";
  585. $this->GeneralFilter['classAttributeQuery'] = $classQuery;
  586. }
  587. else if ( is_array( $searchContentClassID ) )
  588. {
  589. // Build query for searching in a number of classes
  590. $classString = $db->generateSQLINStatement( $searchContentClassID, 'ezsearch_object_word_link.contentclass_id', false, false, 'int' );
  591. $classQuery = "$classString AND ";
  592. $this->GeneralFilter['classAttributeQuery'] = $classQuery;
  593. }
  594. $classAttributeQuery = "";
  595. if ( is_numeric( $searchContentClassAttributeID ) and $searchContentClassAttributeID > 0 )
  596. {
  597. $classAttributeQuery = "ezsearch_object_word_link.contentclass_attribute_id = '$searchContentClassAttributeID' AND ";
  598. }
  599. else if ( is_array( $searchContentClassAttributeID ) )
  600. {
  601. // Build query for searching in a number of attributes
  602. $classAttributeQuery = $db->generateSQLINStatement( $searchContentClassAttributeID , 'ezsearch_object_word_link.contentclass_attribute_id', false, false, 'int' ) . ' AND ';
  603. }
  604. // Get the total number of objects
  605. $totalObjectCount = $this->fetchTotalObjectCount();
  606. $searchPartsArray = array();
  607. $wordIDHash = array();
  608. $wildCardCount = 0;
  609. if ( trim( $searchText ) != '' )
  610. {
  611. $wordIDArrays = $this->prepareWordIDArrays( $searchText );
  612. $wordIDArray = $wordIDArrays['wordIDArray'];
  613. $wordIDHash = $wordIDArrays['wordIDHash'];
  614. $wildIDArray = $wordIDArrays['wildIDArray'];
  615. $wildCardCount = $wordIDArrays['wildCardCount'];
  616. $searchPartsArray = $this->buildSearchPartArray( $phraseTextArray, $nonPhraseText, $wordIDHash, $wildIDArray );
  617. }
  618. /// OR search, not used in this version
  619. $doOrSearch = false;
  620. if ( $doOrSearch == true )
  621. {
  622. // build fulltext search SQL part
  623. $searchWordArray = $this->splitString( $fullText );
  624. $fullTextSQL = "";
  625. if ( count( $searchWordArray ) > 0 )
  626. {
  627. $i = 0;
  628. // Build the word query string
  629. foreach ( $searchWordArray as $searchWord )
  630. {
  631. $wordID = null;
  632. if ( isset( $wordIDHash[$searchWord] ) )
  633. $wordID = $wordIDHash[$searchWord]['id'];
  634. if ( is_numeric( $wordID ) and ( $wordID > 0 ) )
  635. {
  636. if ( $i == 0 )
  637. $fullTextSQL .= "ezsearch_object_word_link.word_id='$wordID' ";
  638. else
  639. $fullTextSQL .= " OR ezsearch_object_word_link.word_id='$wordID' ";
  640. }
  641. else
  642. {
  643. $nonExistingWordArray[] = $searchWord;
  644. }
  645. $i++;
  646. }
  647. $fullTextSQL = " ( $fullTextSQL ) AND ";
  648. }
  649. }
  650. // Search only in specific sub trees
  651. $subTreeSQL = "";
  652. $subTreeTable = "";
  653. if ( count( $subTreeArray ) > 0 )
  654. {
  655. // Fetch path_string value to use when searching subtrees
  656. $i = 0;
  657. $doSubTreeSearch = false;
  658. $subTreeNodeSQL = '';
  659. foreach ( $subTreeArray as $nodeID )
  660. {
  661. if ( is_numeric( $nodeID ) and ( $nodeID > 0 ) )
  662. {
  663. $subTreeNodeSQL .= " $nodeID";
  664. if ( isset( $subTreeArray[$i + 1] ) and
  665. is_numeric( $subTreeArray[$i + 1] ) )
  666. $subTreeNodeSQL .= ", ";
  667. $doSubTreeSearch = true;
  668. }
  669. $i++;
  670. }
  671. if ( $doSubTreeSearch == true )
  672. {
  673. $subTreeNodeSQL = "( " . $subTreeNodeSQL;
  674. // $subTreeTable = ", ezcontentobject_tree ";
  675. $subTreeTable = '';
  676. $subTreeNodeSQL .= " ) ";
  677. $nodeQuery = "SELECT node_id, path_string FROM ezcontentobject_tree WHERE node_id IN $subTreeNodeSQL";
  678. // Build SQL subtre search query
  679. $subTreeSQL = " ( ";
  680. $nodeArray = $db->arrayQuery( $nodeQuery );
  681. $i = 0;
  682. foreach ( $nodeArray as $node )
  683. {
  684. $pathString = $node['path_string'];
  685. $subTreeSQL .= " ezcontentobject_tree.path_string like '$pathString%' ";
  686. if ( $i < ( count( $nodeArray ) -1 ) )
  687. $subTreeSQL .= " OR ";
  688. $i++;
  689. }
  690. $subTreeSQL .= " ) AND ";
  691. $this->GeneralFilter['subTreeTable'] = $subTreeTable;
  692. $this->GeneralFilter['subTreeSQL'] = $subTreeSQL;
  693. }
  694. }
  695. $limitation = false;
  696. if ( isset( $params['Limitation'] ) )
  697. {
  698. $limitation = $params['Limitation'];
  699. }
  700. $limitationList = eZContentObjectTreeNode::getLimitationList( $limitation );
  701. $sqlPermissionChecking = eZContentObjectTreeNode::createPermissionCheckingSQL( $limitationList );
  702. $this->GeneralFilter['sqlPermissionChecking'] = $sqlPermissionChecking;
  703. $useVersionName = true;
  704. if ( $useVersionName )
  705. {
  706. $versionNameTables = ', ezcontentobject_name ';
  707. $versionNameTargets = ', ezcontentobject_name.name as name, ezcontentobject_name.real_translation ';
  708. $versionNameJoins = " and ezcontentobject_tree.contentobject_id = ezcontentobject_name.contentobject_id and
  709. ezcontentobject_tree.contentobject_version = ezcontentobject_name.content_version and ";
  710. $versionNameJoins .= eZContentLanguage::sqlFilter( 'ezcontentobject_name', 'ezcontentobject' );
  711. }
  712. /// Only support AND search at this time
  713. // build fulltext search SQL part
  714. $searchWordArray = $this->splitString( $fullText );
  715. $searchWordCount = count( $searchWordArray );
  716. $fullTextSQL = "";
  717. $stopWordArray = array( );
  718. $ini = eZINI::instance();
  719. $tmpTableCount = 0;
  720. $i = 0;
  721. foreach ( $searchTypes['and'] as $searchType )
  722. {
  723. $methodName = $this->constructMethodName( $searchType );
  724. $intermediateResult = $this->callMethod( $methodName, array( $searchType ) );
  725. if ( $intermediateResult == false )
  726. {
  727. // cleanup temp tables
  728. $db->dropTempTableList( $sqlPermissionChecking['temp_tables'] );
  729. return array( "SearchResult" => array(),
  730. "SearchCount" => 0,
  731. "StopWordArray" => array() );
  732. }
  733. }
  734. // Do not execute search if site.ini:[SearchSettings]->AllowEmptySearch is enabled, but no conditions are set.
  735. if ( !$searchDateQuery &&
  736. !$sectionQuery &&
  737. !$classQuery &&
  738. !$classAttributeQuery &&
  739. !$searchPartsArray &&
  740. !$subTreeSQL )
  741. {
  742. // cleanup temp tables
  743. $db->dropTempTableList( $sqlPermissionChecking['temp_tables'] );
  744. return array( "SearchResult" => array(),
  745. "SearchCount" => 0,
  746. "StopWordArray" => array() );
  747. }
  748. $i = $this->TempTablesCount;
  749. // Loop every word and insert result in temporary table
  750. // Determine whether we should search invisible nodes.
  751. $showInvisibleNodesCond = eZContentObjectTreeNode::createShowInvisibleSQLString( !$ignoreVisibility );
  752. foreach ( $searchPartsArray as $searchPart )
  753. {
  754. $stopWordThresholdValue = 100;
  755. if ( $ini->hasVariable( 'SearchSettings', 'StopWordThresholdValue' ) )
  756. $stopWordThresholdValue = $ini->variable( 'SearchSettings', 'StopWordThresholdValue' );
  757. $stopWordThresholdPercent = 60;
  758. if ( $ini->hasVariable( 'SearchSettings', 'StopWordThresholdPercent' ) )
  759. $stopWordThresholdPercent = $ini->variable( 'SearchSettings', 'StopWordThresholdPercent' );
  760. $searchThresholdValue = $totalObjectCount;
  761. if ( $totalObjectCount > $stopWordThresholdValue )
  762. {
  763. $searchThresholdValue = (int)( $totalObjectCount * ( $stopWordThresholdPercent / 100 ) );
  764. }
  765. // do not search words that are too frequent
  766. if ( $searchPart['object_count'] < $searchThresholdValue )
  767. {
  768. $tmpTableCount++;
  769. $searchPartText = $searchPart['sql_part'];
  770. if ( $i == 0 )
  771. {
  772. $table = $db->generateUniqueTempTableName( 'ezsearch_tmp_%', 0 );
  773. $this->saveCreatedTempTableName( 0, $table );
  774. $db->createTempTable( "CREATE TEMPORARY TABLE $table ( contentobject_id int primary key not null, published int )" );
  775. $db->query( "INSERT INTO $table SELECT DISTINCT ezsearch_object_word_link.contentobject_id, ezsearch_object_word_link.published
  776. FROM ezcontentobject,
  777. ezsearch_object_word_link
  778. $subTreeTable,
  779. ezcontentclass,
  780. ezcontentobject_tree
  781. $sqlPermissionChecking[from]
  782. WHERE
  783. $searchDateQuery
  784. $sectionQuery
  785. $classQuery
  786. $classAttributeQuery
  787. $searchPartText
  788. $subTreeSQL
  789. ezcontentobject.id=ezsearch_object_word_link.contentobject_id and
  790. ezcontentobject.contentclass_id = ezcontentclass.id and
  791. ezcontentclass.version = '0' and
  792. ezcontentobject.id = ezcontentobject_tree.contentobject_id and
  793. ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id
  794. $showInvisibleNodesCond
  795. $sqlPermissionChecking[where]",
  796. eZDBInterface::SERVER_SLAVE );
  797. }
  798. else
  799. {
  800. $table = $db->generateUniqueTempTableName( 'ezsearch_tmp_%', $i );
  801. $this->saveCreatedTempTableName( $i, $table );
  802. $tmpTable0 = $this->getSavedTempTableName( 0 );
  803. $db->createTempTable( "CREATE TEMPORARY TABLE $table ( contentobject_id int primary key not null, published int )" );
  804. $db->query( "INSERT INTO $table SELECT DISTINCT ezsearch_object_word_link.contentobject_id, ezsearch_object_word_link.published
  805. FROM
  806. ezcontentobject,
  807. ezsearch_object_word_link
  808. $subTreeTable,
  809. ezcontentclass,
  810. ezcontentobject_tree,
  811. $tmpTable0
  812. $sqlPermissionChecking[from]
  813. WHERE
  814. $tmpTable0.contentobject_id=ezsearch_object_word_link.contentobject_id AND
  815. $searchDateQuery
  816. $sectionQuery
  817. $classQuery
  818. $classAttributeQuery
  819. $searchPartText
  820. $subTreeSQL
  821. ezcontentobject.id=ezsearch_object_word_link.contentobject_id and
  822. ezcontentobject.contentclass_id = ezcontentclass.id and
  823. ezcontentclass.version = '0' and
  824. ezcontentobject.id = ezcontentobject_tree.contentobject_id and
  825. ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id
  826. $showInvisibleNodesCond
  827. $sqlPermissionChecking[where]",
  828. eZDBInterface::SERVER_SLAVE );
  829. }
  830. $i++;
  831. }
  832. else
  833. {
  834. $stopWordArray[] = array( 'word' => $searchPart['text'] );
  835. }
  836. }
  837. if ( count( $searchPartsArray ) === 0 && $this->TempTablesCount == 0 )
  838. {
  839. $table = $db->generateUniqueTempTableName( 'ezsearch_tmp_%', 0 );
  840. $this->saveCreatedTempTableName( 0, $table );
  841. $db->createTempTable( "CREATE TEMPORARY TABLE $table ( contentobject_id int primary key not null, published int )" );
  842. $db->query( "INSERT INTO $table SELECT DISTINCT ezsearch_object_word_link.contentobject_id, ezsearch_object_word_link.published
  843. FROM ezcontentobject,
  844. ezsearch_object_word_link
  845. $subTreeTable,
  846. ezcontentclass,
  847. ezcontentobject_tree
  848. $sqlPermissionChecking[from]
  849. WHERE
  850. $searchDateQuery
  851. $sectionQuery
  852. $classQuery
  853. $classAttributeQuery
  854. $subTreeSQL
  855. ezcontentobject.id=ezsearch_object_word_link.contentobject_id and
  856. ezcontentobject.contentclass_id = ezcontentclass.id and
  857. ezcontentclass.version = '0' and
  858. ezcontentobject.id = ezcontentobject_tree.contentobject_id and
  859. ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id
  860. $showInvisibleNodesCond
  861. $sqlPermissionChecking[where]",
  862. eZDBInterface::SERVER_SLAVE );
  863. $this->TempTablesCount = 1;
  864. $i = $this->TempTablesCount;
  865. }
  866. $nonExistingWordCount = count( array_unique( $searchWordArray ) ) - count( $wordIDHash ) - $wildCardCount;
  867. $excludeWordCount = $searchWordCount - count( $stopWordArray );
  868. if ( ( count( $stopWordArray ) + $nonExistingWordCount ) == $searchWordCount && $this->TempTablesCount == 0 )
  869. {
  870. // No words to search for, return empty result
  871. // cleanup temp tables
  872. $db->dropTempTableList( $sqlPermissionChecking['temp_tables'] );
  873. $db->dropTempTableList( $this->getSavedTempTablesList() );
  874. return array( "SearchResult" => array(),
  875. "SearchCount" => 0,
  876. "StopWordArray" => $stopWordArray );
  877. }
  878. $tmpTablesFrom = "";
  879. $tmpTablesWhere = "";
  880. /// tmp tables
  881. $tmpTableCount = $i;
  882. for ( $i = 0; $i < $tmpTableCount; $i++ )
  883. {
  884. $tmpTablesFrom .= $this->getSavedTempTableName( $i );
  885. if ( $i < ( $tmpTableCount - 1 ) )
  886. $tmpTablesFrom .= ", ";
  887. }
  888. $tmpTablesSeparator = '';
  889. if ( $tmpTableCount > 0 )
  890. {
  891. $tmpTablesSeparator = ',';
  892. }
  893. $tmpTable0 = $this->getSavedTempTableName( 0 );
  894. for ( $i = 1; $i < $tmpTableCount; $i++ )
  895. {
  896. $tmpTableI = $this->getSavedTempTableName( $i );
  897. $tmpTablesWhere .= " $tmpTable0.contentobject_id=$tmpTableI.contentobject_id ";
  898. if ( $i < ( $tmpTableCount - 1 ) )
  899. $tmpTablesWhere .= " AND ";
  900. }
  901. $tmpTablesWhereExtra = '';
  902. if ( $tmpTableCount > 0 )
  903. {
  904. $tmpTablesWhereExtra = "ezcontentobject.id=$tmpTable0.contentobject_id AND";
  905. }
  906. $and = "";
  907. if ( $tmpTableCount > 1 )
  908. $and = " AND ";
  909. // Generate ORDER BY SQL
  910. $orderBySQLArray = $this->buildSortSQL( $sortArray );
  911. $orderByFieldsSQL = $orderBySQLArray['sortingFields'];
  912. $sortWhereSQL = $orderBySQLArray['whereSQL'];
  913. $sortFromSQL = $orderBySQLArray['fromSQL'];
  914. // Fetch data from table
  915. $searchQuery ='';
  916. $dbName = $db->databaseName();
  917. if ( $dbName == 'mysql' )
  918. {
  919. $searchQuery = "SELECT DISTINCT ezcontentobject.*, ezcontentclass.serialized_name_list as class_serialized_name_list, ezcontentobject_tree.*
  920. $versionNameTargets
  921. FROM
  922. $tmpTablesFrom $tmpTablesSeparator
  923. ezcontentobject,
  924. ezcontentclass,
  925. ezcontentobject_tree
  926. $versionNameTables
  927. $sortFromSQL
  928. WHERE
  929. $tmpTablesWhere $and
  930. $tmpTablesWhereExtra
  931. ezcontentobject.contentclass_id = ezcontentclass.id and
  932. ezcontentclass.version = '0' and
  933. ezcontentobject.id = ezcontentobject_tree.contentobject_id and
  934. ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id
  935. $versionNameJoins
  936. $showInvisibleNodesCond
  937. $sortWhereSQL
  938. ORDER BY $orderByFieldsSQL";
  939. }
  940. else
  941. {
  942. $searchQuery = "SELECT DISTINCT ezcontentobject.*, ezcontentclass.serialized_name_list as class_serialized_name_list, ezcontentobject_tree.*
  943. $versionNameTargets
  944. FROM
  945. $tmpTablesFrom $tmpTablesSeparator
  946. ezcontentobject,
  947. ezcontentclass,
  948. ezcontentobject_tree
  949. $versionNameTables
  950. WHERE
  951. $tmpTablesWhere $and
  952. $tmpTablesWhereExtra
  953. ezcontentobject.contentclass_id = ezcontentclass.id and
  954. ezcontentclass.version = '0' and
  955. ezcontentobject.id = ezcontentobject_tree.contentobject_id and
  956. ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id
  957. $versionNameJoins
  958. ";
  959. }
  960. // Count query
  961. $languageCond = eZContentLanguage::languagesSQLFilter( 'ezcontentobject' );
  962. if ( $tmpTableCount == 0 )
  963. {
  964. $searchCountQuery = "SELECT count( DISTINCT ezcontentobject.id ) AS count
  965. FROM
  966. ezcontentobject,
  967. ezcontentobject_tree
  968. WHERE
  969. ezcontentobject.id = ezcontentobject_tree.contentobject_id and
  970. ezcontentobject_tree.node_id = ezcontentobject_tree.main_node_id
  971. AND $languageCond
  972. $showInvisibleNodesCond";
  973. }
  974. else
  975. {
  976. $searchCountQuery = "SELECT count( DISTINCT ezcontentobject.id ) AS count
  977. FROM $tmpTablesFrom $tmpTablesSeparator
  978. ezcontentobject
  979. WHERE $tmpTablesWhere $and
  980. $tmpTablesWhereExtra
  981. $languageCond";
  982. }
  983. $objectRes = array();
  984. $searchCount = 0;
  985. if ( $nonExistingWordCount <= 0 )
  986. {
  987. // execute search query
  988. $objectResArray = $db->arrayQuery( $searchQuery, array( "limit" => $searchLimit, "offset" => $searchOffset ), eZDBInterface::SERVER_SLAVE );
  989. // execute search count query
  990. $objectCountRes = $db->arrayQuery( $searchCountQuery, array(), eZDBInterface::SERVER_SLAVE );
  991. $objectRes = eZContentObjectTreeNode::makeObjectsArray( $objectResArray );
  992. $searchCount = $objectCountRes[0]['count'];
  993. }
  994. else
  995. $objectRes = array();
  996. // Drop tmp tables
  997. $db->dropTempTableList( $sqlPermissionChecking['temp_tables'] );
  998. $db->dropTempTableList( $this->getSavedTempTablesList() );
  999. return array( "SearchResult" => $objectRes,
  1000. "SearchCount" => $searchCount,
  1001. "StopWordArray" => $stopWordArray );
  1002. }
  1003. else
  1004. {
  1005. return array( "SearchResult" => array(),
  1006. "SearchCount" => 0,
  1007. "StopWordArray" => array() );
  1008. }
  1009. }
  1010. /*!
  1011. \private
  1012. \return an array of ORDER BY SQL
  1013. */
  1014. function buildSortSQL( $sortArray )
  1015. {
  1016. $sortCount = 0;
  1017. $sortList = false;
  1018. if ( isset( $sortArray ) and
  1019. is_array( $sortArray ) and
  1020. count( $sortArray ) > 0 )
  1021. {
  1022. $sortList = $sortArray;
  1023. if ( count( $sortList ) > 1 and
  1024. !is_array( $sortList[0] ) )
  1025. {
  1026. $sortList = array( $sortList );
  1027. }
  1028. }
  1029. $attributeJoinCount = 0;
  1030. $attributeFromSQL = "";
  1031. $attributeWereSQL = "";
  1032. if ( $sortList !== false )
  1033. {
  1034. $sortingFields = '';
  1035. foreach ( $sortList as $sortBy )
  1036. {
  1037. if ( is_array( $sortBy ) and count( $sortBy ) > 0 )
  1038. {
  1039. if ( $sortCount > 0 )
  1040. $sortingFields .= ', ';
  1041. $sortField = $sortBy[0];
  1042. switch ( $sortField )
  1043. {
  1044. case 'path':
  1045. {
  1046. $sortingFields .= 'path_string';
  1047. } break;
  1048. case 'published':
  1049. {
  1050. $sortingFields .= 'ezcontentobject.published';
  1051. } break;
  1052. case 'modified':
  1053. {
  1054. $sortingFields .= 'ezcontentobject.modified';

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