Changeset 58

Show
Ignore:
Timestamp:
12/21/05 11:03:12 (3 years ago)
Author:
fabien
Message:

day 21 modifications

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/batch/load_data.php

    r12 r58  
    88require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php'); 
    99 
     10SearchIndexPeer::doDeleteAll(); 
     11 
    1012$data = new sfPropelData(); 
    1113$data->loadData(SF_DATA_DIR.DIRECTORY_SEPARATOR.'fixtures'); 
  • trunk/config/schema.xml

    r55 r58  
    9393   </table> 
    9494 
     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    
    95104   <table name="ask_report_question" phpName="ReportQuestion"> 
    96105     <column name="question_id" type="integer" primaryKey="true" /> 
  • trunk/data/sql/schema.sql

    r55 r58  
    139139Type=InnoDB; 
    140140# ----------------------------------------------------------------------- 
     141# ask_search_index  
     142# ----------------------------------------------------------------------- 
     143DROP TABLE IF EXISTS `ask_search_index`; 
     144 
     145CREATE 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) 
     154Type=InnoDB; 
     155# ----------------------------------------------------------------------- 
    141156# ask_report_question  
    142157# ----------------------------------------------------------------------- 
     
    186201   
    187202   
     203   
    188204# This restores the fkey checks, after having unset them 
    189205# in database-start.tpl 
  • trunk/frontend/config/app.yml

    r49 r58  
    99    max_questions:       10 
    1010 
     11  search: 
     12    body_weight:         1 
     13    title_weight:        2 
     14    tag_weight:          3 
     15    results_max:         10 
     16 
    1117  .global: 
    1218    universe:            on 
  • trunk/frontend/config/routing.yml

    r55 r58  
    1515  url:   /add_question 
    1616  param: { module: question, action: add } 
     17 
     18search_question: 
     19  url:   /search/* 
     20  param: { module: question, action: search } 
    1721 
    1822# answers 
  • trunk/frontend/modules/question/actions/actions.class.php

    r40 r58  
    5454  } 
    5555 
     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 
    5668  public function handleErrorAdd() 
    5769  { 
  • trunk/frontend/modules/question/templates/_question_list.php

    r55 r58  
    1 <?php use_helpers('Text', 'Global', 'Question', 'Date') ?> 
     1<?php use_helpers('Global') ?> 
    22 
    33<?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     &nbsp;-&nbsp; 
    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)) ?> 
    395<?php endforeach ?> 
    406 
  • trunk/frontend/modules/sidebar/templates/defaultSuccess.php

    r55 r58  
    99<div class="right" style="padding-top: 5px"><?php echo link_to('more popular tags &raquo;', '@popular_tags') ?></div> 
    1010 
     11<h2>find it</h2> 
     12<?php echo include_partial('question/search') ?> 
     13 
    1114<h2>browse askeet</h2> 
    1215<?php echo include_partial('rss_links') ?> 
  • trunk/frontend/modules/sidebar/templates/questionSuccess.php

    r55 r58  
    44  <?php echo link_to_login('ask a new question', '@add_question') ?> 
    55</div> 
     6 
     7<h2>find it</h2> 
     8<?php echo include_partial('question/search') ?> 
    69 
    710<h2>browse askeet</h2> 
  • trunk/lib/model/Question.php

    r55 r58  
    150150  } 
    151151 
     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 
    152235  public function deleteReports() 
    153236  { 
  • trunk/lib/model/QuestionPeer.php

    r55 r58  
    103103  } 
    104104 
     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 
    105175  public static function getReportedSpamPager($page) 
    106176  { 
  • trunk/lib/model/QuestionTag.php

    r31 r58  
    2323    $this->setNormalizedTag(Tag::normalize($v)); 
    2424  } 
     25 
     26  public function save($con = null) 
     27  { 
     28    $ret = parent::save($con); 
     29 
     30    $this->getQuestion()->updateSearchIndex(); 
     31 
     32    return $ret; 
     33  } 
    2534} 
    2635 
  • trunk/lib/model/om/BaseQuestion.php

    r55 r58  
    138138     */ 
    139139    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; 
    140152 
    141153    /** 
     
    683695            } 
    684696 
     697            if ($this->collSearchIndexs !== null) { 
     698                foreach($this->collSearchIndexs as $referrerFK) { 
     699                    if (!$referrerFK->isDeleted()) { 
     700                        $affectedRows += $referrerFK->save($con); 
     701                    } 
     702                } 
     703            } 
     704 
    685705            if ($this->collReportQuestions !== null) { 
    686706                foreach($this->collReportQuestions as $referrerFK) { 
     
    769789                if ($this->collQuestionTags !== null) { 
    770790                    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) { 
    771799                        if (($retval = $referrerFK->validate()) !== true) { 
    772800                            $failureMap = array_merge($failureMap, $retval); 
     
    10821110            } 
    10831111 
     1112            foreach($this->getSearchIndexs() as $relObj) { 
     1113                $copyObj->addSearchIndex($relObj->copy($deepCopy)); 
     1114            } 
     1115 
    10841116            foreach($this->getReportQuestions() as $relObj) { 
    10851117                $copyObj->addReportQuestion($relObj->copy($deepCopy)); 
     
    16501682 
    16511683    /** 
     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    /** 
    16521790     * Temporary storage of collReportQuestions to save a possible db hit in 
    16531791     * the event objects are add to the collection, but the 
  • trunk/lib/myTools.class.php

    r12 r58  
    1919    return $text; 
    2020  } 
     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  } 
    2165} 
    2266