Source for file query.php
Documentation is available at query.php
 * @package     Joomla.Administrator  
 * @copyright   Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.  
 * @license     GNU General Public License version 2 or later; see LICENSE  
 * Query class for the Finder indexer package.  
 * @package     Joomla.Administrator  
     * Flag to show whether the query can return results.  
     * The query input string.  
     * The language of the query.  
     * The query string matching mode.  
     * The tokens to ignore because no matches exist.  
     * The operators used in the query input string.  
     * The terms to highlight as matches.  
     * The number of matching terms for the query input.  
     * The taxonomy filters. This is a multi-dimensional array of taxonomy  
     * branches as the first level and then the taxonomy nodes as the values.  
     *     'Type' = array(10, 32, 29, 11, ...);  
     *     'Label' = array(20, 314, 349, 91, 82, ...);  
     * The start date filter modifier.  
     * The end date filter modifier.  
     * Method to instantiate the query object.  
     * @param   array  $options  An array of query options.  
     * @throws  Exception on database error.  
        $this->input = isset
($options['input']) ? 
$options['input'] : 
null;  
        // Get the empty query setting.  
        $this->empty = isset
($options['empty']) ? (bool) 
$options['empty'] : 
false;  
        // Get the input language.  
        // Get the matching mode.  
        // Initialize the temporary date storage.  
        // Populate the temporary date storage.  
        if (isset
($options['date1']) && 
!empty($options['date1']))  
            $this->dates->set('date1', $options['date1']);  
        if (isset
($options['date2']) && 
!empty($options['date1']))  
            $this->dates->set('date2', $options['date2']);  
        if (isset
($options['when1']) && 
!empty($options['date1']))  
            $this->dates->set('when1', $options['when1']);  
        if (isset
($options['when2']) && 
!empty($options['date1']))  
            $this->dates->set('when2', $options['when2']);  
        // Process the static taxonomy filters.  
        if (isset
($options['filter']) && 
!empty($options['filter']))  
        // Process the dynamic taxonomy filters.  
        if (isset
($options['filters']) && 
!empty($options['filters']))  
        $d1 = 
$this->dates->get('date1');  
        $d2 = 
$this->dates->get('date2');  
        $w1 = 
$this->dates->get('when1');  
        $w2 = 
$this->dates->get('when2');  
        // Process the date filters.  
        if (!empty($d1) || 
!empty($d2))  
        // Process the input string.  
        // Get the number of matching terms.  
        // Remove the temporary date storage.  
         * Lastly, determine whether this query can return a result set.  
        // Check if we have a query string.  
        if (!empty($this->input))  
        // Check if we can search without a query string.  
        elseif ($this->empty && 
(!empty($this->filter) || 
!empty($this->filters) || 
!empty($this->date1) || 
!empty($this->date2)))  
        // We do not have a valid search query.  
     * Method to convert the query object into a URI string.  
     * @param   string  $base  The base URI. [optional]  
     * @return  string  The complete query URI.  
    public function toURI($base = 
null)  
        // Set the base if not specified.  
            $base = 
'index.php?option=com_finder&view=search';  
        // Add the static taxonomy filter if present.  
            $uri->setVar('f', $this->filter);  
        // Get the filters in the request.  
        $t = 
$input->request->get('t', array(), 'array');  
        // Add the dynamic taxonomy filters if present.  
                foreach ($nodes as $node)  
                    $uri->setVar('t[]', $node);  
        // Add the input string if present.  
        if (!empty($this->input))  
            $uri->setVar('q', $this->input);  
        // Add the start date if present.  
        if (!empty($this->date1))  
            $uri->setVar('d1', $this->date1);  
        // Add the end date if present.  
        if (!empty($this->date2))  
            $uri->setVar('d2', $this->date2);  
        // Add the start date modifier if present.  
        if (!empty($this->when1))  
            $uri->setVar('w1', $this->when1);  
        // Add the end date modifier if present.  
        if (!empty($this->when2))  
            $uri->setVar('w2', $this->when2);  
        // Add a menu item id if one is not present.  
        if (!$uri->getVar('Itemid'))  
                'view' => 
$uri->getVar('view'),  
                'f' => 
