Source for file search.php
Documentation is available at search.php
* @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
// Register dependent classes.
define('FINDER_PATH_INDEXER', JPATH_ADMINISTRATOR .
'/components/com_finder/helpers/indexer');
* Search model class for the Finder package.
* Context string for the model type
protected $context =
'com_finder.search';
* The query object is an instance of FinderIndexerQuery which contains and
* models the entire search query including the text input; static and
* dynamic taxonomy filters; date filters; etc.
* @var FinderIndexerQuery
* An array of all excluded terms ids.
* An array of all included terms ids.
* An array of all required terms ids.
* Method to get the results of the query.
* @return array An array of FinderIndexerResult objects.
* @throws Exception on database error.
// Check if the search query is valid.
if (empty($this->query->search))
// Check if we should return results.
// Use the cached data if possible.
// Create the query to get the search results.
$query =
$db->getQuery(true)
->select($db->quoteName('link_id') .
', ' .
$db->quoteName('object'))
->from($db->quoteName('#__finder_links'))
->where($db->quoteName('link_id') .
' IN (' .
implode(',', array_keys($items)) .
')');
// Load the results from the database.
$rows =
$db->loadObjectList('link_id');
// Set up our results container.
// Convert the rows to result objects.
foreach ($rows as $rk =>
$row)
// Build the result object.
$result->weight =
$results[$rk];
// Add the result back to the stack.
// Switch to a non-associative array.
// Push the results into cache.
$this->store($store, $results);
* Method to get the total number of results.
* @return integer The total number of results.
* @throws Exception on database error.
// Check if the search query is valid.
if (empty($this->query->search))
// Check if we should return results.
// Use the cached data if possible.
// Get the results total.
// Push the total into cache.
$this->store($store, $total);
* Method to get the query object.
* @return FinderIndexerQuery A query object.
// Return the query object.
* Method to build a database query to load the list data.
* @return JDatabaseQuery A database query.
// Use the cached data if possible.
return clone($this->retrieve($store, false));
$groups =
implode(',', $user->getAuthorisedViewLevels());
// Create a new query object.
$query =
$db->getQuery(true)
->from($db->quoteName('#__finder_links') .
' AS l')
->where('l.access IN (' .
$groups .
')')
// Get the null date and the current date, minus seconds.
$nullDate =
$db->quote($db->getNullDate());
// Add the publish up and publish down filters.
$query->where('(l.publish_start_date = ' .
$nullDate .
' OR l.publish_end_date <= ' .
$nowDate .
')')
->where('(l.publish_end_date = ' .
$nullDate .
' OR l.publish_end_date >= ' .
$nowDate .
')');
* Add the taxonomy filters to the query. We have to join the taxonomy
* map table for each group so that we can use AND clauses across
* groups. Within each group there can be an array of values that will
if (!empty($this->query->filters))
// Convert the associative array to a numerically indexed array.
// Iterate through each taxonomy group and add the join and where.
for ($i =
0, $c =
count($groups); $i <
$c; $i++
)
// We use the offset because each join needs a unique alias.
$query->join('INNER', $db->quoteName('#__finder_taxonomy_map') .
' AS t' .
$i .
' ON t' .
$i .
'.link_id = l.link_id')
->where('t' .
$i .
'.node_id IN (' .
implode(',', $groups[$i]) .
')');
// Add the start date filter to the query.
if (!empty($this->query->date1))
$date1 =
$db->quote($this->query->date1);
// Add the appropriate WHERE condition.
if ($this->query->when1 ==
'before')
$query->where($db->quoteName('l.start_date') .
' <= ' .
$date1);
elseif ($this->query->when1 ==
'after')
$query->where($db->quoteName('l.start_date') .
' >= ' .
$date1);
$query->where($db->quoteName('l.start_date') .
' = ' .
$date1);
// Add the end date filter to the query.
if (!empty($this->query->date2))
$date2 =
$db->quote($this->query->date2);
// Add the appropriate WHERE condition.
if ($this->query->when2 ==
'before')
$query->where($db->quoteName('l.start_date') .
' <= ' .
$date2);
elseif ($this->query->when2 ==
'after')
$query->where($db->quoteName('l.start_date') .
' >= ' .
$date2);
$query->where($db->quoteName('l.start_date') .
' = ' .
$date2);
$query->where('l.language IN (' .
$db->quote(JFactory::getLanguage()->getTag()) .
', ' .
$db->quote('*') .
')');
// Push the data into cache.
$this->store($store, $query, false);
// Return a copy of the query object.
return clone($this->retrieve($store, false));
* Method to get the total number of results for the search query.
* @return integer The results total.
* @throws Exception on database error.
$store =
$this->getStoreId('getResultsTotal', false);
// Use the cached data if possible.
// Get the base query and add the ordering information.
$base->select('0 AS ordering');
// Get the maximum number of results.
$limit = (int)
$this->getState('match.limit');
* If there are no optional or required search terms in the query,
* we can get the result total in one relatively simple database query.
// Adjust the query to join on the appropriate mapping table.
->select('COUNT(DISTINCT l.link_id)');
// Get the total from the database.
$this->_db->setQuery($query);
$total =
$this->_db->loadResult();
// Push the total into cache.
$this->store($store, min($total, $limit));
* If there are optional or required search terms in the query, the
* process of getting the result total is more complicated.
* Iterate through the included search terms and group them by mapping
* table suffix. This ensures that we never have to do more than 16
* queries to get a batch. This may seem like a lot but it is rarely
* anywhere near 16 because of the improved mapping algorithm.
// Get the mapping table suffix.
// Initialize the mapping group.
$maps[$suffix] =
array();
// Add the terms to the mapping group.
* When the query contains search terms we need to find and process the
* result total iteratively using a do-while loop.
// Create a container for the fetched results.
* Iterate through the mapping groups and load the total from each
foreach ($maps as $suffix =>
$ids)
// Create a storage key for this set.
// Use the cached data if possible.
// Load the data from the database.
// Adjust the query to join on the appropriate mapping table.
$query->join('INNER', '#__finder_links_terms' .
$suffix .
' AS m ON m.link_id = l.link_id')
->where('m.term_id IN (' .
implode(',', $ids) .
')');
// Load the results from the database.
$this->_db->setQuery($query, $start, $limit);
$temp =
$this->_db->loadObjectList();
// Set the more flag to true if any of the sets equal the limit.
$more =
(count($temp) ===
$limit) ?
true :
false;
// We loaded the data unkeyed but we need it to be keyed for later.
// Convert to an associative array.
for ($i =
0, $c =
count($junk); $i <
$c; $i++
)
$temp[$junk[$i]->link_id] =
$junk[$i];
// Store this set in cache.
$this->store($setId, $temp);
// Check if there are any excluded terms to deal with.
// Remove any results that match excluded terms.
for ($i =
0, $c =
count($results); $i <
$c; $i++
)
if (in_array($results[$i]->link_id, $excluded))
// Iterate through the set to extract the unique items.
for ($i =
0, $c =
count($results); $i <
$c; $i++
)
if (!isset
($sorted[$results[$i]->link_id]))
$sorted[$results[$i]->link_id] =
$results[$i]->ordering;
* If the query contains just optional search terms and we have
* enough items for the page, we can stop here.
// If we need more items and they're available, make another pass.
if ($more &&
count($sorted) <
$limit)
// Increment the batch starting point and continue.
// Push the total into cache.
* The query contains required search terms so we have to iterate
* over the items and remove any items that do not match all of the
* required search terms. This is one of the most expensive steps
* because a required token could theoretically eliminate all of
* current terms which means we would have to loop through all of
// Create a storage key for this set.
// Use the cached data if possible.
// Check if the token was matched.
elseif (empty($required))
// Load the data from the database.
// Setup containers in case we have to make multiple passes.
// Get the map table suffix.
// Adjust the query to join on the appropriate mapping table.
$query->join('INNER', '#__finder_links_terms' .
$suffix .
' AS m ON m.link_id = l.link_id')
->where('m.term_id IN (' .
implode(',', $required) .
')');
// Load the results from the database.
$this->_db->setQuery($query, $reqStart, $limit);
$temp =
$this->_db->loadObjectList('link_id');
// Set the required token more flag to true if the set equal the limit.
$reqMore =
(count($temp) ===
$limit) ?
true :
false;
// Merge the matching set for this token.
$reqTemp =
$reqTemp +
$temp;
// Increment the term offset.
while ($reqMore ==
true);
// Store this set in cache.
$this->store($setId, $reqTemp);
// Remove any items that do not match the required term.
// If we need more items and they're available, make another pass.
if ($more &&
count($sorted) <
$limit)
// Increment the batch starting point.
// Merge the found items.
$items =
$items +
$sorted;
// Otherwise, end the loop.
// Merge the found items.
$items =
$items +
$sorted;
$total =
min($total, $limit);
// Push the total into cache.
$this->store($store, $total);
* Method to get the results for the search query.
* @return array An array of result data objects.
* @throws Exception on database error.
$store =
$this->getStoreId('getResultsData', false);
// Use the cached data if possible.
// Get the result ordering and direction.
$ordering =
$this->getState('list.ordering', 'l.start_date');
$direction =
$this->getState('list.direction', 'DESC');
// Get the base query and add the ordering information.
$base->select($this->_db->escape($ordering) .
' AS ordering');
$base->order($this->_db->escape($ordering) .
' ' .
$this->_db->escape($direction));
* If there are no optional or required search terms in the query, we
* can get the results in one relatively simple database query.
// Get the results from the database.
$this->_db->setQuery($base, (int)
$this->getState('list.start'), (int)
$this->getState('list.limit'));
$return =
$this->_db->loadObjectList('link_id');
// Get a new store id because this data is page specific.
$store =
$this->getStoreId('getResultsData', true);
// Push the results into cache.
$this->store($store, $return);
* If there are optional or required search terms in the query, the
* process of getting the results is more complicated.
$limit = (int)
$this->getState('match.limit');
* Iterate through the included search terms and group them by mapping
* table suffix. This ensures that we never have to do more than 16
* queries to get a batch. This may seem like a lot but it is rarely
* anywhere near 16 because of the improved mapping algorithm.
// Get the mapping table suffix.
// Initialize the mapping group.
$maps[$suffix] =
array();
// Add the terms to the mapping group.
* When the query contains search terms we need to find and process the
* results iteratively using a do-while loop.
// Create a container for the fetched results.
* Iterate through the mapping groups and load the results from each
foreach ($maps as $suffix =>
$ids)
// Create a storage key for this set.
// Use the cached data if possible.
// Load the data from the database.
// Adjust the query to join on the appropriate mapping table.
$query->join('INNER', $this->_db->quoteName('#__finder_links_terms' .
$suffix) .
' AS m ON m.link_id = l.link_id')
->where('m.term_id IN (' .
implode(',', $ids) .
')');
// Load the results from the database.
$this->_db->setQuery($query, $start, $limit);
$temp =
$this->_db->loadObjectList('link_id');
// Store this set in cache.
$this->store($setId, $temp);
// The data is keyed by link_id to ease caching, we don't need it till later.
// Set the more flag to true if any of the sets equal the limit.
$more =
(count($temp) ===
$limit) ?
true :
false;
// Check if there are any excluded terms to deal with.
// Remove any results that match excluded terms.
for ($i =
0, $c =
count($results); $i <
$c; $i++
)
if (in_array($results[$i]->link_id, $excluded))
* If we are ordering by relevance we have to add up the relevance
* scores that are contained in the ordering field.
if ($ordering ===
'm.weight')
// Iterate through the set to extract the unique items.
for ($i =
0, $c =
count($results); $i <
$c; $i++
)
// Add the total weights for all included search terms.
if (isset
($sorted[$results[$i]->link_id]))
$sorted[$results[$i]->link_id] += (float)
$results[$i]->ordering;
$sorted[$results[$i]->link_id] = (float)
$results[$i]->ordering;
* If we are ordering by start date we have to add convert the
* dates to unix timestamps.
elseif ($ordering ===
'l.start_date')
// Iterate through the set to extract the unique items.
for ($i =
0, $c =
count($results); $i <
$c; $i++
)
if (!isset
($sorted[$results[$i]->link_id]))
$sorted[$results[$i]->link_id] =
strtotime($results[$i]->ordering);
* If we are not ordering by relevance or date, we just have to add
* the unique items to the set.
// Iterate through the set to extract the unique items.
for ($i =
0, $c =
count($results); $i <
$c; $i++
)
if (!isset
($sorted[$results[$i]->link_id]))
$sorted[$results[$i]->link_id] =
$results[$i]->ordering;
if ($direction ===
'DESC')
* If the query contains just optional search terms and we have
* enough items for the page, we can stop here.
// If we need more items and they're available, make another pass.
// Increment the batch starting point and continue.
// Push the results into cache.
$this->store($store, $sorted);
// Return the requested set.
* The query contains required search terms so we have to iterate
* over the items and remove any items that do not match all of the
* required search terms. This is one of the most expensive steps
* because a required token could theoretically eliminate all of
* current terms which means we would have to loop through all of
// Create a storage key for this set.
// Use the cached data if possible.
// Check if the token was matched.
elseif (empty($required))
// Load the data from the database.
// Setup containers in case we have to make multiple passes.
// Get the map table suffix.
// Adjust the query to join on the appropriate mapping table.
$query->join('INNER', $this->_db->quoteName('#__finder_links_terms' .
$suffix) .
' AS m ON m.link_id = l.link_id')
->where('m.term_id IN (' .
implode(',', $required) .
')');
// Load the results from the database.
$this->_db->setQuery($query, $reqStart, $limit);
$temp =
$this->_db->loadObjectList('link_id');
// Set the required token more flag to true if the set equal the limit.
$reqMore =
(count($temp) ===
$limit) ?
true :
false;
// Merge the matching set for this token.
$reqTemp =
$reqTemp +
$temp;
// Increment the term offset.
while ($reqMore ==
true);
// Store this set in cache.
$this->store($setId, $reqTemp);
// Remove any items that do not match the required term.
// If we need more items and they're available, make another pass.
// Increment the batch starting point.
// Merge the found items.
// Otherwise, end the loop.
// Push the results into cache.
$this->store($store, $items);
// Return the requested set.
* Method to get an array of link ids that match excluded terms.
* @return array An array of links ids.
* @throws Exception on database error.
// Check if the search query has excluded terms.
$store =
$this->getStoreId('getExcludedLinkIds', false);
// Use the cached data if possible.
// Initialize containers.
* Iterate through the excluded search terms and group them by mapping
* table suffix. This ensures that we never have to do more than 16
* queries to get a batch. This may seem like a lot but it is rarely
* anywhere near 16 because of the improved mapping algorithm.
// Get the mapping table suffix.
// Initialize the mapping group.
$maps[$suffix] =
array();
// Add the terms to the mapping group.
$maps[$suffix][] = (int)
$id;
* Iterate through the mapping groups and load the excluded links ids
* from each mapping table.
// Create a new query object.
$query =
$db->getQuery(true);
foreach ($maps as $suffix =>
$ids)
// Create the query to get the links ids.
->from($db->quoteName('#__finder_links_terms' .
$suffix))
->where($db->quoteName('term_id') .
' IN (' .
implode(',', $ids) .
')')
->group($db->quoteName('link_id'));
// Load the link ids from the database.
$temp =
$db->loadColumn();
// Sanitize the link ids.
// Push the link ids into cache.
$this->store($store, $links);
* Method to get a subquery for filtering link ids mapped to specific
* @param array $terms An array of search term ids.
* @return JDatabaseQuery A database object.
// Create the SQL query to get the matching link ids.
// TODO: Impact of removing SQL_NO_CACHE?
$query =
$db->getQuery(true)
->select('SQL_NO_CACHE link_id')
->from('#__finder_links_terms')
->where('term_id IN (' .
implode(',', $terms) .
')');
* Method to get a store id based on model the configuration state.
* This is necessary because the model is used by the component and
* different modules that might need different sets of data or different
* @param string $id An identifier string to generate the store id. [optional]
* @param boolean $page True to store the data paged, false to store all data. [optional]
* @return string A store id.
protected function getStoreId($id =
'', $page =
true)
// Add the search query state.
$id .=
':' .
$query->input;
$id .=
':' .
$query->language;
$id .=
':' .
$query->filter;
$id .=
':' .
$query->date1;
$id .=
':' .
$query->date2;
$id .=
':' .
$query->when1;
$id .=
':' .
$query->when2;
// Add the list state for page specific data.
$id .=
':' .
$this->getState('list.start');
$id .=
':' .
$this->getState('list.limit');
$id .=
':' .
$this->getState('list.ordering');
$id .=
':' .
$this->getState('list.direction');
* Method to auto-populate the model state. Calling getState in this method will result in recursion.
* @param string $ordering An optional ordering field. [optional]
* @param string $direction An optional direction. [optional]
protected function populateState($ordering =
null, $direction =
null)
// Get the configuration options.
$params =
$app->getParams();
if ($params->get('stem', 1) &&
$params->get('stemmer', 'porter_en'))
FinderIndexerHelper::$stemmer =
FinderIndexerStemmer::getInstance($params->get('stemmer', 'porter_en'));
$request =
$input->request;
$options['input'] =
!is_null($request->get('q')) ?
$request->get('q', '', 'string') :
$params->get('q');
$options['input'] =
$filter->clean($options['input'], 'string');
// Get the empty query setting.
$options['empty'] =
$params->get('allow_empty_query', 0);
// Get the query language.
$options['language'] =
!is_null($request->get('l')) ?
$request->get('l', '', 'cmd') :
$params->get('l');
$options['language'] =
$filter->clean($options['language'], 'cmd');
// Get the static taxonomy filters.
$options['filter'] =
!is_null($request->get('f')) ?
$request->get('f', '', 'int') :
$params->get('f');
$options['filter'] =
$filter->clean($options['filter'], 'int');
// Get the dynamic taxonomy filters.
$options['filters'] =
!is_null($request->get('t', '', 'array')) ?
$request->get('t', '', 'array') :
$params->get('t');
$options['filters'] =
$filter->clean($options['filters'], 'array');
// Get the start date and start date modifier filters.
$options['date1'] =
!is_null($request->get('d1')) ?
$request->get('d1', '', 'string') :
$params->get('d1');
$options['date1'] =
$filter->clean($options['date1'], 'string');
$options['when1'] =
!is_null($request->get('w1')) ?
$request->get('w1', '', 'string') :
$params->get('w1');
$options['when1'] =
$filter->clean($options['when1'], 'string');
// Get the end date and end date modifier filters.
$options['date2'] =
!is_null($request->get('d2')) ?
$request->get('d2', '', 'string') :
$params->get('d2');
$options['date2'] =
$filter->clean($options['date2'], 'string');
$options['when2'] =
!is_null($request->get('w2')) ?
$request->get('w2', '', 'string') :
$params->get('w2');
$options['when2'] =
$filter->clean($options['when2'], 'string');
// Load the query object.
// Load the query token data.
$this->setState('list.start', $input->get('limitstart', 0, 'uint'));
$this->setState('list.limit', $input->get('limit', $app->getCfg('list_limit', 20), 'uint'));
// Load the sort ordering.
$order =
$params->get('sort_order', 'relevance');
$this->setState('list.ordering', 'l.start_date');
$this->setState('list.ordering', 'l.list_price');
$this->setState('list.ordering', 'm.weight');
$this->setState('list.ordering', 'l.link_id');
// Load the sort direction.
$dirn =
$params->get('sort_direction', 'desc');
$this->setState('list.direction', 'ASC');
$this->setState('list.direction', 'DESC');
$this->setState('user.id', (int)
$user->get('id'));
$this->setState('user.groups', $user->getAuthorisedViewLevels());
* Method to retrieve data from cache.
* @param string $id The cache store id.
* @param boolean $persistent Flag to enable the use of external cache. [optional]
* @return mixed The cached data if found, null otherwise.
protected function retrieve($id, $persistent =
true)
// Use the internal cache if possible.
if (isset
($this->cache[$id]))
return $this->cache[$id];
// Use the external cache if data is persistent.
// Store the data in internal cache.
$this->cache[$id] =
$data;
* Method to store data in cache.
* @param string $id The cache store id.
* @param mixed $data The data to cache.
* @param boolean $persistent Flag to enable the use of external cache. [optional]
* @return boolean True on success, false on failure.
protected function store($id, $data, $persistent =
true)
// Store the data in internal cache.
$this->cache[$id] =
$data;
// Store the data in external cache if data is persistent.
Documentation generated on Tue, 19 Nov 2013 15:12:42 +0100 by phpDocumentor 1.4.3