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