$uri->getVar('f'),  
            // Add the menu item id if present.  
                $uri->setVar('Itemid', $item);  
        return $uri->toString(array('path', 'query'));  
     * Method to get a list of excluded search term ids.  
     * @return  array  An array of excluded term ids.  
        // Iterate through the excluded tokens and compile the matching terms.  
     * Method to get a list of included search term ids.  
     * @return  array  An array of included term ids.  
        // Iterate through the included tokens and compile the matching terms.  
            // Check if we have any terms.  
            if (empty($this->included[$i]->matches))  
            // Prepare the container for the term if necessary.  
                $results[$term] = 
array();  
            // Add the matches to the stack.  
        foreach ($results as $key => 
$value)  
     * Method to get a list of required search term ids.  
     * @return  array  An array of required term ids.  
        // Iterate through the included tokens and compile the matching terms.  
            // Check if the token is required.  
                // Prepare the container for the term if necessary.  
                    $results[$term] = 
array();  
                // Add the matches to the stack.  
        foreach ($results as $key => 
$value)  
     * Method to process the static taxonomy input. The static taxonomy input  
     * comes in the form of a pre-defined search filter that is assigned to the  
     * @param   integer  $filterId  The id of static filter.  
     * @return  boolean  True on success, false on failure.  
     * @throws  Exception on database error.  
        // Get the database object.  
        // Initialize user variables  
        $groups = 
implode(',', $user->getAuthorisedViewLevels());  
        // Load the predefined filter.  
        $query = 
$db->getQuery(true)  
            ->select('f.data, f.params')  
            ->from($db->quoteName('#__finder_filters') . 
' AS f')  
            ->where('f.filter_id = ' . (int) 
$filterId);  
        $return = 
$db->loadObject();  
        // Check the returned filter.  
        $this->filter = (int) 
$filterId;  
        // Get a parameter object for the filter date options.  
        $registry->loadString($return->params);  
        // Set the dates if not already set.  
        $this->dates->def('d1', $params->get('d1'));  
        $this->dates->def('d2', $params->get('d2'));  
        $this->dates->def('w1', $params->get('w1'));  
        $this->dates->def('w2', $params->get('w2'));  
        // Remove duplicates and sanitize.  
        $filters = 
explode(',', $return->data);  
        // Remove any values of zero.  
        // Check if we have any real input.  
         * Create the query to get filters from the database. We do this for  
         * two reasons: one, it allows us to ensure that the filters being used  
         * are real; two, we need to sort the filters by taxonomy branch.  
            ->select('t1.id, t1.title, t2.title AS branch')  
            ->from($db->quoteName('#__finder_taxonomy') . 
' AS t1')  
            ->join('INNER', $db->quoteName('#__finder_taxonomy') . 
' AS t2 ON t2.id = t1.parent_id')  
            ->where('t1.access IN (' . 
$groups . 
')')  
            ->where('t1.id IN (' . 
implode(',', $filters) . 
')')  
            ->where('t2.access IN (' . 
$groups . 
')');  
        $results = 
$db->loadObjectList();  
        // Sort the filter ids by branch.  
        foreach ($results as $result)  
            $this->filters[$result->branch][$result->title] = (int) 
$result->id;  
     * Method to process the dynamic taxonomy input. The dynamic taxonomy input  
     * comes in the form of select fields that the user chooses from. The  
     * dynamic taxonomy input is processed AFTER the static taxonomy input  
     * because the dynamic options can be used to further narrow a static  
     * @param   array  $filters  An array of taxonomy node ids.  
     * @return  boolean  True on success.  
     * @throws  Exception on database error.  
        // Initialize user variables  
        $groups = 
implode(',', $user->getAuthorisedViewLevels());  
        // Remove duplicates and sanitize.  
        // Remove any values of zero.  
        // Check if we have any real input.  
        // Get the database object.  
        $query = 
$db->getQuery(true);  
         * Create the query to get filters from the database. We do this for  
         * two reasons: one, it allows us to ensure that the filters being used  
         * are real; two, we need to sort the filters by taxonomy branch.  
        $query->select('t1.id, t1.title, t2.title AS branch')  
            ->from($db->quoteName('#__finder_taxonomy') . 
' AS t1')  
            ->join('INNER', $db->quoteName('#__finder_taxonomy') . 
' AS t2 ON t2.id = t1.parent_id')  
            ->where('t1.access IN (' . 
$groups . 
')')  
            ->where('t1.id IN (' . 
implode(',', $filters) . 
')')  
            ->where('t2.access IN (' . 
$groups . 
')');  
        $results = 
