Alpha Framework alpha--controller
[ class tree: alpha--controller ] [ index: alpha--controller ] [ all elements ]

Source for file Search.php

Documentation is available at Search.php

  1. <?php
  2.  
  3. // include the config file
  4. if(!isset($config)) {
  5.     require_once '../util/AlphaConfig.inc';
  6.     $config AlphaConfig::getInstance();
  7. }
  8.  
  9. require_once $config->get('sysRoot').'alpha/controller/AlphaController.inc';
  10. require_once $config->get('sysRoot').'alpha/controller/AlphaControllerInterface.inc';
  11. require_once $config->get('sysRoot').'alpha/model/TagObject.inc';
  12. require_once $config->get('sysRoot').'alpha/view/AlphaView.inc';
  13. require_once $config->get('sysRoot').'alpha/util/LogFile.inc';
  14. require_once $config->get('sysRoot').'alpha/util/AlphaKPI.inc';
  15.  
  16. /**
  17.  * 
  18.  * Generic tag-based search engine controller
  19.  * 
  20.  * @package alpha::controller
  21.  * @since 1.0
  22.  * @author John Collins <dev@alphaframework.org>
  23.  * @version $Id: Search.php 1453 2011-12-04 15:12:54Z johnc $
  24.  * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
  25.  * @copyright Copyright (c) 2011, John Collins (founder of Alpha Framework).
  26.  *  All rights reserved.
  27.  * 
  28.  *  <pre>
  29.  *  Redistribution and use in source and binary forms, with or
  30.  *  without modification, are permitted provided that the
  31.  *  following conditions are met:
  32.  * 
  33.  *  * Redistributions of source code must retain the above
  34.  *    copyright notice, this list of conditions and the
  35.  *    following disclaimer.
  36.  *  * Redistributions in binary form must reproduce the above
  37.  *    copyright notice, this list of conditions and the
  38.  *    following disclaimer in the documentation and/or other
  39.  *    materials provided with the distribution.
  40.  *  * Neither the name of the Alpha Framework nor the names
  41.  *    of its contributors may be used to endorse or promote
  42.  *    products derived from this software without specific
  43.  *    prior written permission.
  44.  *   
  45.  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  46.  *  CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  47.  *  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  48.  *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  49.  *  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
  50.  *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  51.  *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  52.  *  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  53.  *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  54.  *  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  55.  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
  56.  *  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  57.  *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  58.  *  </pre>
  59.  *  
  60.  */
  61. class Search extends AlphaController implements AlphaControllerInterface {
  62.     /**
  63.      * Trace logger
  64.      * 
  65.      * @var Logger 
  66.      * @since 1.0
  67.      */
  68.     private static $logger null;
  69.     
  70.     /**
  71.      * The start number for list pageination
  72.      * 
  73.      * @var integer 
  74.      * @since 1.0
  75.      */
  76.     protected $startPoint;
  77.     
  78.     /**
  79.      * The result count from the search
  80.      * 
  81.      * @var integer 
  82.      * @since 1.0
  83.      */
  84.     private $resultCount 0;
  85.     
  86.     /**
  87.      * The search query supplied
  88.      * 
  89.      * @var string 
  90.      * @since 1.0
  91.      */
  92.     private $query;
  93.     
  94.     /**
  95.      * constructor to set up the object
  96.      * 
  97.      * @param string $visibility The name of the rights group that can access this controller.
  98.      * @since 1.0
  99.      */
  100.     public function __construct($visibility='Public'{
  101.         self::$logger new Logger('Search');
  102.         self::$logger->debug('>>__construct(visibility=['.$visibility.'])');
  103.         
  104.         global $config;
  105.         
  106.         // ensure that the super class constructor is called, indicating the rights group
  107.         parent::__construct($visibility);
  108.         
  109.         self::$logger->debug('<<__construct');
  110.     }
  111.     
  112.     /**
  113.      * Handle GET requests
  114.      * 
  115.      * @param array $params 
  116.      * @since 1.0
  117.      * @throws IllegalArguementException
  118.      */
  119.     public function doGET($params{
  120.         self::$logger->debug('>>doGET($params=['.var_export($paramstrue).'])');
  121.         
  122.         if (isset($params['start']$this->startPoint = $params['start']$this->startPoint = 0);
  123.         
  124.         global $config;
  125.         
  126.         $KPI new AlphaKPI('search');
  127.         
  128.         if(isset($params['q'])) {
  129.             
  130.             $this->query $params['q'];
  131.             
  132.             // replace any %20 on the URL with spaces
  133.             $params['q'str_replace('%20'' '$params['q']);
  134.             
  135.             $this->setTitle('Search results - '.$params['q']);
  136.             echo AlphaView::displayPageHead($this);
  137.             
  138.             // log the user's search query in a log file
  139.             $log new LogFile($config->get('sysRoot').'logs/search.log');        
  140.             $log->writeLine(array($params['q']date('Y-m-d H:i:s')$_SERVER['HTTP_USER_AGENT']$_SERVER['REMOTE_ADDR']));
  141.             
  142.             $KPI->logStep('log search query');
  143.             
  144.             // if a BO name is provided, only search tags on that class, otherwise search all BOs
  145.             if(isset($params['bo']))
  146.                 $BOs array($params['bo']);
  147.             else            
  148.                 $BOs AlphaDAO::getBOClassNames();
  149.             
  150.             try {
  151.                 foreach($BOs as $BO{
  152.                     AlphaDAO::loadClassDef($BO);
  153.                     $temp new $BO;
  154.                     
  155.                     if($temp->isTagged()) {
  156.                         $BOIDs $this->doTagSearch($params['q']$BO);
  157.                         
  158.                         $this->resultCount += count($BOIDs);
  159.                         
  160.                         $KPI->logStep('built array of matching OIDs');
  161.                         
  162.                         // sort the BO IDs based on tag frequency weight                        
  163.                         arsort($BOIDs);                        
  164.                         
  165.                         // render the list view for each BO
  166.                         $this->renderResultList($BOIDs$BO$params['q']);                        
  167.                         
  168.                         AlphaDAO::disconnect();
  169.                     }
  170.                 }
  171.             }catch(Exception $e{
  172.                 self::$logger->error($e->getMessage());
  173.                 throw new IllegalArguementException('Error occured while searching for: ['.$params['q'].']');                
  174.             }
  175.         }else{
  176.             $this->setTitle('Search results');            
  177.             echo AlphaView::displayPageHead($this);
  178.             self::$logger->debug('No search query provided!');
  179.         }        
  180.         
  181.         echo AlphaView::displayPageFoot($this);
  182.         
  183.         $KPI->log();
  184.         
  185.         self::$logger->debug('<<doGET');
  186.     }
  187.     
  188.     /**
  189.      * Searches for tags on the given BO type matching the query provided
  190.      * 
  191.      * @param string $query 
  192.      * @param string $BOName 
  193.      * @param array $BOIDs 
  194.      * @return array 
  195.      * @since 1.1
  196.      */
  197.     protected function doTagSearch($query$BOName$BOIDs=array()) {
  198.         // explode the user's query into a set of tokenized transient TagObjects
  199.         $queryTags TagObject::tokenize($query''''false);            
  200.         $matchingTags array();
  201.                         
  202.         // load TagObjects from the DB where content equals the content of one of our transient TagObjects
  203.         foreach($queryTags as $queryTag{
  204.             $tags $queryTag->loadAllByAttribute('content'$queryTag->get('content'));
  205.             $matchingTags array_merge($matchingTags$tags);
  206.         }
  207.                         
  208.         self::$logger->debug('There are ['.count($matchingTags).'] TagObjects matching the query ['.$query.']');
  209.                         
  210.         /*
  211.          * Build an array of BOs for the matching tags from the DB:
  212.          * array key = BO ID
  213.          * array value = weight (the amount of tags matching the BO)
  214.          */
  215.         foreach($matchingTags as $tag{                            
  216.             if($tag->get('taggedClass'== $BOName{
  217.                 
  218.                 if(isset($BOIDs[$tag->get('taggedOID')])) {
  219.                     // increment the weight if the same BO is tagged more than once
  220.                     $weight intval($BOIDs[$tag->get('taggedOID')]1;
  221.                     $BOIDs[$tag->get('taggedOID')$weight;                                    
  222.                 }else{
  223.                     $BOIDs[$tag->get('taggedOID')1;                                    
  224.                 }
  225.                 
  226.                 self::$logger->debug('Found BO ['.$tag->get('taggedOID').'] has weight ['.$BOIDs[$tag->get('taggedOID')].']');                                
  227.             }
  228.         }
  229.         
  230.         return $BOIDs;
  231.     }
  232.     
  233.     /**
  234.      * Renders the search result list
  235.      * 
  236.      * @param array $BOIDs 
  237.      * @param string $BO 
  238.      * @param string $query 
  239.      * @param bool $showTags 
  240.      * @param bool $showScore 
  241.      * @since 1.0
  242.      */
  243.     protected function renderResultList($BOIDs$BO$query=''$showTags=true$showScore=false{
  244.         global $config;
  245.         
  246.         // used to track when our pagination range ends
  247.         $end ($this->startPoint+$config->get('sysListPageAmount'));
  248.         // used to track how many results have been displayed or skipped from the pagination range
  249.         $displayedCount 0;
  250.         
  251.         if(!empty($query))
  252.             echo '<h2>Displaying results for &quot;'.$query.'&quot;</h2>';
  253.             
  254.         foreach(array_keys($BOIDsas $oid{
  255.             try {
  256.                 // if we have reached the end of the pagination range then break out
  257.                 if($displayedCount == $end)
  258.                     break;
  259.                             
  260.                 // if our display count is >= the start but < the end...
  261.                 if($displayedCount >= $this->startPoint{
  262.                     $temp new $BO;
  263.                     $temp->load($oid);
  264.                     
  265.                     if($temp instanceof ArticleObject && $temp->get('published'== false){
  266.                         $this->resultCount--;
  267.                     }else{            
  268.                         $view AlphaView::getInstance($temp);
  269.                         echo $view->listView();
  270.  
  271.                         if($showTags{
  272.                             $tags $temp->getPropObject('tags')->getRelatedObjects();
  273.                         
  274.                             if(count($tags0{
  275.                                 echo '<p>Tags: ';
  276.                                                 
  277.                                 $queryTerms explode(' 'strtolower($query));
  278.                                                 
  279.                                 foreach($tags as $tag{
  280.                                     echo (in_array($tag->get('content')$queryTerms'<strong>'.$tag->get('content').' </strong>' $tag->get('content').' ');
  281.                                 }
  282.                                             
  283.                                 echo '</p>';
  284.                             }
  285.                         }
  286.                         
  287.                         if($showScore{
  288.                             $score $BOIDs[$oid];
  289.                             
  290.                             echo '<p>Score: <strong>'.$score.'</strong></p>';
  291.                         }
  292.                     }
  293.                 }
  294.                             
  295.                 $displayedCount++;
  296.             }catch(BONotFoundException $e{
  297.                 self::$logger->warn('Orpaned TagObject detected pointing to a non-existant BO of OID ['.$oid.'] and type ['.$BO.'].');
  298.             }
  299.         }
  300.     }
  301.     
  302.     /**
  303.      * Handle POST requests
  304.      * 
  305.      * @param array $params 
  306.      * @since 1.0
  307.      */
  308.     public function doPOST($params{
  309.         self::$logger->debug('>>doPOST($params=['.var_export($paramstrue).'])');
  310.         
  311.         self::$logger->debug('<<doPOST');
  312.     }
  313.     
  314.     /**
  315.      * Displays a search form on the top of the page
  316.      * 
  317.      * @return string 
  318.      * @since 1.0
  319.      */
  320.     public function after_displayPageHead_callback({
  321.         global $config;
  322.         
  323.         $html '<div align="center"><form method="GET" id="search_form" onsubmit="document.location = \''.$config->get('sysURL').'search/q/\'+document.getElementById(\'q\').value; return false;">';
  324.         $html .= 'Search for: <input type="text" size="80" name="q" id="q"/>&nbsp;';        
  325.         $button new Button('document.location = \''.$config->get('sysURL').'search/q/\'+document.getElementById(\'q\').value''Search''searchButton');
  326.         $html .= $button->render();
  327.         $html .= '</form></div>';
  328.         
  329.         return $html;
  330.     }
  331.     
  332.     /**
  333.      * Method to display the page footer with pageination links
  334.      * 
  335.      * @return string 
  336.      * @since 1.0
  337.      */
  338.     public function before_displayPageFoot_callback({
  339.         $html $this->renderPageLinks();
  340.         
  341.         $html .= '<br>';
  342.         
  343.         return $html;
  344.     }
  345.     
  346.     /**
  347.      * Method for rendering the pagination links
  348.      * 
  349.      * @return string 
  350.      * @since 1.0
  351.      */
  352.     protected function renderPageLinks({
  353.         global $config;
  354.         
  355.         $html '';
  356.         
  357.         $end ($this->startPoint+$config->get('sysListPageAmount'));
  358.         
  359.         if($end $this->resultCount)
  360.             $end $this->resultCount;
  361.         
  362.         if($this->resultCount 0{
  363.             $html .= '<p align="center">Displaying '.($this->startPoint+1).' to '.$end.' of <strong>'.$this->resultCount.'</strong>.&nbsp;&nbsp;';
  364.         }else{
  365.             if(!empty($this->query))
  366.                 $html .= AlphaView::displayUpdateMessage('There were no search results for your query.');
  367.         }    
  368.                 
  369.         if ($this->startPoint > 0{
  370.             // handle secure URLs
  371.             if(isset($_GET['tk']))
  372.                 $html .= '<a href="'.FrontController::generateSecureURL('act=Search&q='.$this->query.'&start='.($this->startPoint-$config->get('sysListPageAmount'))).'">&lt;&lt;-Previous</a>&nbsp;&nbsp;';
  373.             else
  374.                 $html .= '<a href="'.$config->get('sysURL').'search/q/'.$this->query.'/start/'.($this->startPoint-$config->get('sysListPageAmount')).'">&lt;&lt;-Previous</a>&nbsp;&nbsp;';
  375.         }elseif($this->resultCount $config->get('sysListPageAmount')){
  376.             $html .= '&lt;&lt;-Previous&nbsp;&nbsp;';
  377.         }
  378.         $page 1;
  379.         for ($i 0$i $this->resultCount$i+=$config->get('sysListPageAmount')) {
  380.             if($i != $this->startPoint{
  381.                 // handle secure URLs
  382.                 if(isset($_GET['tk']))
  383.                     $html .= '&nbsp;<a href="'.FrontController::generateSecureURL('act=Search&q='.$this->query.'&start='.$i).'">'.$page.'</a>&nbsp;';
  384.                 else
  385.                     $html .= '&nbsp;<a href="'.$config->get('sysURL').'search/q/'.$this->query.'/start/'.$i.'">'.$page.'</a>&nbsp;';
  386.             }elseif($this->resultCount $config->get('sysListPageAmount')){
  387.                 $html .= '&nbsp;'.$page.'&nbsp;';
  388.             }
  389.             $page++;
  390.         }
  391.         if ($this->resultCount $end{
  392.             // handle secure URLs
  393.             if(isset($_GET['tk']))
  394.                 $html .= '&nbsp;&nbsp;<a href="'.FrontController::generateSecureURL('act=Search&q='.$this->query.'&start='.($this->startPoint+$config->get('sysListPageAmount'))).'">Next-&gt;&gt;</a>';
  395.             else
  396.                 $html .= '&nbsp;&nbsp;<a href="'.$config->get('sysURL').'search/q/'.$this->query.'/start/'.($this->startPoint+$config->get('sysListPageAmount')).'">Next-&gt;&gt;</a>';
  397.         }elseif($this->resultCount $config->get('sysListPageAmount')){
  398.             $html .= '&nbsp;&nbsp;Next-&gt;&gt;';
  399.         }
  400.         $html .= '</p>';
  401.         
  402.         return $html;
  403.     }
  404. }
  405.  
  406. // now build the new controller
  407. if(basename($_SERVER['PHP_SELF']== 'Search.php'{
  408.     $controller new Search();
  409.     
  410.     if(!empty($_POST)) {            
  411.         $controller->doPOST($_REQUEST);
  412.     }else{
  413.         $controller->doGET($_GET);
  414.     }
  415. }
  416.  
  417. ?>

Documentation generated on Tue, 13 Dec 2011 20:27:28 +0000 by phpDocumentor 1.4.3