Changeset 58
- Timestamp:
- 12/21/05 11:03:12 (3 years ago)
- Files:
-
- trunk/batch/load_data.php (modified) (1 diff)
- trunk/config/schema.xml (modified) (1 diff)
- trunk/data/sql/schema.sql (modified) (2 diffs)
- trunk/frontend/config/app.yml (modified) (1 diff)
- trunk/frontend/config/routing.yml (modified) (1 diff)
- trunk/frontend/modules/question/actions/actions.class.php (modified) (1 diff)
- trunk/frontend/modules/question/templates/_question_block.php (added)
- trunk/frontend/modules/question/templates/_question_list.php (modified) (1 diff)
- trunk/frontend/modules/question/templates/_search.php (added)
- trunk/frontend/modules/question/templates/searchSuccess.php (added)
- trunk/frontend/modules/sidebar/templates/defaultSuccess.php (modified) (1 diff)
- trunk/frontend/modules/sidebar/templates/questionSuccess.php (modified) (1 diff)
- trunk/lib/PorterStemmer.class.php (added)
- trunk/lib/model/Question.php (modified) (1 diff)
- trunk/lib/model/QuestionPeer.php (modified) (1 diff)
- trunk/lib/model/QuestionTag.php (modified) (1 diff)
- trunk/lib/model/SearchIndex.php (added)
- trunk/lib/model/SearchIndexPeer.php (added)
- trunk/lib/model/map/SearchIndexMapBuilder.php (added)
- trunk/lib/model/om/BaseQuestion.php (modified) (5 diffs)
- trunk/lib/model/om/BaseSearchIndex.php (added)
- trunk/lib/model/om/BaseSearchIndexPeer.php (added)
- trunk/lib/myTools.class.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/batch/load_data.php
r12 r58 8 8 require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php'); 9 9 10 SearchIndexPeer::doDeleteAll(); 11 10 12 $data = new sfPropelData(); 11 13 $data->loadData(SF_DATA_DIR.DIRECTORY_SEPARATOR.'fixtures'); trunk/config/schema.xml
r55 r58 93 93 </table> 94 94 95 <table name="ask_search_index" phpName="SearchIndex"> 96 <column name="question_id" type="integer" /> 97 <foreign-key foreignTable="ask_question" onDelete="cascade"> 98 <reference local="question_id" foreign="id" /> 99 </foreign-key> 100 <column name="word" type="varchar" size="255" /> 101 <column name="weight" type="integer" /> 102 </table> 103 95 104 <table name="ask_report_question" phpName="ReportQuestion"> 96 105 <column name="question_id" type="integer" primaryKey="true" /> trunk/data/sql/schema.sql
r55 r58 139 139 Type=InnoDB; 140 140 # ----------------------------------------------------------------------- 141 # ask_search_index 142 # ----------------------------------------------------------------------- 143 DROP TABLE IF EXISTS `ask_search_index`; 144 145 CREATE TABLE `ask_search_index`( 146 `question_id` INTEGER , 147 `word` VARCHAR(255) , 148 `weight` INTEGER , 149 INDEX `ask_search_index_FI_1` (`question_id`), 150 CONSTRAINT `ask_search_index_FK_1` 151 FOREIGN KEY (`question_id`) 152 REFERENCES `ask_question` (`id`) 153 ON DELETE CASCADE) 154 Type=InnoDB; 155 # ----------------------------------------------------------------------- 141 156 # ask_report_question 142 157 # ----------------------------------------------------------------------- … … 186 201 187 202 203 188 204 # This restores the fkey checks, after having unset them 189 205 # in database-start.tpl trunk/frontend/config/app.yml
r49 r58 9 9 max_questions: 10 10 10 11 search: 12 body_weight: 1 13 title_weight: 2 14 tag_weight: 3 15 results_max: 10 16 11 17 .global: 12 18 universe: on trunk/frontend/config/routing.yml
r55 r58 15 15 url: /add_question 16 16 param: { module: question, action: add } 17 18 search_question: 19 url: /search/* 20 param: { module: question, action: search } 17 21 18 22 # answers trunk/frontend/modules/question/actions/actions.class.php
r40 r58 54 54 } 55 55 56 public function executeSearch () 57 { 58 if ($this->getRequestParameter('search')) 59 { 60 $this->questions = QuestionPeer::search($this->getRequestParameter('search'), $this->getRequestParameter('search_all', false), ($this->getRequestParameter('page', 1) - 1) * APP_SEARCH_RESULTS_MAX, APP_SEARCH_RESULTS_MAX); 61 } 62 else 63 { 64 $this->redirect('@homepage'); 65 } 66 } 67 56 68 public function handleErrorAdd() 57 69 { trunk/frontend/modules/question/templates/_question_list.php
r55 r58 1 <?php use_helpers(' Text', 'Global', 'Question', 'Date') ?>1 <?php use_helpers('Global') ?> 2 2 3 3 <?php foreach($question_pager->getResults() as $question): ?> 4 <div class="question"> 5 <div class="interested_block" id="block_<?php echo $question->getId() ?>"> 6 <?php echo include_partial('question/interested_user', array('question' => $question)) ?> 7 </div> 8 9 <h2><?php echo link_to_question($question) ?></h2> 10 11 <div class="subtitle">asked by <?php echo link_to_profile($question->getUser()) ?> on <?php echo format_date($question->getCreatedAt(), 'f') ?></div> 12 13 <div class="question_body"> 14 <?php echo truncate_text(strip_tags($question->getHtmlBody()), 200) ?> 15 16 <div class="options"> 17 18 <?php if ($question->getAnswers()): ?> 19 <?php echo link_to(count($question->getAnswers()).' answer'.(count($question->getAnswers()) > 1 ? 's' : ''), '@question?stripped_title='.$question->getStrippedTitle()) ?> 20 <?php else: ?> 21 <?php echo link_to('answer it', '@question?stripped_title='.$question->getStrippedTitle()) ?> 22 <?php endif ?> 23 24 - 25 26 <?php if ($question->getTags()): ?> 27 tags: <?php echo tags_for_question($question) ?> 28 <?php endif ?> 29 30 </div> 31 32 <div class="options" id="report_question_<?php echo $question->getId() ?>"> 33 <?php echo link_to_report_question($question, $user) ?> 34 <?php include_partial('moderator/question_options', array('question' => $question)) ?> 35 </div> 36 37 </div> 38 </div> 4 <?php echo include_partial('question/question_block', array('question' => $question)) ?> 39 5 <?php endforeach ?> 40 6 trunk/frontend/modules/sidebar/templates/defaultSuccess.php
r55 r58 9 9 <div class="right" style="padding-top: 5px"><?php echo link_to('more popular tags »', '@popular_tags') ?></div> 10 10 11 <h2>find it</h2> 12 <?php echo include_partial('question/search') ?> 13 11 14 <h2>browse askeet</h2> 12 15 <?php echo include_partial('rss_links') ?> trunk/frontend/modules/sidebar/templates/questionSuccess.php
r55 r58 4 4 <?php echo link_to_login('ask a new question', '@add_question') ?> 5 5 </div> 6 7 <h2>find it</h2> 8 <?php echo include_partial('question/search') ?> 6 9 7 10 <h2>browse askeet</h2> trunk/lib/model/Question.php
r55 r58 150 150 } 151 151 152 public function getWords() 153 { 154 // body 155 $raw_text = str_repeat(' '.strip_tags($this->getHtmlBody()), APP_SEARCH_BODY_WEIGHT); 156 157 // title 158 $raw_text .= str_repeat(' '.$this->getTitle(), APP_SEARCH_TITLE_WEIGHT); 159 160 // title and body stemming 161 $stemmed_words = myTools::stemPhrase($raw_text); 162 163 // unique words with weight 164 $words = array_count_values($stemmed_words); 165 166 // add tags 167 $max = 0; 168 foreach ($this->getPopularTags(20) as $tag => $count) 169 { 170 if (!$max) 171 { 172 $max = $count; 173 } 174 175 $stemmed_tag = PorterStemmer::stem($tag); 176 177 if (!isset($words[$stemmed_tag])) 178 { 179 $words[$stemmed_tag] = 0; 180 } 181 $words[$stemmed_tag] += ceil(($count / $max) * APP_SEARCH_TAG_WEIGHT); 182 } 183 184 return $words; 185 } 186 187 public function save($con = null) 188 { 189 $con = Propel::getConnection(); 190 try 191 { 192 $con->begin(); 193 194 $ret = parent::save(); 195 196 $this->updateSearchIndex(); 197 198 $con->commit(); 199 200 return $ret; 201 } 202 catch (Exception $e) 203 { 204 $con->rollback(); 205 throw $e; 206 } 207 } 208 209 public function updateSearchIndex() 210 { 211 // update search index 212 $c = new Criteria(); 213 $c->add(SearchIndexPeer::QUESTION_ID, $this->getId()); 214 SearchIndexPeer::doDelete($c); 215 216 foreach ($this->getWords() as $word => $weight) 217 { 218 $index = new SearchIndex(); 219 $index->setQuestionId($this->getId()); 220 $index->setWord($word); 221 $index->setWeight($weight); 222 $index->save(); 223 } 224 } 225 226 public function hasTag($tag) 227 { 228 $c = new Criteria(); 229 $c->add(QuestionTagPeer::QUESTION_ID, $this->getId()); 230 $c->add(QuestionTagPeer::NORMALIZED_TAG, Tag::normalize($tag)); 231 232 return QuestionTagPeer::doSelectOne($c) ? true : false; 233 } 234 152 235 public function deleteReports() 153 236 { trunk/lib/model/QuestionPeer.php
r55 r58 103 103 } 104 104 105 public static function search($phrase, $exact = false, $offset = 0, $max = 10) 106 { 107 $words = array_values(myTools::stemPhrase($phrase)); 108 $nb_words = count($words); 109 110 $con = Propel::getConnection(); 111 $query = ' 112 SELECT DISTINCT %s, COUNT(*) AS nb, SUM(%s) AS total_weight 113 FROM %s 114 '; 115 116 if (defined('APP_PERMANENT_TAG')) 117 { 118 $query .= sprintf(' 119 LEFT JOIN %s ON %s = %s 120 WHERE %s = ? AND ', 121 QuestionTagPeer::TABLE_NAME, 122 QuestionTagPeer::QUESTION_ID, 123 SearchIndexPeer::QUESTION_ID, 124 QuestionTagPeer::NORMALIZED_TAG 125 ); 126 } 127 else 128 { 129 $query .= 'WHERE'; 130 } 131 132 $query .= ' 133 ('.implode(' OR ', array_fill(0, $nb_words, SearchIndexPeer::WORD.' = ?')).') 134 GROUP BY %s 135 '; 136 137 // AND query? 138 if ($exact) 139 { 140 $query .= ' HAVING nb = '.$nb_words; 141 } 142 143 $query .= ' ORDER BY nb DESC, total_weight DESC'; 144 145 $query = sprintf($query, 146 SearchIndexPeer::QUESTION_ID, 147 SearchIndexPeer::WEIGHT, 148 SearchIndexPeer::TABLE_NAME, 149 SearchIndexPeer::QUESTION_ID 150 ); 151 152 $stmt = $con->prepareStatement($query); 153 $stmt->setOffset($offset); 154 $stmt->setLimit($max); 155 $placeholder_offset = 1; 156 if (defined('APP_PERMANENT_TAG')) 157 { 158 $stmt->setString(1, APP_PERMANENT_TAG); 159 $placeholder_offset = 2; 160 } 161 for ($i = 0; $i < $nb_words; $i++) 162 { 163 $stmt->setString($i + $placeholder_offset, $words[$i]); 164 } 165 $rs = $stmt->executeQuery(); 166 $questions = array(); 167 while ($rs->next()) 168 { 169 $questions[] = self::retrieveByPK($rs->getInt('question_id')); 170 } 171 172 return $questions; 173 } 174 105 175 public static function getReportedSpamPager($page) 106 176 { trunk/lib/model/QuestionTag.php
r31 r58 23 23 $this->setNormalizedTag(Tag::normalize($v)); 24 24 } 25 26 public function save($con = null) 27 { 28 $ret = parent::save($con); 29 30 $this->getQuestion()->updateSearchIndex(); 31 32 return $ret; 33 } 25 34 } 26 35 trunk/lib/model/om/BaseQuestion.php
r55 r58 138 138 */ 139 139 private $lastQuestionTagCriteria = null; 140 141 /** 142 * Collection to store aggregation of collSearchIndexs. 143 * @var array 144 */ 145 protected $collSearchIndexs; 146 147 /** 148 * The criteria used to select the current contents of collSearchIndexs. 149 * @var Criteria 150 */ 151 private $lastSearchIndexCriteria = null; 140 152 141 153 /** … … 683 695 } 684 696 697 if ($this->collSearchIndexs !== null) { 698 foreach($this->collSearchIndexs as $referrerFK) { 699 if (!$referrerFK->isDeleted()) { 700 $affectedRows += $referrerFK->save($con); 701 } 702 } 703 } 704 685 705 if ($this->collReportQuestions !== null) { 686 706 foreach($this->collReportQuestions as $referrerFK) { … … 769 789 if ($this->collQuestionTags !== null) { 770 790 foreach($this->collQuestionTags as $referrerFK) { 791 if (($retval = $referrerFK->validate()) !== true) { 792 $failureMap = array_merge($failureMap, $retval); 793 } 794 } 795 } 796 797 if ($this->collSearchIndexs !== null) { 798 foreach($this->collSearchIndexs as $referrerFK) { 771 799 if (($retval = $referrerFK->validate()) !== true) { 772 800 $failureMap = array_merge($failureMap, $retval); … … 1082 1110 } 1083 1111 1112 foreach($this->getSearchIndexs() as $relObj) { 1113 $copyObj->addSearchIndex($relObj->copy($deepCopy)); 1114 } 1115 1084 1116 foreach($this->getReportQuestions() as $relObj) { 1085 1117 $copyObj->addReportQuestion($relObj->copy($deepCopy)); … … 1650 1682 1651 1683 /** 1684 * Temporary storage of collSearchIndexs to save a possible db hit in 1685 * the event objects are add to the collection, but the 1686 * complete collection is never requested. 1687 * @return void 1688 */ 1689 public function initSearchIndexs() 1690 { 1691 if ($this->collSearchIndexs === null) { 1692 $this->collSearchIndexs = array(); 1693 } 1694 } 1695 1696 /** 1697 * If this collection has already been initialized with 1698 * an identical criteria, it returns the collection. 1699 * Otherwise if this Question has previously 1700 * been saved, it will retrieve related SearchIndexs from storage. 1701 * If this Question is new, it will return 1702 * an empty collection or the current collection, the criteria 1703 * is ignored on a new object. 1704 * 1705 * @param Connection $con 1706 * @param Criteria $criteria 1707 * @throws PropelException 1708 */ 1709 public function getSearchIndexs($criteria = null, $con = null) 1710 { 1711 // include the Peer class 1712 include_once 'model/om/BaseSearchIndexPeer.php'; 1713 if ($criteria === null) { 1714 $criteria = new Criteria(); 1715 } 1716 elseif ($criteria instanceof Criteria) 1717 { 1718 $criteria = clone $criteria; 1719 } 1720 1721 if ($this->collSearchIndexs === null) { 1722 if ($this->isNew()) { 1723 $this->collSearchIndexs = array(); 1724 } else { 1725 1726 $criteria->add(SearchIndexPeer::QUESTION_ID, $this->getId()); 1727 1728 SearchIndexPeer::addSelectColumns($criteria); 1729 $this->collSearchIndexs = SearchIndexPeer::doSelect($criteria, $con); 1730 } 1731 } else { 1732 // criteria has no effect for a new object 1733 if (!$this->isNew()) { 1734 // the following code is to determine if a new query is 1735 // called for. If the criteria is the same as the last 1736 // one, just return the collection. 1737 1738 1739 $criteria->add(SearchIndexPeer::QUESTION_ID, $this->getId()); 1740 1741 SearchIndexPeer::addSelectColumns($criteria); 1742 if (!isset($this->lastSearchIndexCriteria) || !$this->lastSearchIndexCriteria->equals($criteria)) { 1743 $this->collSearchIndexs = SearchIndexPeer::doSelect($criteria, $con); 1744 } 1745 } 1746 } 1747 $this->lastSearchIndexCriteria = $criteria; 1748 return $this->collSearchIndexs; 1749 } 1750 1751 /** 1752 * Returns the number of related SearchIndexs. 1753 * 1754 * @param Criteria $criteria 1755 * @param Connection $con 1756 * @throws PropelException 1757 */ 1758 public function countSearchIndexs($criteria = null, $con = null) 1759 { 1760 // include the Peer class 1761 include_once 'model/om/BaseSearchIndexPeer.php'; 1762 if ($criteria === null) { 1763 $criteria = new Criteria(); 1764 } 1765 elseif ($criteria instanceof Criteria) 1766 { 1767 $criteria = clone $criteria; 1768 } 1769 1770 $criteria->add(SearchIndexPeer::QUESTION_ID, $this->getId()); 1771 1772 return SearchIndexPeer::doCount($criteria, $con); 1773 } 1774 1775 /** 1776 * Method called to associate a SearchIndex object to this object 1777 * through the SearchIndex foreign key attribute 1778 * 1779 * @param SearchIndex $l SearchIndex 1780 * @return void 1781 * @throws PropelException 1782 */ 1783 public function addSearchIndex(SearchIndex $l) 1784 { 1785 $this->collSearchIndexs[] = $l; 1786 $l->setQuestion($this); 1787 } 1788 1789 /** 1652 1790 * Temporary storage of collReportQuestions to save a possible db hit in 1653 1791 * the event objects are add to the collection, but the trunk/lib/myTools.class.php
r12 r58 19 19 return $text; 20 20 } 21 22 public static function stemPhrase($phrase) 23 { 24 // split into words 25 $words = str_word_count(strtolower($phrase), 1); 26 27 // ignore stop words 28 $words = myTools::removeStopWordsFromArray($words); 29 30 // stem words 31 $stemmed_words = array(); 32 foreach ($words as $word) 33 { 34 // ignore 1 and 2 letter words 35 if (strlen($word) <= 2) 36 { 37 continue; 38 } 39 40 $stemmed_words[] = PorterStemmer::stem($word, true); 41 } 42 43 return $stemmed_words; 44 } 45 46 public static function removeStopWordsFromArray($words) 47 { 48 $stop_words = array( 49 'i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', 'your', 'yours', 50 'yourself', 'yourselves', 'he', 'him', 'his', 'himself', 'she', 'her', 'hers', 51 'herself', 'it', 'its', 'itself', 'they', 'them', 'their', 'theirs', 'themselves', 52 'what', 'which', 'who', 'whom', 'this', 'that', 'these', 'those', 'am', 'is', 'are', 53 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'having', 'do', 'does', 54 'did', 'doing', 'a', 'an', 'the', 'and', 'but', 'if', 'or', 'because', 'as', 'until', 55 'while', 'of', 'at', 'by', 'for', 'with', 'about', 'against', 'between', 'into', 56 'through', 'during', 'before', 'after', 'above', 'below', 'to', 'from', 'up', 'down', 57 'in', 'out', 'on', 'off', 'over', 'under', 'again', 'further', 'then', 'once', 'here', 58 'there', 'when', 'where', 'why', 'how', 'all', 'any', 'both', 'each', 'few', 'more', 59 'most', 'other', 'some', 'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 60 'than', 'too', 'very', 61 ); 62 63 return array_diff($words, $stop_words); 64 } 21 65 } 22 66