$db->loadObjectList();  
        // Cleared filter branches.  
         * Sort the filter ids by branch. Because these filters are designed to  
         * override and further narrow the items selected in the static filter,  
         * we will clear the values from the static filter on a branch by  
         * branch basis before adding the dynamic filters. So, if the static  
         * filter defines a type filter of "articles" and three "category"  
         * filters but the user only limits the category further, the category  
         * filters will be flushed but the type filters will not.  
        foreach ($results as $result)  
            // Check if the branch has been cleared.  
            if (!in_array($result->branch, $cleared))  
                $this->filters[$result->branch] = 
array();  
                // Add the branch to the cleared list.  
                $cleared[] = 
$result->branch;  
            // Add the filter to the list.  
            $this->filters[$result->branch][$result->title] = (int) 
$result->id;  
     * Method to process the query date filters to determine start and end  
     * @param   string  $date1  The first date filter.  
     * @param   string  $date2  The second date filter.  
     * @param   string  $when1  The first date modifier.  
     * @param   string  $when2  The second date modifier.  
     * @return  boolean  True on success.  
    protected function processDates($date1, $date2, $when1, $when2)  
        // Array of allowed when values.  
        $whens = 
array('before', 'after', 'exact');  
        // The value of 'today' is a special case that we need to handle.  
            $date1 = 
$today->format('%Y-%m-%d');  
        // Try to parse the date string.  
        // Check if the date was parsed successfully.  
        if ($date->toUnix() !== 
null)  
            $this->date1 = 
$date->toSQL();  
        // The value of 'today' is a special case that we need to handle.  
            $date2 = 
$today->format('%Y-%m-%d');  
        // Try to parse the date string.  
        // Check if the date was parsed successfully.  
        if ($date->toUnix() !== 
null)  
            $this->date2 = 
$date->toSQL();  
     * Method to process the query input string and extract required, optional,  
     * and excluded tokens; taxonomy filters; and date filters.  
     * @param   string  $input  The query input string.  
     * @param   string  $lang   The query input language.  
     * @param   string  $mode   The query matching mode.  
     * @return  boolean  True on success.  
     * @throws  Exception on database error.  
        // Clean up the input string.  
         * First, we need to handle string based modifiers. String based  
         * modifiers could potentially include things like "category:blah" or  
         * "before:2009-10-21" or "type:article", etc.  
            'before' => 
JText::_('COM_FINDER_FILTER_WHEN_BEFORE'),  
            'after' => 
JText::_('COM_FINDER_FILTER_WHEN_AFTER')  
        // Add the taxonomy branch titles to the possible patterns.  
        // Container for search terms and phrases.  
        // Cleared filter branches.  
         * Compile the suffix pattern. This is used to match the values of the  
         * filter input string. Single words can be input directly, multi-word  
         * values have to be wrapped in double quotes.  
        $suffix = 
'(([\w\d' . 
$quotes . 
'-]+)|\"([\w\d\s' . 
$quotes . 
'-]+)\")';  
         * Iterate through the possible filter patterns and search for matches.  
         * We need to match the key, colon, and a value pattern for the match  
        foreach ($patterns as $modifier => 
$pattern)  
                $pattern = 
substr($pattern, 2, -
2);  
            // Check if the filter pattern is in the input string.  
            if (preg_match('#' . 
$pattern . 
'\s*:\s*' . 
$suffix . 
'#mi', $input, $matches))  
                // Get the value given to the modifier.  
                $value = isset
($matches[3]) ? 
$matches[3] : 
$matches[1];  
                // Now we have to handle the filter string.  
                    // Handle a before and after date filters.  
                        // Array of allowed when values.  
                        $whens = 
array('before', 'after', 'exact');  
                        // The value of 'today' is a special case that we need to handle.  
                            $value = 
$today->format('%Y-%m-%d');  
                        // Try to parse the date string.  
                        // Check if the date was parsed successfully.  
                        if ($date->toUnix() !== 
null)  
                            $this->date1 = 
$date->toSQL();  
                            $this->when1 = 
in_array($modifier, $whens) ? 
$modifier : 
'before';  
                    // Handle a taxonomy branch filter.  
                        // Try to find the node id.  
                        // Check if the node id was found.  
                            // Check if the branch has been cleared.  
                                $this->filters[$modifier] = 
