This commit was manufactured by cvs2svn to create tag
[Packages/TYPO3.CMS.git] / typo3 / sysext / cms / tslib / class.tslib_search.php
index 19ca4b7..80ac6e7 100755 (executable)
@@ -1,22 +1,22 @@
 <?php
 /***************************************************************
 *  Copyright notice
-*  
-*  (c) 1999-2003 Kasper Skårhøj (kasper@typo3.com)
+*
+*  (c) 1999-2004 Kasper Skaarhoj (kasper@typo3.com)
 *  All rights reserved
 *
-*  This script is part of the TYPO3 project. The TYPO3 project is 
+*  This script is part of the TYPO3 project. The TYPO3 project is
 *  free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
-* 
+*
 *  The GNU General Public License can be found at
 *  http://www.gnu.org/copyleft/gpl.html.
-*  A copy is found in the textfile GPL.txt and important notices to the license 
+*  A copy is found in the textfile GPL.txt and important notices to the license
 *  from the author is found in LICENSE.txt distributed with these scripts.
 *
-* 
+*
 *  This script is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *
 *  This copyright notice MUST APPEAR in all copies of the script!
 ***************************************************************/
-/** 
+/**
  * Searching in database tables, typ. "pages" and "tt_content"
  * Used to generate search queries for TypoScript.
  * The class is included from "class.tslib_pagegen.php" based on whether there has been detected content in the GPvar "sword"
  *
- * Revised for TYPO3 3.6 June/2003 by Kasper Skårhøj
+ * $Id$
+ * Revised for TYPO3 3.6 June/2003 by Kasper Skaarhoj
  *
- * @author     Kasper Skårhøj <kasper@typo3.com>
+ * @author     Kasper Skaarhoj <kasper@typo3.com>
  * @author     Rene Fritz      <r.fritz@colorcube.de>
- * @package TYPO3
- * @subpackage tslib
  */
 /**
  * [CLASS/FUNCTION INDEX of SCRIPT]
  *
  *
  *
- *   87: class tslib_search 
- *  137:     function register_tables_and_columns($requestedCols,$allowedCols) 
- *  179:     function explodeCols($in) 
- *  204:     function register_and_explode_search_string ($sword)      
- *  237:     function split($origSword, $specchars='+-')       
- *  268:     function quotemeta($str)  
- *  282:     function build_search_query ($endClause) 
- *  365:     function build_search_query_for_searchwords ()    
- *  414:     function get_operator ($operator) 
- *  437:     function count_query () 
- *  451:     function execute_query() 
- *  467:     function get_searchwords()        
- *  483:     function get_searchwordsArray()   
+ *   88: class tslib_search
+ *  130:     function register_tables_and_columns($requestedCols,$allowedCols)
+ *  171:     function explodeCols($in)
+ *  196:     function register_and_explode_search_string($sword)
+ *  229:     function split($origSword, $specchars='+-', $delchars='+-.,')
+ *  272:     function quotemeta($str)
+ *  288:     function build_search_query($endClause)
+ *  374:     function build_search_query_for_searchwords()
+ *  416:     function get_operator($operator)
+ *  439:     function count_query()
+ *  452:     function execute_query()
+ *  465:     function get_searchwords()
+ *  480:     function get_searchwordsArray()
  *
  * TOTAL FUNCTIONS: 12
  * (This index is automatically created/updated by the extension "extdeveval")
  */
 
 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 /**
  * Search class used for the content object SEARCHRESULT
- * 
- * @author     Kasper Skårhøj <kasper@typo3.com>
+ *
+ * @author     Kasper Skaarhoj <kasper@typo3.com>
+ * @package TYPO3
+ * @subpackage tslib
  * @see        tslib_cObj::SEARCHRESULT()
  */
 class tslib_search {
        var $tables = Array ();
 
-       var $standalone = '';   // true / false - if the searchresult MAY NOT be a part-string (doesn't work yet. Does not find words with parentheses or periods before/after....beginning/end of a line is also a problem!)
-       var $mixedcase = 'yes';         // true / false - matches both upper and lower case (doesn't work if you disable. Matches all cases currently)
-
-       var $order_by = '';             // ORDER BY part of the query. (field-reference, eg. 'pages.uid'
-       var $group_by = 'PRIMARY_KEY';          // Alternatively 'PRIMARY_KEY'; sorting by primary key
-
-       var $default_operator = 'AND';  // Standard SQL-operator between words
+       var $group_by = 'PRIMARY_KEY';                                                  // Alternatively 'PRIMARY_KEY'; sorting by primary key
+       var $default_operator = 'AND';                                                  // Standard SQL-operator between words
        var $operator_translate_table_caseinsensitive = '1';
-       var $operator_translate_table = Array (         // case-sensitiv. Defineres the words, which will be operators between words
+       var $operator_translate_table = Array (                                 // case-sensitiv. Defineres the words, which will be operators between words
                Array ('+' , 'AND'),
                Array ('-' , 'AND NOT'),
                        // english
@@ -110,10 +106,7 @@ class tslib_search {
 
        // Internal
        var $sword_array;               // Contains the search-words and operators
-       var $query_begin = '';          // Beginning of query
-       var $query_end = '';                    // Ending of query
-
-       var $query;                     // Contains the final query after processing.
+       var $queryParts;                // Contains the query parts after processing.
 
        var $other_where_clauses;       // Addition to the whereclause. This could be used to limit search to a certain page or alike in the system.
        var $fTable;            // This is set with the foreign table that 'pages' are connected to.
@@ -121,33 +114,32 @@ class tslib_search {
        var $res_offset = 0;    // How many rows to offset from the beginning
        var $res_shows = 20;    // How many results to show (0 = no limit)
        var $res_count;                 // Intern: How many results, there was last time (with the exact same searchstring.
-       
+
        var $pageIdList='';             // List of pageIds.
-       
+
        var $listOfSearchFields ='';
 
        /**
         * Creates the $this->tables-array.
         * The 'pages'-table is ALWAYS included as the search is page-based. Apart from this there may be one and only one table, joined with the pages-table. This table is the first table mentioned in the requested-list. If any more tables are set here, they are ignored.
-        * 
+        *
         * @param       string          is a list (-) of columns that we want to search. This could be input from the search-form (see TypoScript documentation)
         * @param       string          $allowedCols: is the list of columns, that MAY be searched. All allowed cols are set as result-fields. All requested cols MUST be in the allowed-fields list.
-        * @return      void            
+        * @return      void
         */
        function register_tables_and_columns($requestedCols,$allowedCols)       {
                $rCols=$this->explodeCols($requestedCols);
                $aCols=$this->explodeCols($allowedCols);
-               
+
                foreach ($rCols as $k => $v)    {
                        $rCols[$k]=trim($v);
                        if (in_array($rCols[$k], $aCols))       {
                                $parts = explode('.',$rCols[$k]);
                                $this->tables[$parts[0]]['searchfields'][] = $parts[1];
-//                             $this->tables[$parts[0]]['resultfields']['uid']='uid';  // Cannot set this, because then the link to the searched page will not be correct! Must set otherwise
                        }
                }
                $this->tables['pages']['primary_key'] = 'uid';
-               $this->tables['pages']['resultfields'][]='uid';
+               $this->tables['pages']['resultfields'][] = 'uid';
                unset($this->tables['pages']['fkey']);
 
                foreach ($aCols as $k => $v)    {
@@ -161,7 +153,7 @@ class tslib_search {
                foreach ($this->tables as $t => $v)     {
                        if ($t!='pages')        {
                                if (!$this->fTable)     {
-                                       $this->fTable=$t;
+                                       $this->fTable = $t;
                                } else {
                                        unset($this->tables[$t]);
                                }
@@ -171,7 +163,7 @@ class tslib_search {
 
        /**
         * Function that can convert the syntax for entering which tables/fields the search should be conducted in.
-        * 
+        *
         * @param       string          This is the code-line defining the tables/fields to search. Syntax: '[table1].[field1]-[field2]-[field3] : [table2].[field1]-[field2]'
         * @return      array           An array where the values is "[table].[field]" strings to search
         * @see register_tables_and_columns()
@@ -197,11 +189,11 @@ class tslib_search {
         * Takes a search-string (WITHOUT SLASHES or else it'll be a little sppooky , NOW REMEMBER to unslash!!)
         * Sets up $this->sword_array op with operators.
         * This function uses $this->operator_translate_table as well as $this->default_operator
-        * 
+        *
         * @param       string          The input search-word string.
-        * @return      void            
+        * @return      void
         */
-       function register_and_explode_search_string ($sword)    {
+       function register_and_explode_search_string($sword)     {
                $sword = trim($sword);
                if ($sword)     {
                        $components = $this->split($sword);
@@ -227,191 +219,201 @@ class tslib_search {
 
        /**
         * Used to split a search-word line up into elements to search for. This function will detect boolean words like AND and OR, + and -, and even find sentences encapsulated in ""
-        * This function also has the charm of still containing some of the original comments - in danish!
         * This function could be re-written to be more clean and effective - yet it's not that important.
-        * 
+        *
         * @param       string          The raw sword string from outside
         * @param       string          Special chars which are used as operators (+- is default)
+        * @param       string          Special chars which are deleted if the append the searchword (+-., is default)
         * @return      mixed           Returns an ARRAY if there were search words, othervise the return value may be unset.
         */
-       function split($origSword, $specchars='+-')     {
+       function split($origSword, $specchars='+-', $delchars='+-.,')   {
                $sword = $origSword;
                $specs = '['.$this->quotemeta($specchars).']';
+               $delchars = '['.$this->quotemeta($delchars).']';
 
                        // As long as $sword is true (that means $sword MUST be reduced little by little until its empty inside the loop!)
                while ($sword)  {
                        if (ereg('^"',$sword))  {               // There was a double-quote and we will then look for the ending quote.
-                               $sword = ereg_replace('^"','',$sword);          // Fjerner først gåseøje.
-                               ereg('^[^"]*',$sword,$reg);  // Tager alt indtil næste gåseøje
-                               $value[] = $reg[0];  // reg[0] er lig med værdien!! Skal ikke trimmes
+                               $sword = ereg_replace('^"','',$sword);          // Removes first double-quote
+                               ereg('^[^"]*',$sword,$reg);  // Removes everything till next double-quote
+                               $value[] = $reg[0];  // reg[0] is the value, should not be trimmed
                                $sword = ereg_replace('^'.$this->quotemeta($reg[0]),'',$sword);
-                               $sword = trim(ereg_replace('^"','',$sword));            // Fjerner det sidste gåseøje.
+                               $sword = trim(ereg_replace('^"','',$sword));            // Removes last double-quote
                        } elseif (ereg('^'.$specs,$sword,$reg)) {
                                $value[] = $reg[0];
-                               $sword = trim(ereg_replace('^'.$specs,'',$sword));              // Fjerner = tegn.
+                               $sword = trim(ereg_replace('^'.$specs,'',$sword));              // Removes = sign
+                       } elseif (ereg('[\+\-]',$sword)) {      // Check if $sword contains + or -
+                                       // + and - shall only be interpreted as $specchars when there's whitespace before it
+                                       // otherwise it's included in the searchword (e.g. "know-how")
+                               $a_sword = explode(' ',$sword); // explode $sword to single words
+                               $word = array_shift($a_sword);  // get first word
+                               $word = ereg_replace($delchars.'$','',$word);           // Delete $delchars at end of string
+                               $value[] = $word;       // add searchword to values
+                               $sword = implode(' ',$a_sword); // re-build $sword
                        } else {
-                               // Der er ikke gåseøjne om værdien. Der ledes efter næste ' ' eller '>'
+                                       // There are no double-quotes around the value. Looking for next (space) or special char.
                                ereg('^[^ '.$this->quotemeta($specchars).']*',$sword,$reg);
-                               $value[] = trim($reg[0]);
+                               $word = ereg_replace($delchars.'$','',trim($reg[0]));           // Delete $delchars at end of string
+                               $value[] = $word;
                                $sword = trim(ereg_replace('^'.$this->quotemeta($reg[0]),'',$sword));
                        }
                }
+
                return $value;
        }
 
        /**
-        * Local version of quotemeta. This is the same as the PHP function but the vertical line, |, is also escaped with a slash.
-        * 
+        * Local version of quotemeta. This is the same as the PHP function
+        * but the vertical line, |, and minus, -, is also escaped with a slash.
+        *
         * @param       string          String to pass through quotemeta()
         * @return      string          Return value
         */
        function quotemeta($str)        {
-               return str_replace('|','\|',quotemeta($str));
+               $str = str_replace('|','\|',quotemeta($str));
+               #$str = str_replace('-','\-',$str);             // Breaks "-" which should NOT have a slash before it inside of [ ] in a regex.
+               return $str;
        }
 
        /**
         * This creates the search-query.
         * In TypoScript this is used for searching only records not hidden, start/endtimed and fe_grouped! (enable-fields, see tt_content)
-        * Sets $this->query
-        * 
+        * Sets $this->queryParts
+        *
         * @param       string          $endClause is some extra conditions that the search must match.
         * @return      boolean         Returns true no matter what - sweet isn't it!
         * @access private
         * @see tslib_cObj::SEARCHRESULT()
         */
-       function build_search_query ($endClause) {
+       function build_search_query($endClause) {
+
                if (is_array($this->tables))    {
                        $tables = $this->tables;
                        $primary_table = '';
-                       $query = 'SELECT';
+
                                // Primary key table is found.
-                       reset($tables);
-                       while (list($key,$val) = each($tables)) {
+                       foreach($tables as $key => $val)        {
                                if ($tables[$key]['primary_key'])       {$primary_table = $key;}
                        }
+
                        if ($primary_table) {
-                               reset($tables);
-                               while (list($key,$val) = each($tables)) {
+
+                                       // Initialize query parts:
+                               $this->queryParts = array(
+                                       'SELECT' => '',
+                                       'FROM' => '',
+                                       'WHERE' => '',
+                                       'GROUPBY' => '',
+                                       'ORDERBY' => '',
+                                       'LIMIT' => '',
+                               );
+
+                                       // Find tables / field names to select:
+                               $fieldArray = array();
+                               $tableArray = array();
+                               foreach($tables as $key => $val)        {
+                                       $tableArray[] = $key;
                                        $resultfields = $tables[$key]['resultfields'];
                                        if (is_array($resultfields))    {
-                                               reset($resultfields);
-                                               while (list($key2,$val2) = each($resultfields)) {
-                                                       $query.= ' '.$key.'.'.$val2.',';
+                                               foreach($resultfields as $key2 => $val2)        {
+                                                       $fieldArray[] = $key.'.'.$val2;
                                                }
                                        }
                                }
-       
-                               $query = t3lib_div::rm_endcomma($query);
-                               $query.= ' FROM';
-       
-                               reset($tables);
-                               while (list($key,$val) = each($tables)) {
-                                       $query.= ' '.$key.',';
-                               }
-       
-                               $query = t3lib_div::rm_endcomma($query);
-                               $query.= ' WHERE';
-                               
-                               reset($tables);
+                               $this->queryParts['SELECT'] = implode(',',$fieldArray);
+                               $this->queryParts['FROM'] = implode(',',$tableArray);
+
+                                       // Set join WHERE parts:
+                               $whereArray = array();
+
                                $primary_table_and_key = $primary_table.'.'.$tables[$primary_table]['primary_key'];
-                               $primKeys=Array();
-                               while (list($key,$val) = each($tables)) {
+                               $primKeys = Array();
+                               foreach($tables as $key => $val)        {
                                        $fkey = $tables[$key]['fkey'];
                                        if ($fkey)      {
-                                                $primKeys[]=$key.'.'.$fkey.'='.$primary_table_and_key;
+                                                $primKeys[] = $key.'.'.$fkey.'='.$primary_table_and_key;
                                        }
                                }
                                if (count($primKeys))   {
-                                       $query.='('.implode($primKeys,' OR ').')';
+                                       $whereArray[] = '('.implode(' OR ',$primKeys).')';
                                }
-                               if (!ereg('WHERE$',trim($query)))       {
-                                       $query.=' AND';
+
+                                       // Additional where clause:
+                               if (trim($endClause))   {
+                                       $whereArray[] = trim($endClause);
                                }
 
-                               $tempClause = trim($endClause);
-                               if ($tempClause)        {
-                                       $query.= ' ('.$tempClause.') AND';
-                               }               
+                                       // Add search word where clause:
+                               $query_part = $this->build_search_query_for_searchwords();
+                               if (!$query_part)       {
+                                       $query_part = '(0!=0)';
+                               }
+                               $whereArray[] = '('.$query_part.')';
 
-                               $query.= ' (';
-                               $this->query_begin = $query;
-       
+                                       // Implode where clauses:
+                               $this->queryParts['WHERE'] = implode(' AND ',$whereArray);
+
+                                       // Group by settings:
                                if ($this->group_by)    {
                                        if ($this->group_by == 'PRIMARY_KEY')   {
-                                               $this->query_end = ') GROUP BY '.$primary_table_and_key;
+                                               $this->queryParts['GROUPBY'] = $primary_table_and_key;
                                        } else {
-                                               $this->query_end = ') GROUP BY '.$this->group_by;
+                                               $this->queryParts['GROUPBY'] = $this->group_by;
                                        }
-                               } else {
-                                       $this->query_end = ')';
                                }
                        }
                }
-
-               $query_part = $this->build_search_query_for_searchwords();
-               if (!$query_part)       {
-                       $query_part='(0!=0)';
-               }
-               $this->query = $this->query_begin.$query_part.$this->query_end;
-               return true;
        }
 
        /**
         * Creates the part of the SQL-sentence, that searches for the search-words ($this->sword_array)
-        * 
+        *
         * @return      string          Part of where class limiting result to the those having the search word.
         * @access private
         */
-       function build_search_query_for_searchwords ()  {
-               $tables = $this->tables;
-               $sword_array = $this->sword_array;
-               $query_part = '';
-               $sp='';
-               if ($this->standalone)  {$sp=' ';}              // Der indsættes et space foran og efter ordet, hvis det SKAL stå alene. Dette er dog ikke korrekt implementeret, fordi det ikke finder ord i starten, slutningen og i parenteser osv. Egentlig skal der være check på noget om der er alfanumeriske værdier før eller efter!!
-               if (is_array($sword_array))     {
-                       reset($sword_array);
-                       while (list($key,$val) = each($sword_array))    {
-                               $s_sword = $sword_array[$key]['sword'];
+       function build_search_query_for_searchwords()   {
+
+               if (is_array($this->sword_array))       {
+                       $main_query_part = array();
+
+                       foreach($this->sword_array as $key => $val)     {
+                               $s_sword = $this->sword_array[$key]['sword'];
+
                                        // Get subQueryPart
-                               $sub_query_part='';
-                               reset ($tables);
+                               $sub_query_part = array();
+
                                $this->listOfSearchFields='';
-                               while (list($key3,$val3) = each($tables))       {
-                                       $searchfields = $tables[$key3]['searchfields'];
+                               foreach($this->tables as $key3 => $val3)        {
+                                       $searchfields = $this->tables[$key3]['searchfields'];
                                        if (is_array($searchfields))    {
-                                               reset ($searchfields);
-                                               while (list($key2,$val2) = each($searchfields)) {
-                                                       $this->listOfSearchFields.=$key3.'.'.$val2.',';
-                                                       $sub_query_part.= ' '.$key3.'.'.$val2.' LIKE "%'.$sp.addslashes($s_sword).$sp.'%" OR';
+                                               foreach($searchfields as $key2 => $val2)        {
+                                                       $this->listOfSearchFields.= $key3.'.'.$val2.',';
+                                                       $sub_query_part[] = $key3.'.'.$val2.' LIKE "%'.$GLOBALS['TYPO3_DB']->quoteStr($s_sword, $key3).'%"';
                                                }
                                        }
                                }
-                               $sub_query_part = trim(ereg_replace('OR$','',$sub_query_part));
 
-                               if ($sub_query_part)    {
-                                       if ($query_part != '')  {
-                                               $query_part.= ' '.$sword_array[$key]['oper'].' ('.$sub_query_part.')';
-                                       } else {
-                                               $query_part.= '('.$sub_query_part.')';
-                                       }
+                               if (count($sub_query_part))     {
+                                       $main_query_part[] = $this->sword_array[$key]['oper'];
+                                       $main_query_part[] = '('.implode(' OR ',$sub_query_part).')';
                                }
                        }
-                       $query_part = trim($query_part);
-                       if (!$query_part || trim($query_part)=='()')    {
-                               $query_part = '';
+
+                       if (count($main_query_part))    {
+                               unset($main_query_part[0]);     // Remove first part anyways.
+                               return implode(' ',$main_query_part);
                        }
-                       return $query_part;
                }
        }
 
        /**
         * This returns an SQL search-operator (eg. AND, OR, NOT) translated from the current localized set of operators (eg. in danish OG, ELLER, IKKE).
-        * 
+        *
         * @param       string          The possible operator to find in the internal operator array.
         * @return      string          If found, the SQL operator for the localized input operator.
         * @access private
         */
-       function get_operator ($operator)       {
+       function get_operator($operator)        {
                $operator = trim($operator);
                $op_array = $this->operator_translate_table;
                reset ($op_array);
@@ -431,44 +433,39 @@ class tslib_search {
 
        /**
         * Counts the results and sets the result in $this->res_count
-        * 
+        *
         * @return      boolean         True, if $this->query was found
         */
-       function count_query () {
-               if ($this->query)       {
-                       $res = mysql(TYPO3_db, $this->query);
-                       echo mysql_error();
-                   $this->res_count = mysql_num_rows($res);
-                       return true;
+       function count_query() {
+               if (is_array($this->queryParts))        {
+                       $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery($this->queryParts['SELECT'], $this->queryParts['FROM'], $this->queryParts['WHERE'], $this->queryParts['GROUPBY']);
+                   $this->res_count = $GLOBALS['TYPO3_DB']->sql_num_rows($res);
+                       return TRUE;
                }
        }
 
        /**
         * Executes the search, sets result pointer in $this->result
-        * 
+        *
         * @return      boolean         True, if $this->query was set and query performed
         */
        function execute_query() {
-               $query = $this->query;
-               if ($query)     {
-                       if ($this->order_by)    $query.= ' ORDER BY '.$this->order_by;
-               $this->result = mysql(TYPO3_db, $query);
-                       echo mysql_error();
-                       return true;
+               if (is_array($this->queryParts))        {
+               $this->result = $GLOBALS['TYPO3_DB']->exec_SELECT_queryArray($this->queryParts);
+                       return TRUE;
                }
        }
 
        /**
         * Returns URL-parameters with the current search words.
         * Used when linking to result pages so that search words can be highlighted.
-        * 
+        *
         * @return      string          URL-parameters with the searchwords
         */
        function get_searchwords()      {
                $SWORD_PARAMS='';
                if (is_array($this->sword_array))       {
-                       reset($this->sword_array);
-                       while (list($key,$val)=each($this->sword_array))        {
+                       foreach($this->sword_array as $key => $val)     {
                                $SWORD_PARAMS.='&sword_list[]='.rawurlencode($val['sword']);
                        }
                }
@@ -477,19 +474,18 @@ class tslib_search {
 
        /**
         * Returns an array with the search words in
-        * 
+        *
         * @return      array           IF the internal sword_array contained search words it will return these, otherwise "void"
         */
        function get_searchwordsArray() {
                if (is_array($this->sword_array))       {
-                       reset($this->sword_array);
-                       while (list($key,$val)=each($this->sword_array))        {
+                       foreach($this->sword_array as $key => $val)     {
                                $swords[]=$val['sword'];
                        }
                }
                return $swords;
        }
-}         
+}
 
 
 
@@ -498,4 +494,4 @@ if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['tslib/class
        include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['tslib/class.tslib_search.php']);
 }
 
-?>
\ No newline at end of file
+?>