system/classes/search.class.php
author Dirk Haun <dirk@haun-online.de>
Sat, 07 Nov 2009 21:52:55 +0100
branchHEAD
changeset 7456 4ef1c9a5915c
parent 7449 ad0616900bdb
child 7459 0f9a8d809cc5
permissions -rw-r--r--
"Refine search" lost the status of the "Titles Only" checkbox
     1 <?php
     2 
     3 /* Reminder: always indent with 4 spaces (no tabs). */
     4 // +---------------------------------------------------------------------------+
     5 // | Geeklog 1.6.1                                                             |
     6 // +---------------------------------------------------------------------------+
     7 // | search.class.php                                                          |
     8 // |                                                                           |
     9 // | Geeklog search class.                                                     |
    10 // +---------------------------------------------------------------------------+
    11 // | Copyright (C) 2000-2009 by the following authors:                         |
    12 // |                                                                           |
    13 // | Authors: Tony Bibbs       - tony AT geeklog DOT net                       |
    14 // |          Dirk Haun        - dirk AT haun-online DOT de                    |
    15 // |          Sami Barakat     - s.m.barakat AT gmail DOT com                  |
    16 // +---------------------------------------------------------------------------+
    17 // |                                                                           |
    18 // | This program is free software; you can redistribute it and/or             |
    19 // | modify it under the terms of the GNU General Public License               |
    20 // | as published by the Free Software Foundation; either version 2            |
    21 // | of the License, or (at your option) any later version.                    |
    22 // |                                                                           |
    23 // | This program is distributed in the hope that it will be useful,           |
    24 // | but WITHOUT ANY WARRANTY; without even the implied warranty of            |
    25 // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             |
    26 // | GNU General Public License for more details.                              |
    27 // |                                                                           |
    28 // | You should have received a copy of the GNU General Public License         |
    29 // | along with this program; if not, write to the Free Software Foundation,   |
    30 // | Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.           |
    31 // |                                                                           |
    32 // +---------------------------------------------------------------------------+
    33 
    34 if (strpos(strtolower($_SERVER['PHP_SELF']), 'search.class.php') !== false) {
    35     die('This file can not be used on its own.');
    36 }
    37 
    38 require_once $_CONF['path_system'] . 'classes/plugin.class.php';
    39 require_once $_CONF['path_system'] . 'classes/searchcriteria.class.php';
    40 require_once $_CONF['path_system'] . 'classes/listfactory.class.php';
    41 
    42 /**
    43 * Geeklog Search Class
    44 *
    45 * @author Tony Bibbs, tony AT geeklog DOT net
    46 * @package net.geeklog.search
    47 *
    48 */
    49 class Search {
    50 
    51     // PRIVATE VARIABLES
    52     var $_query = '';
    53     var $_topic = '';
    54     var $_dateStart = null;
    55     var $_dateEnd = null;
    56     var $_author = '';
    57     var $_type = '';
    58     var $_keyType = '';
    59     var $_names = array();
    60     var $_url_rewrite = array();
    61     var $_append_query = array();
    62     var $_searchURL = '';
    63     var $_wordlength;
    64     var $_verbose = false; // verbose logging
    65     var $_titlesOnly = false;
    66 
    67     /**
    68     * Constructor
    69     *
    70     * Sets up private search variables
    71     *
    72     * @author Tony Bibbs, tony AT geeklog DOT net
    73     * @access public
    74     *
    75     */
    76     function Search()
    77     {
    78         global $_CONF, $_TABLES;
    79 
    80         // Set search criteria
    81         if (isset ($_GET['query'])) {
    82             $this->_query = strip_tags (COM_stripslashes ($_GET['query']));
    83         }
    84         if (isset ($_GET['topic'])) {
    85             $this->_topic = COM_applyFilter ($_GET['topic']);
    86         }
    87         if (isset ($_GET['datestart'])) {
    88             $this->_dateStart = COM_applyFilter ($_GET['datestart']);
    89         }
    90         if (isset ($_GET['dateend'])) {
    91             $this->_dateEnd = COM_applyFilter ($_GET['dateend']);
    92         }
    93         if (isset ($_GET['author'])) {
    94             $this->_author = COM_applyFilter($_GET['author']);
    95 
    96             // In case we got a username instead of uid, convert it.  This should
    97             // make custom themes for search page easier.
    98             if (!is_numeric($this->_author) && !preg_match('/^([0-9]+)$/', $this->_author) && $this->_author != '') {
    99                 $this->_author = DB_getItem($_TABLES['users'], 'uid', 'username=\'' . addslashes ($this->_author) . '\'');
   100             }
   101 
   102             if ($this->_author < 1) {
   103                 $this->_author = '';
   104             }
   105         }
   106         $this->_type = isset($_GET['type']) ? COM_applyFilter($_GET['type']) : 'all';
   107         $this->_keyType = isset($_GET['keyType']) ? COM_applyFilter($_GET['keyType']) : $_CONF['search_def_keytype'];
   108 
   109         $this->_titlesOnly = isset($_GET['title']) ? true : false;
   110     }
   111 
   112     /**
   113     * Shows an error message to anonymous users
   114     *
   115     * This is called when anonymous users attempt to access search
   116     * functionality that has been locked down by the Geeklog admin.
   117     *
   118     * @author Tony Bibbs, tony AT geeklog DOT net
   119     * @access private
   120     * @return string HTML output for error message
   121     *
   122     */
   123     function _getAccessDeniedMessage()
   124     {
   125         global $_CONF, $LANG_LOGIN;
   126 
   127         $retval .= COM_startBlock ($LANG_LOGIN[1], '',
   128                         COM_getBlockTemplate ('_msg_block', 'header'));
   129         $login = new Template($_CONF['path_layout'] . 'submit');
   130         $login->set_file (array ('login'=>'submitloginrequired.thtml'));
   131         $login->set_var ( 'xhtml', XHTML );
   132         $login->set_var ('login_message', $LANG_LOGIN[2]);
   133         $login->set_var ('site_url', $_CONF['site_url']);
   134         $login->set_var ('site_admin_url', $_CONF['site_admin_url']);
   135         $login->set_var ('layout_url', $_CONF['layout_url']);
   136         $login->set_var ('lang_login', $LANG_LOGIN[3]);
   137         $login->set_var ('lang_newuser', $LANG_LOGIN[4]);
   138         $login->parse ('output', 'login');
   139         $retval .= $login->finish ($login->get_var('output'));
   140         $retval .= COM_endBlock (COM_getBlockTemplate ('_msg_block', 'footer'));
   141 
   142         return $retval;
   143     }
   144 
   145     /**
   146     * Determines if user is allowed to perform a search
   147     *
   148     * Geeklog has a number of settings that may prevent
   149     * the access anonymous users have to the search engine.
   150     * This performs those checks
   151     *
   152     * @author Tony Bibbs, tony AT geeklog DOT net
   153     * @access private
   154     * @return boolean True if search is allowed, otherwise false
   155     *
   156     */
   157     function _isSearchAllowed()
   158     {
   159         global $_USER, $_CONF;
   160 
   161         if (empty($_USER['username'])) {
   162             //check if an anonymous user is attempting to illegally access privilege search capabilities
   163             if (($this->_type != 'all') OR !empty($this->_dateStart) OR !empty($this->_dateEnd) OR ($this->_author > 0) OR !empty($topic)) {
   164                 if (($_CONF['loginrequired'] == 1) OR ($_CONF['searchloginrequired'] >= 1)) {
   165                     return false;
   166                 }
   167             } else {
   168                 if (($_CONF['loginrequired'] == 1) OR ($_CONF['searchloginrequired'] == 2)) {
   169                     return false;
   170                 }
   171             }
   172         }
   173 
   174         return true;
   175     }
   176 
   177     /**
   178     * Determines if user is allowed to use the search form
   179     *
   180     * Geeklog has a number of settings that may prevent
   181     * the access anonymous users have to the search engine.
   182     * This performs those checks
   183     *
   184     * @author Dirk Haun, dirk AT haun-online DOT de
   185     * @access private
   186     * @return boolean True if form usage is allowed, otherwise false
   187     *
   188     */
   189     function _isFormAllowed ()
   190     {
   191         global $_CONF, $_USER;
   192 
   193         if (empty($_USER['username']) AND (($_CONF['loginrequired'] == 1) OR ($_CONF['searchloginrequired'] >= 1))) {
   194             return false;
   195         }
   196 
   197         return true;
   198     }
   199 
   200     /**
   201     * Shows search form
   202     *
   203     * Shows advanced search page
   204     *
   205     * @author Tony Bibbs, tony AT geeklog DOT net
   206     * @access public
   207     * @return string HTML output for form
   208     *
   209     */
   210     function showForm ()
   211     {
   212         global $_CONF, $_TABLES, $LANG09;
   213 
   214         $retval = '';
   215 
   216         // Verify current user my use the search form
   217         if (!$this->_isFormAllowed()) {
   218             return $this->_getAccessDeniedMessage();
   219         }
   220 
   221         $retval .= COM_startBlock($LANG09[1],'advancedsearch.html');
   222         $searchform = new Template($_CONF['path_layout'].'search');
   223         $searchform->set_file (array ('searchform' => 'searchform.thtml',
   224                                       'authors'    => 'searchauthors.thtml'));
   225         $searchform->set_var( 'xhtml', XHTML );
   226         $searchform->set_var('search_intro', $LANG09[19]);
   227         $searchform->set_var('site_url', $_CONF['site_url']);
   228         $searchform->set_var('site_admin_url', $_CONF['site_admin_url']);
   229         $searchform->set_var('layout_url', $_CONF['layout_url']);
   230         $searchform->set_var('lang_keywords', $LANG09[2]);
   231         $searchform->set_var('lang_date', $LANG09[20]);
   232         $searchform->set_var('lang_to', $LANG09[21]);
   233         $searchform->set_var('date_format', $LANG09[22]);
   234         $searchform->set_var('lang_topic', $LANG09[3]);
   235         $searchform->set_var('lang_all', $LANG09[4]);
   236         $searchform->set_var('topic_option_list',
   237                             COM_topicList ('tid,topic', $this->_topic));
   238         $searchform->set_var('lang_type', $LANG09[5]);
   239         $searchform->set_var('lang_results', $LANG09[59]);
   240         $searchform->set_var('lang_per_page', $LANG09[60]);
   241 
   242         $searchform->set_var('lang_exact_phrase', $LANG09[43]);
   243         $searchform->set_var('lang_all_words', $LANG09[44]);
   244         $searchform->set_var('lang_any_word', $LANG09[45]);
   245         $searchform->set_var('lang_titles', $LANG09[69]);
   246 
   247         $escquery = htmlspecialchars($this->_query);
   248         $escquery = str_replace(array('{', '}'), array('&#123;', '&#125;'),
   249                                 $escquery);
   250         $searchform->set_var ('query', $escquery);
   251         $searchform->set_var ('datestart', $this->_dateStart);
   252         $searchform->set_var ('dateend', $this->_dateEnd);
   253         if ($this->_titlesOnly) {
   254             $searchform->set_var('title_checked', ' checked="checked"');
   255         } else {
   256             $searchform->set_var('title_checked', '');
   257         }
   258 
   259         $phrase_selected = '';
   260         $all_selected = '';
   261         $any_selected = '';
   262         if ($this->_keyType == 'phrase') {
   263             $phrase_selected = 'selected="selected"';
   264         } else if ($this->_keyType == 'all') {
   265             $all_selected = 'selected="selected"';
   266         } else if ($this->_keyType == 'any') {
   267             $any_selected = 'selected="selected"';
   268         }
   269         $searchform->set_var ('key_phrase_selected', $phrase_selected);
   270         $searchform->set_var ('key_all_selected', $all_selected);
   271         $searchform->set_var ('key_any_selected', $any_selected);
   272 
   273         $options = '';
   274         $plugintypes = array('all' => $LANG09[4], 'stories' => $LANG09[6], 'comments' => $LANG09[7]);
   275         $plugintypes = array_merge($plugintypes, PLG_getSearchTypes());
   276         // Generally I don't like to hardcode HTML but this seems easiest
   277         foreach ($plugintypes as $key => $val) {
   278             $options .= "<option value=\"$key\"";
   279             if ($this->_type == $key)
   280                 $options .= ' selected="selected"';
   281             $options .= ">$val</option>".LB;
   282         }
   283         $searchform->set_var('plugin_types', $options);
   284 
   285         if ($_CONF['contributedbyline'] == 1) {
   286             $searchform->set_var('lang_authors', $LANG09[8]);
   287             $searchusers = array();
   288             $result = DB_query("SELECT DISTINCT uid FROM {$_TABLES['comments']}");
   289             while ($A = DB_fetchArray($result)) {
   290                 $searchusers[$A['uid']] = $A['uid'];
   291             }
   292             $result = DB_query("SELECT DISTINCT uid FROM {$_TABLES['stories']} WHERE (date <= NOW()) AND (draft_flag = 0)");
   293             while ($A = DB_fetchArray($result)) {
   294                 $searchusers[$A['uid']] = $A['uid'];
   295             }
   296 
   297             $inlist = implode(',', $searchusers);
   298 
   299             if (!empty ($inlist)) {
   300                 $sql = "SELECT uid,username,fullname FROM {$_TABLES['users']} WHERE uid IN ($inlist)";
   301                 if (isset ($_CONF['show_fullname']) && ($_CONF['show_fullname'] == 1)) {
   302                     /* Caveat: This will group all users with an emtpy fullname
   303                      *         together, so it's not exactly sorted by their
   304                      *         full name ...
   305                      */
   306                     $sql .= ' ORDER BY fullname,username';
   307                 } else {
   308                     $sql .= ' ORDER BY username';
   309                 }
   310                 $result = DB_query ($sql);
   311                 $options = '';
   312                 while ($A = DB_fetchArray($result)) {
   313                     $options .= '<option value="' . $A['uid'] . '"';
   314                     if ($A['uid'] == $this->_author) {
   315                         $options .= ' selected="selected"';
   316                     }
   317                     $options .= '>' . htmlspecialchars(COM_getDisplayName('', $A['username'], $A['fullname'])) . '</option>';
   318                 }
   319                 $searchform->set_var('author_option_list', $options);
   320                 $searchform->parse('author_form_element', 'authors', true);
   321             } else {
   322                 $searchform->set_var('author_form_element', '<input type="hidden" name="author" value="0"' . XHTML . '>');
   323             }
   324         } else {
   325             $searchform->set_var ('author_form_element',
   326                     '<input type="hidden" name="author" value="0"' . XHTML . '>');
   327         }
   328 
   329         // Results per page
   330         $options = '';
   331         $limits = explode(',', $_CONF['search_limits']);
   332         foreach ($limits as $limit) {
   333             $options .= "<option value=\"$limit\"";
   334             if ($_CONF['num_search_results'] == $limit) {
   335                 $options .= ' selected="selected"';
   336             }
   337             $options .= ">$limit</option>" . LB;
   338         }
   339         $searchform->set_var('search_limits', $options);
   340 
   341         $searchform->set_var('lang_search', $LANG09[10]);
   342         $searchform->parse('output', 'searchform');
   343 
   344         $retval .= $searchform->finish($searchform->get_var('output'));
   345         $retval .= COM_endBlock();
   346 
   347         return $retval;
   348     }
   349 
   350     /**
   351     * Performs search on all stories
   352     *
   353     * @access private
   354     * @return object plugin object
   355     *
   356     */
   357     function _searchStories()
   358     {
   359         global $_TABLES, $_DB_dbms, $LANG09;
   360 
   361         // Make sure the query is SQL safe
   362         $query = trim(addslashes($this->_query));
   363 
   364         $sql = 'SELECT s.sid AS id, s.title AS title, s.introtext AS description, ';
   365         $sql .= 'UNIX_TIMESTAMP(s.date) AS date, s.uid AS uid, s.hits AS hits, ';
   366         $sql .= 'CONCAT(\'/article.php?story=\',s.sid) AS url ';
   367         $sql .= 'FROM '.$_TABLES['stories'].' AS s, '.$_TABLES['users'].' AS u ';
   368         $sql .= 'WHERE (draft_flag = 0) AND (date <= NOW()) AND (u.uid = s.uid) ';
   369         $sql .= COM_getPermSQL('AND') . COM_getTopicSQL('AND') . COM_getLangSQL('sid', 'AND') . ' ';
   370 
   371         if (!empty($this->_topic)) {
   372             $sql .= 'AND (s.tid = \''.$this->_topic.'\') ';
   373         }
   374         if (!empty($this->_author)) {
   375             $sql .= 'AND (s.uid = \''.$this->_author.'\') ';
   376         }
   377 
   378         $search_s = new SearchCriteria('stories', $LANG09[65]);
   379 
   380         $columns = array('title' => 'title', 'introtext', 'bodytext');
   381         $sql .= $search_s->getDateRangeSQL('AND', 'date', $this->_dateStart, $this->_dateEnd);
   382         list($sql, $ftsql) = $search_s->buildSearchSQL($this->_keyType, $query, $columns, $sql);
   383 
   384         $search_s->setSQL($sql);
   385         $search_s->setFTSQL($ftsql);
   386         $search_s->setRank(5);
   387         $search_s->setURLRewrite(true);
   388 
   389         // Search Story Comments
   390         $sql = 'SELECT c.cid AS id, c.title AS title, c.comment AS description, ';
   391         $sql .= 'UNIX_TIMESTAMP(c.date) AS date, c.uid AS uid, ';
   392 
   393         // MSSQL has a problem when concatenating numeric values
   394         if ($_DB_dbms == 'mssql') {
   395             $sql .= '\'/comment.php?mode=view&amp;cid=\' + CAST(c.cid AS varchar(10)) AS url ';
   396         } else {
   397             $sql .= 'CONCAT(\'/comment.php?mode=view&amp;cid=\',c.cid) AS url ';
   398         }
   399 
   400         $sql .= 'FROM '.$_TABLES['users'].' AS u, '.$_TABLES['comments'].' AS c ';
   401         $sql .= 'LEFT JOIN '.$_TABLES['stories'].' AS s ON ((s.sid = c.sid) ';
   402         $sql .= COM_getPermSQL('AND',0,2,'s').COM_getTopicSQL('AND',0,'s').COM_getLangSQL('sid','AND','s').') ';
   403         $sql .= 'WHERE (u.uid = c.uid) AND (s.draft_flag = 0) AND (s.commentcode >= 0) AND (s.date <= NOW()) ';
   404 
   405         if (!empty($this->_topic)) {
   406             $sql .= 'AND (s.tid = \''.$this->_topic.'\') ';
   407         }
   408         if (!empty($this->_author)) {
   409             $sql .= 'AND (c.uid = \''.$this->_author.'\') ';
   410         }
   411 
   412         $search_c = new SearchCriteria('comments', array($LANG09[65],$LANG09[66]));
   413 
   414         $columns = array('title' => 'c.title', 'comment');
   415         $sql .= $search_c->getDateRangeSQL('AND', 'c.date', $this->_dateStart, $this->_dateEnd);
   416         list($sql, $ftsql) = $search_c->buildSearchSQL($this->_keyType, $query, $columns, $sql);
   417 
   418         $search_c->setSQL($sql);
   419         $search_c->setFTSQL($ftsql);
   420         $search_c->setRank(2);
   421 
   422         return array($search_s, $search_c);
   423     }
   424 
   425     /**
   426     * Kicks off the appropriate search(es)
   427     *
   428     * Initiates the search engine and returns HTML formatted
   429     * results. It also provides support to plugins using a
   430     * search API. Backwards compatibility has been incorporated
   431     * in this function to allow legacy support to plugins using
   432     * the old API calls defined versions prior to Geeklog 1.5.1
   433     *
   434     * @access public
   435     * @return string HTML output for search results
   436     *
   437     */
   438     function doSearch()
   439     {
   440         global $_CONF, $LANG01, $LANG09, $LANG31;
   441 
   442         // Verify current user can perform requested search
   443         if (!$this->_isSearchAllowed())
   444         {
   445             return $this->_getAccessDeniedMessage();
   446         }
   447 
   448         // Make sure there is a query string
   449         // Full text searches have a minimum word length of 3 by default
   450         if ((empty($this->_query) && empty($this->_author) && empty($this->_topic)) || ($_CONF['search_use_fulltext'] && strlen($this->_query) < 3))
   451         {
   452             $retval = '<p>' . $LANG09[41] . '</p>' . LB;
   453             $retval .= $this->showForm();
   454 
   455             return $retval;
   456         }
   457 
   458         // Build the URL strings
   459         $this->_searchURL = $_CONF['site_url'] . '/search.php?query=' . urlencode($this->_query) .
   460             ((!empty($this->_keyType))    ? '&amp;keyType=' . $this->_keyType : '' ) .
   461             ((!empty($this->_dateStart))  ? '&amp;datestart=' . $this->_dateStart : '' ) .
   462             ((!empty($this->_dateEnd))    ? '&amp;dateend=' . $this->_dateEnd : '' ) .
   463             ((!empty($this->_topic))      ? '&amp;topic=' . $this->_topic : '' ) .
   464             ((!empty($this->_author))     ? '&amp;author=' . $this->_author : '' ) .
   465             ($this->_titlesOnly           ? '&amp;title=true' : '');
   466 
   467         $url = "{$this->_searchURL}&amp;type={$this->_type}&amp;mode=";
   468         $obj = new ListFactory($url.'search', $_CONF['search_limits'], $_CONF['num_search_results']);
   469         $obj->setField('ID', 'id', false);
   470         $obj->setField('URL', 'url', false);
   471 
   472         $show_num  = $_CONF['search_show_num'];
   473         $show_type = $_CONF['search_show_type'];
   474         $show_user = $_CONF['contributedbyline'];
   475         $show_hits = !$_CONF['hideviewscount'];
   476         $style = isset($_CONF['search_style']) ? $_CONF['search_style'] : 'google';
   477 
   478         if ($style == 'table')
   479         {
   480             $obj->setStyle('table');
   481             //             Title        Name            Display     Sort   Format
   482             $obj->setField($LANG09[62], LF_ROW_NUMBER,  $show_num,  false, '<b>%d.</b>');
   483             $obj->setField($LANG09[5],  LF_SOURCE_TITLE,$show_type, true,  '<b>%s</b>');
   484             $obj->setField($LANG09[16], 'title',        true,       true);
   485             $obj->setField($LANG09[63], 'description',  true,       false);
   486             $obj->setField($LANG09[17], 'date',         true,       true);
   487             $obj->setField($LANG09[18], 'uid',          $show_user, true);
   488             $obj->setField($LANG09[50], 'hits',         $show_hits, true);
   489             $this->_wordlength = 7;
   490         }
   491         else if ($style == 'google')
   492         {
   493             $sort_uid = $this->_author == '' ? true : false;
   494             $sort_date = empty($this->_dateStart) || empty($this->_dateEnd) || $this->_dateStart != $this->_dateEnd ? true : false;
   495             $sort_type = $this->_type == 'all' ? true : false;
   496             $obj->setStyle('inline');
   497             $obj->setField('',          LF_ROW_NUMBER,  $show_num,  false, '<b>%d.</b>');
   498             $obj->setField($LANG09[16], 'title',        true,       true,  '%s<br' . XHTML . '>');
   499             $obj->setField('',          'description',  true,       false, '%s<br' . XHTML . '>');
   500             $obj->setField('',          '_html',        true,       false, '<span style="color:green;">');
   501             $obj->setField($LANG09[18], 'uid',          $show_user, $sort_uid,  $LANG01[104].' %s ');
   502             $obj->setField($LANG09[17], 'date',         true,       $sort_date,  $LANG01[36].' %s');
   503             $obj->setField($LANG09[5],  LF_SOURCE_TITLE,$show_type, $sort_type,  ' - %s');
   504             $obj->setField($LANG09[50], 'hits',         $show_hits, true,  ' - %s '.$LANG09[50]);
   505             $obj->setField('',          '_html',        true,       false, '</span>');
   506             $this->_wordlength = 50;
   507         }
   508         $obj->setDefaultSort('hits');
   509         // set this only now, for compatibility with PHP 4
   510         $obj->setRowFunction(array($this, 'searchFormatCallback'));
   511 
   512         // Start search timer
   513         $searchtimer = new timerobject();
   514         $searchtimer->setPrecision(4);
   515         $searchtimer->startTimer();
   516 
   517         // Have plugins do their searches
   518         $page = isset($_GET['page']) ? COM_applyFilter($_GET['page'], true) : 1;
   519         $result_plugins = PLG_doSearch($this->_query, $this->_dateStart,
   520             $this->_dateEnd, $this->_topic, $this->_type,
   521             $this->_author, $this->_keyType, $page, 5);
   522 
   523         // Add core searches
   524         $result_plugins = array_merge($result_plugins, $this->_searchStories());
   525 
   526         // Loop through all plugins separating the new API from the old
   527         $new_api = 0;
   528         $old_api = 0;
   529         $num_results = 0;
   530 
   531         foreach ($result_plugins as $result)
   532         {
   533             if (is_a($result, 'SearchCriteria'))
   534             {
   535                 $debug_info = $result->getName().' using APIv2';
   536 
   537                 if ($this->_type != 'all' && $this->_type != $result->getName())
   538                 {
   539                     if ($this->_verbose) {
   540                         $new_api++;
   541                         COM_errorLog($debug_info.'. Skipped as type is not '.$this->_type);
   542                     }
   543                     continue;
   544                 }
   545 
   546                 $api_results = $result->getResults();
   547                 if (!empty($api_results)) {
   548                     $obj->addResultArray($api_results);
   549                 }
   550 
   551                 $api_callback_func = $result->getCallback();
   552                 if (!empty($api_callback_func))
   553                 {
   554                     $debug_info .= ' with Callback Function.';
   555                     $obj->setCallback($result->getLabel(), $result->getName(), $api_callback_func, $result->getRank(), $result->getTotal());
   556                 }
   557                 else
   558                 {
   559                     if ($_CONF['search_use_fulltext'] == true && $result->getFTSQL() != '') {
   560                         $sql = $result->getFTSQL();
   561                     } else {
   562                         $sql = $result->getSQL();
   563                     }
   564 
   565                     $sql = $this->_convertsql($sql);
   566                     $debug_info .= ' with SQL = '.print_r($sql,1);
   567                     $obj->setQuery($result->getLabel(), $result->getName(), $sql, $result->getRank());
   568                 }
   569 
   570                 $this->_url_rewrite[ $result->getName() ] = $result->UrlRewriteEnable();
   571                 $this->_append_query[ $result->getName() ] = $result->AppendQueryEnable();
   572 
   573                 if ($this->_verbose) {
   574                     $new_api++;
   575                     COM_errorLog($debug_info);
   576                 }
   577             }
   578             else if (is_a($result, 'Plugin') && $result->num_searchresults != 0)
   579             {
   580                 // Some backwards compatibility
   581                 if ($this->_verbose) {
   582                     $old_api++;
   583                     $debug_info = $result->plugin_name.' using APIv1 with backwards compatibility.';
   584                     $debug_info .= ' Count: ' . $result->num_searchresults;
   585                     $debug_info .= ' Headings: ' . implode(',', $result->searchheading);
   586                     COM_errorLog($debug_info);
   587                 }
   588 
   589                 // Find the column heading names that closely match what we are looking for
   590                 // There may be issues here on different languages, but this _should_ capture most of the data
   591                 $col_title = $this->_findColumn($result->searchheading, array($LANG09[16],$LANG31[4],'Question', 'Site Page'));//Title,Subject
   592                 $col_desc = $this->_findColumn($result->searchheading, array($LANG09[63],'Answer'));
   593                 $col_date = $this->_findColumn($result->searchheading, array($LANG09[17]));//'Date','Date Added','Last Updated','Date & Time'
   594                 $col_user = $this->_findColumn($result->searchheading, array($LANG09[18],'Submited by'));
   595                 $col_hits = $this->_findColumn($result->searchheading, array($LANG09[50],$LANG09[23],'Downloads','Clicks'));//'Hits','Views'
   596 
   597                 $label = str_replace($LANG09[59], '', $result->searchlabel);
   598                 $num_results += $result->num_itemssearched;
   599 
   600                 // Extract the results
   601                 for ($i = 0; $i < 5; $i++)
   602                 {
   603                     // If the plugin does not repect the $perpage perameter force it here.
   604                     $j = ($i + ($page * 5)) - 5;
   605                     if ($j >= count($result->searchresults))
   606                         break;
   607 
   608                     $old_row = $result->searchresults[$j];
   609                     if ($col_date != -1)
   610                     {
   611                         // Convert the date back to a timestamp
   612                         $date = $old_row[ $col_date ];
   613                         $date = substr($date, 0, strpos($date, '@'));
   614                         $date = ($date == '' ? $old_row[$col_date] : strtotime($date));
   615                     }
   616 
   617                     $api_results = array(
   618                                 LF_SOURCE_NAME =>   $result->plugin_name,
   619                                 LF_SOURCE_TITLE =>  $label,
   620                                 'title' =>        $col_title == -1 ? '<i>' . $LANG09[70] . '</i>' : $old_row[$col_title],
   621                                 'description' =>  $col_desc == -1 ? '<i>' . $LANG09[70] . '</i>' : $old_row[$col_desc],
   622                                 'date' =>         $col_date == -1 ? 'LF_NULL' : $date,
   623                                 'uid' =>          $col_user == -1 ? 'LF_NULL' : $old_row[$col_user],
   624                                 'hits' =>         $col_hits == -1 ? 'LF_NULL' : str_replace(',', '', $old_row[$col_hits])
   625                             );
   626                     preg_match('/href="([^"]+)"/i', $api_results['title'], $links);
   627                     $api_results['url'] = empty($links) ? '#' : $links[1];
   628 
   629                     $obj->addResult($api_results);
   630                 }
   631             }
   632         }
   633 
   634         // Find out how many plugins are on the old/new system
   635         if ($this->_verbose) {
   636             COM_errorLog('Search Plugins using APIv1: '.$old_api.' APIv2: '.$new_api);
   637         }
   638 
   639         // Execute the queries
   640         $results = $obj->ExecuteQueries();
   641 
   642         // Searches are done, stop timer
   643         $searchtime = $searchtimer->stopTimer();
   644 
   645         $escquery = htmlspecialchars($this->_query);
   646         $escquery = str_replace(array('{', '}'), array('&#123;', '&#125;'),
   647                                 $escquery);
   648         if ($this->_keyType == 'any')
   649         {
   650             $searchQuery = str_replace(' ', "</b>' " . $LANG09[57] . " '<b>", $escquery);
   651             $searchQuery = "'<b>$searchQuery</b>'";
   652         }
   653         else if ($this->_keyType == 'all')
   654         {
   655             $searchQuery = str_replace(' ', "</b>' " . $LANG09[56] . " '<b>", $escquery);
   656             $searchQuery = "'<b>$searchQuery</b>'";
   657         }
   658         else
   659         {
   660             $searchQuery = $LANG09[55] . " '<b>$escquery</b>'";
   661         }
   662 
   663         // Clean the query string so that sprintf works as expected
   664         $searchQuery = str_replace('%', '%%', $searchQuery);
   665 
   666         $retval = "{$LANG09[25]} $searchQuery. ";
   667         if (count($results) == 0)
   668         {
   669             $retval .= sprintf($LANG09[24], 0);
   670             $retval = '<p>' . $retval . '</p>' . LB;
   671             $retval .= '<p>' . $LANG09[13] . '</p>' . LB;
   672             $retval .= $this->showForm();
   673         }
   674         else
   675         {
   676             $retval .= $LANG09[64] . " ($searchtime {$LANG09[27]}). ";
   677             $retval .= str_replace('%', '%%', COM_createLink($LANG09[61], $url.'refine'));
   678             $retval = '<p>' . $retval . '</p>' . LB;
   679             $retval = $obj->getFormattedOutput($results, $LANG09[11], $retval, '',
   680                 $_CONF['search_show_sort'], $_CONF['search_show_limit']);
   681         }
   682 
   683         return $retval;
   684     }
   685 
   686     /**
   687     * Callback function for the ListFactory class
   688     *
   689     * This function gets called by the ListFactory class and formats
   690     * each row accordingly for example pulling usernames from the
   691     * users table and displaying a link to their profile.
   692     *
   693     * @access public
   694     * @param array $row An array of plain data to format
   695     * @return array A reformatted version of the input array
   696     *
   697     */
   698     function searchFormatCallback( $preSort, $row )
   699     {
   700         global $_CONF, $LANG09;
   701 
   702         if ($preSort)
   703         {
   704             if (is_array($row[LF_SOURCE_TITLE])) {
   705                 $row[LF_SOURCE_TITLE] = implode($_CONF['search_separator'], $row[LF_SOURCE_TITLE]);
   706             }
   707 
   708             if (is_numeric($row['uid']))
   709             {
   710                 if (empty($this->_names[ $row['uid'] ]))
   711                 {
   712                     $this->_names[ $row['uid'] ] = htmlspecialchars(COM_getDisplayName( $row['uid'] ));
   713                     if ($row['uid'] != 1)
   714                     {
   715                         $this->_names[$row['uid']] = COM_createLink($this->_names[ $row['uid'] ],
   716                                     $_CONF['site_url'] . '/users.php?mode=profile&amp;uid=' . $row['uid']);
   717                     }
   718                 }
   719                 $row['uid'] = $this->_names[ $row['uid'] ];
   720             }
   721         }
   722         else
   723         {
   724             $row[LF_SOURCE_TITLE] = COM_createLink($row[LF_SOURCE_TITLE],
   725                 $this->_searchURL.'&amp;type='.$row[LF_SOURCE_NAME].'&amp;mode=search');
   726 
   727             if ($row['url'] != '#')
   728             {
   729                 $row['url'] = ($row['url'][0] == '/' ? $_CONF['site_url'] : '') . $row['url'];
   730                 if (isset($this->_url_rewrite[$row[LF_SOURCE_NAME]]) &&
   731                         $this->_url_rewrite[$row[LF_SOURCE_NAME]]) {
   732                     $row['url'] = COM_buildUrl($row['url']);
   733                 }
   734                 if (isset($this->_append_query[$row[LF_SOURCE_NAME]]) &&
   735                         $this->_append_query[$row[LF_SOURCE_NAME]]) {
   736                     $row['url'] .= (strpos($row['url'],'?') ? '&amp;' : '?') . 'query=' . urlencode($this->_query);
   737                 }
   738             }
   739 
   740             $row['title'] = $this->_shortenText($this->_query, $row['title'], 8);
   741             $row['title'] = stripslashes(str_replace('$', '&#36;', $row['title']));
   742             $row['title'] = COM_createLink($row['title'], $row['url']);
   743 
   744             if ($row['description'] == 'LF_NULL') {
   745                 $row['description'] = '<i>' . $LANG09[70] . '</i>';
   746             } elseif ($row['description'] != '<i>' . $LANG09[70] . '</i>') {
   747                 $row['description'] = stripslashes($this->_shortenText($this->_query, PLG_replaceTags($row['description']), $this->_wordlength));
   748             }
   749 
   750             if ($row['date'] != 'LF_NULL') {
   751                 $dt = COM_getUserDateTimeFormat(intval($row['date']));
   752                 $row['date'] = $dt[0];
   753             }
   754 
   755             if ($row['hits'] != 'LF_NULL') {
   756                 $row['hits'] = COM_NumberFormat($row['hits']) . ' '; // simple solution to a silly problem!
   757             }
   758         }
   759 
   760         return $row;
   761     }
   762 
   763     /**
   764     * Shortens a long text string to only a few words
   765     *
   766     * Returns a shorter version of the in putted text centred
   767     * around the keyword. The keyword is highlighted in bold.
   768     * Adds '...' to the beginning or the end of the shortened
   769     * version depending where the text was cut. Works on a
   770     * word basis, so long words wont get cut.
   771     *
   772     * @access private
   773     * @param string $keyword The word to centre around
   774     * @param string $text The complete text string
   775     * @param int $num_words The number of words to display, best to use an odd number
   776     * @return string A short version of the text
   777     *
   778     */
   779     function _shortenText($keyword, $text, $num_words = 7)
   780     {
   781         $text = COM_getTextContent($text);
   782         $words = explode(' ', $text);
   783         $word_count = count($words);
   784         if ($word_count <= $num_words) {
   785             return COM_highlightQuery($text, $keyword, 'b');
   786         }
   787 
   788         $rt = '';
   789         $pos = $this->_stripos($text, $keyword);
   790         if ($pos !== false)
   791         {
   792             $pos_space = strpos($text, ' ', $pos);
   793             if (empty($pos_space))
   794             {
   795                 // Keyword at the end of text
   796                 $key = $word_count - 1;
   797                 $start = 0 - $num_words;
   798                 $end = 0;
   799                 $rt = '<b>...</b> ';
   800             }
   801             else
   802             {
   803                 $str = substr($text, $pos, $pos_space - $pos);
   804                 $m = (int) (($num_words - 1) / 2);
   805                 $key = $this->_arraySearch($keyword, $words);
   806                 if ($key === false) {
   807                     // Keyword(s) not found - show start of text
   808                     $key = 0;
   809                     $start = 0;
   810                     $end = $num_words - 1;
   811                 } elseif ($key <= $m) {
   812                     // Keyword at the start of text
   813                     $start = 0 - $key;
   814                     $end = $num_words - 1;
   815                     $end = ($key + $m <= $word_count - 1)
   816                          ? $key : $word_count - $m - 1;
   817                     $abs_length = abs($start) + abs($end) + 1;
   818                     if ($abs_length < $num_words) {
   819                         $end += ($num_words - $abs_length);
   820                     }
   821                 } else {
   822                     // Keyword in the middle of text
   823                     $start = 0 - $m;
   824                     $end = ($key + $m <= $word_count - 1)
   825                          ? $m : $word_count - $key - 1;
   826                     $abs_length = abs($start) + abs($end) + 1;
   827                     if ($abs_length < $num_words) {
   828                         $start -= ($num_words - $abs_length);
   829                     }
   830                     $rt = '<b>...</b> ';
   831                 }
   832             }
   833         }
   834         else
   835         {
   836             $key = 0;
   837             $start = 0;
   838             $end = $num_words - 1;
   839         }
   840 
   841         for ($i = $start; $i <= $end; $i++) {
   842             $rt .= $words[$key + $i] . ' ';
   843         }
   844         if ($key + $i != $word_count) {
   845             $rt .= ' <b>...</b>';
   846         }
   847 
   848         return COM_highlightQuery($rt, $keyword, 'b');
   849     }
   850 
   851     /**
   852     * Search array of words for keyword(s)
   853     *
   854     * @param   string  $needle    keyword(s), separated by spaces
   855     * @param   array   $haystack  array of words to search through
   856     * @return  mixed              index in $haystack or false when not found
   857     * @access  private
   858     *
   859     */
   860     function _arraySearch($needle, $haystack)
   861     {
   862         $keywords = explode(' ', $needle);
   863         $num_keywords = count($keywords);
   864 
   865         foreach ($haystack as $key => $value) {
   866             if ($this->_stripos($value, $keywords[0]) !== false) {
   867                 if ($num_keywords == 1) {
   868                     return $key;
   869                 } else {
   870                     $matched_all = true;
   871                     for ($i = 1; $i < $num_keywords; $i++) {
   872                         if ($this->_stripos($haystack[$key + $i], $keywords[$i]) === false) {
   873                             $matched_all = false;
   874                             break;
   875                         }
   876                     }
   877                     if ($matched_all) {
   878                         return $key;
   879                     }
   880                 }
   881             }
   882         }
   883 
   884         return false;
   885     }
   886 
   887     /**
   888     * Finds the similarities between heading names
   889     *
   890     * Returns the index of a heading that matches a
   891     * number of similar heading names. Used for backwards
   892     * compatibility in the doSearch() function.
   893     *
   894     * @access private
   895     * @param array $headings All the headings
   896     * @param array $find An array of alternative headings to find
   897     * @return int The index of the alternative heading
   898     *
   899     */
   900     function _findColumn( $headings, $find )
   901     {
   902         // We can't use normal for loops here as some of the
   903         // heading indexes start from 1, so foreach works better
   904         foreach ($find as $fh)
   905         {
   906             $j = 0;
   907             foreach ($headings as $h)
   908             {
   909                 if (preg_match("/$fh/i", $h) > 0) {
   910                     return $j;
   911                 }
   912                 $j++;
   913             }
   914         }
   915         return -1;
   916     }
   917 
   918     /**
   919     * Converts the MySQL CONCAT function to the MSSQL equivalent
   920     *
   921     * @access private
   922     * @param string $sql The SQL to convert
   923     * @return string MSSQL friendly SQL
   924     *
   925     */
   926     function _convertsql( $sql )
   927     {
   928         global $_DB_dbms;
   929         if ($_DB_dbms == 'mssql')
   930         {
   931             if (is_string( $sql ))
   932             {
   933                 $sql = preg_replace("/CONCAT\(([^\)]+)\)/ie", "preg_replace('/,?(\'[^\']+\'|[^,]+),/i', '\\\\1 + ', '\\1')", $sql);
   934             }
   935             else if (is_array( $sql ))
   936             {
   937                 $sql['mssql'] = preg_replace("/CONCAT\(([^\)]+)\)/ie", "preg_replace('/,?(\'[^\']+\'|[^,]+),/i', '\\\\1 + ', '\\1')", $sql['mssql']);
   938             }
   939         }
   940         return $sql;
   941     }
   942 
   943     /**
   944     * Helper function: Simulate stripos on PHP 4
   945     *
   946     * @param   string  $haystack  string to search in
   947     * @param   string  $needle    string to search for
   948     * @return  mixed              first pos of $needle in $haystack, or false
   949     *
   950     */
   951     function _stripos($haystack, $needle)
   952     {
   953         if (function_exists('stripos')) {
   954             return stripos($haystack, $needle);
   955         } elseif (empty($needle)) {
   956             return false;
   957         } else {
   958             return strpos(strtolower($haystack), strtolower($needle));
   959         }
   960     }
   961 }
   962 
   963 ?>