array();  
                                // Add the branch to the cleared list.  
                            // Add the filter to the list.  
                            $this->filters[$modifier][$return->title] = (int) 
$return->id;  
                // Clean up the input string again.  
         * Extract the tokens enclosed in double quotes so that we can handle  
            // Extract the tokens enclosed in double quotes.  
                 * One or more phrases were found so we need to iterate through  
                 * them, tokenize them as phrases, and remove them from the raw  
                 * input string before we move on to the next processing step.  
                foreach ($matches[1] as $key => 
$match)  
                    // Find the complete phrase in the input string.  
                    // Add any terms that are before this phrase to the stack.  
                    // Strip out everything up to and including the phrase.  
                    // Clean up the input string again.  
                    // Get the number of words in the phrase.  
                    // Check if the phrase is longer than three words.  
                         * If the phrase is longer than three words, we need to  
                         * break it down into smaller chunks of phrases that  
                         * are less than or equal to three words. We overlap  
                         * the chunks so that we can ensure that a match is  
                         * found for the complete phrase and not just portions  
                        for ($i = 
0, $c = 
count($parts); $i < 
$c; $i += 
2)  
                            // The chunk has to be assembled based on how many  
                            // pieces are available to use.  
                                 * If only one word is left, we can break from  
                                 * the switch and loop because the last word  
                                 * was already used at the end of the last  
                                // If there words are left, we use them both as  
                                // the last chunk of the phrase and we're done.  
                                    $chunk[] = 
$parts[$i + 
1];  
                                // If there are three or more words left, we  
                                // build a three word chunk and continue on.  
                                    $chunk[] = 
$parts[$i + 
1];  
                                    $chunk[] = 
$parts[$i + 
2];  
                            // If the chunk is not empty, add it as a phrase.  
                        // The phrase is <= 3 words so we can use it as is.  
        // Add the remaining terms if present.  
        // An array of our boolean operators. $operator => $translation  
        // If language debugging is enabled you need to ignore the debug strings in matching.  
            $debugStrings = 
array('**', '??');  
            $operators = 
str_replace($debugStrings, '', $operators);  
         * Iterate through the terms and perform any sorting that needs to be  
         * done based on boolean search operators. Terms that are before an  
         * and/or/not modifier have to be handled in relation to their operator.  
        for ($i = 
0, $c = 
count($terms); $i < 
$c; $i++
)  
            // Check if the term is followed by an operator that we understand.  
            if (isset
($terms[$i + 
1]) && 
in_array($terms[$i + 
1], $operators))  
                // Get the operator mode.  
                // Handle the AND operator.  
                if ($op === 
'AND' && isset
($terms[$i + 
2]))  
                    // Tokenize the current term.  
                    // Set the required flag.  
                    // Add the current token to the stack.  
                    // Skip the next token (the mode operator).  
                    // Tokenize the term after the next term (current plus two).  
                    // Set the required flag.  
                    // Add the token after the next token to the stack.  
                    // Remove the processed phrases if possible.  
                    if (($pk = 
array_search($terms[$i + 
2], $phrases)) !== 
false)  
                    // Remove the processed terms.  
                // Handle the OR operator.  
                elseif ($op === 
'OR' && isset
($terms[$i + 
2]))  
                    // Tokenize the current term.  
                    // Set the required flag.  
                    $token->required = 
false;  
                    // Add the current token to the stack.  
                    if (count($token->matches))  
                    // Skip the next token (the mode operator).  
                    // Tokenize the term after the next term (current plus two).  
                    // Set the required flag.  
                    $other->required = 
false;  
                    // Add the token after the next token to the stack.  
                    if (count($other->matches))  
                    // Remove the processed phrases if possible.  
                    if (($pk = 
array_search($terms[$i + 
2], $phrases)) !== 
false)  
                    // Remove the processed terms.  
            // Handle an orphaned OR operator.  
            elseif (isset
($terms[$i + 
1]) && 
array_search($terms[$i], $operators) === 
'OR')  
                // Skip the next token (the mode operator).  
                // Tokenize the next term (current plus one).  
                // Set the required flag.  
                $other->required = 
false;  
                // Add the token after the next token to the stack.  
                if (count($other->matches))  
                // Remove the processed phrase if possible.  
                if (($pk = 
array_search($terms[$i + 
1], $phrases)) !== 
false)  
                // Remove the processed terms.  
            // Handle the NOT operator.  
            elseif (isset
($terms[$i + 
1]) && 
array_search($terms[$i], $operators) === 
'NOT')  
                // Skip the next token (the mode operator).  
                // Tokenize the next term (current plus one).  
                // Set the required flag.  
                $other->required = 
false;  
                // Add the next token to the stack.  
                if (count($other->matches))  
                // Remove the processed phrase if possible.  
                if (($pk = 
array_search($terms[$i + 
1], $phrases)) !== 
false)  
                // Remove the processed terms.  
         * Iterate through any search phrases and tokenize them. We handle  
         * phrases as autonomous units and do not break them down into two and  
         * three word combinations.  
        for ($i = 
0, $c = 
count($phrases); $i < 
$c; $i++
)  
            // Set the required flag.  
            // Add the current token to the stack.  
            // Remove the processed term if possible.  
            // Remove the processed phrase.  
         * Handle any remaining tokens using the standard processing mechanism.  
            // Make sure we are working with an array.  
            $tokens = 
is_array($tokens) ? 
$tokens : 
array($tokens);  
            // Get the token data and required state for all the tokens.  
            foreach ($tokens as $token)  
                // Set the required flag for the token.  
                $token->required = 
$mode === 
'AND' ? 
($token->phrase ? 
false : 
true) : 
false;  
                // Add the token to the appropriate stack.  
                if (count($token->matches) || 
$token->required)  
     * Method to get the base and similar term ids and, if necessary, suggested  
     * term data from the database. The terms ids are identified based on a  
     * 'like' match in MySQL and/or a common stem. If no term ids could be  
     * found, then we know that we will not be able to return any results for  
     * that term and we should try to find a similar term to use that we can  
     * match so that we can suggest the alternative search query to the user.  
     * @param   FinderIndexerToken  $token  A FinderIndexerToken object.  
     * @return  FinderIndexerToken  A FinderIndexerToken object.  
     * @throws  Exception on database error.  
        // Get the database object.  
        // Create a database query to build match the token.  
        $query = 
$db->getQuery(true)  
            ->select('t.term, t.term_id')  
            ->from('#__finder_terms AS t');  
         * If the token is a phrase, the lookup process is fairly simple. If  
         * the token is a word, it is a little more complicated. We have to  
         * create two queries to lookup the term and the stem respectively,  
         * then union the result sets together. This is MUCH faster than using  
         * an or condition in the database query.  
            // Add the phrase to the query.  
            $query->where('t.term = ' . 
$db->quote($token->term))  
            // Add the term to the query.  
            $query->where('t.term = ' . 
$db->quote($token->term))  
            // Clone the query, replace the WHERE clause.  
            $sub->where('t.stem = ' . 
$db->quote($token->stem));  
            $sub->where('t.phrase = 0');  
            // Union the two queries.  
        $matches = 
$db->loadObjectList();  
        $token->matches = 
array();  
        // Check the matching terms.  
            // Add the matches to the token.  
            for ($i = 
0, $c = 
count($matches); $i < 
$c; $i++
)  
                $token->matches[$matches[$i]->term] = (int) 
$matches[$i]->term_id;  
        // If no matches were found, try to find a similar but better token.  
        if (empty($token->matches))  
            // Create a database query to get the similar terms.  
            // TODO: PostgreSQL doesn't support SOUNDEX out of the box  
                ->select('DISTINCT t.term_id AS id, t.term AS term')  
                ->from('#__finder_terms AS t')  
                // ->where('t.soundex = ' . soundex($db->quote($token->term)))  
                ->where('t.soundex = SOUNDEX(' . 
$db->quote($token->term) . 
')')  
                ->where('t.phrase = ' . (int) 
$token->phrase);  
            $results = 
$db->loadObjectList();  
            // Check if any similar terms were found.  
            // Stack for sorting the similar terms.  
            // Get the levnshtein distance for all suggested terms.  
            foreach ($results as $sk => 
$st)  
                // Get the levenshtein distance between terms.  
                // Make sure the levenshtein distance isn't over 50.  
                    $suggestions[$sk] = 
$distance;  
            asort($suggestions, SORT_NUMERIC);  
            // Get the closest match.  
            // Add the suggested term.  
            $token->suggestion = 
$results[$key]->term;  
 
 
	
		Documentation generated on Tue, 19 Nov 2013 15:11:28 +0100 by phpDocumentor 1.4.3