You've already forked joomla_test
							
							
		
			
				
	
	
		
			1367 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			1367 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /**
 | |
|  * @package     Joomla.Administrator
 | |
|  * @subpackage  com_finder
 | |
|  *
 | |
|  * @copyright   Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
 | |
|  * @license     GNU General Public License version 2 or later; see LICENSE
 | |
|  */
 | |
| 
 | |
| defined('_JEXEC') or die;
 | |
| 
 | |
| JLoader::register('FinderIndexerHelper', __DIR__ . '/helper.php');
 | |
| JLoader::register('FinderIndexerTaxonomy', __DIR__ . '/taxonomy.php');
 | |
| JLoader::register('FinderHelperRoute', JPATH_SITE . '/components/com_finder/helpers/route.php');
 | |
| JLoader::register('FinderHelperLanguage', JPATH_ADMINISTRATOR . '/components/com_finder/helpers/language.php');
 | |
| 
 | |
| /**
 | |
|  * Query class for the Finder indexer package.
 | |
|  *
 | |
|  * @package     Joomla.Administrator
 | |
|  * @subpackage  com_finder
 | |
|  * @since       2.5
 | |
|  */
 | |
| class FinderIndexerQuery
 | |
| {
 | |
| 	/**
 | |
| 	 * Flag to show whether the query can return results.
 | |
| 	 *
 | |
| 	 * @var    boolean
 | |
| 	 * @since  2.5
 | |
| 	 */
 | |
| 	public $search;
 | |
| 
 | |
| 	/**
 | |
| 	 * The query input string.
 | |
| 	 *
 | |
| 	 * @var    string
 | |
| 	 * @since  2.5
 | |
| 	 */
 | |
| 	public $input;
 | |
| 
 | |
| 	/**
 | |
| 	 * The language of the query.
 | |
| 	 *
 | |
| 	 * @var    string
 | |
| 	 * @since  2.5
 | |
| 	 */
 | |
| 	public $language;
 | |
| 
 | |
| 	/**
 | |
| 	 * The query string matching mode.
 | |
| 	 *
 | |
| 	 * @var    string
 | |
| 	 * @since  2.5
 | |
| 	 */
 | |
| 	public $mode;
 | |
| 
 | |
| 	/**
 | |
| 	 * The included tokens.
 | |
| 	 *
 | |
| 	 * @var    array
 | |
| 	 * @since  2.5
 | |
| 	 */
 | |
| 	public $included = array();
 | |
| 
 | |
| 	/**
 | |
| 	 * The excluded tokens.
 | |
| 	 *
 | |
| 	 * @var    array
 | |
| 	 * @since  2.5
 | |
| 	 */
 | |
| 	public $excluded = array();
 | |
| 
 | |
| 	/**
 | |
| 	 * The tokens to ignore because no matches exist.
 | |
| 	 *
 | |
| 	 * @var    array
 | |
| 	 * @since  2.5
 | |
| 	 */
 | |
| 	public $ignored = array();
 | |
| 
 | |
| 	/**
 | |
| 	 * The operators used in the query input string.
 | |
| 	 *
 | |
| 	 * @var    array
 | |
| 	 * @since  2.5
 | |
| 	 */
 | |
| 	public $operators = array();
 | |
| 
 | |
| 	/**
 | |
| 	 * The terms to highlight as matches.
 | |
| 	 *
 | |
| 	 * @var    array
 | |
| 	 * @since  2.5
 | |
| 	 */
 | |
| 	public $highlight = array();
 | |
| 
 | |
| 	/**
 | |
| 	 * The number of matching terms for the query input.
 | |
| 	 *
 | |
| 	 * @var    integer
 | |
| 	 * @since  2.5
 | |
| 	 */
 | |
| 	public $terms;
 | |
| 
 | |
| 	/**
 | |
| 	 * The static filter id.
 | |
| 	 *
 | |
| 	 * @var    string
 | |
| 	 * @since  2.5
 | |
| 	 */
 | |
| 	public $filter;
 | |
| 
 | |
| 	/**
 | |
| 	 * The taxonomy filters. This is a multi-dimensional array of taxonomy
 | |
| 	 * branches as the first level and then the taxonomy nodes as the values.
 | |
| 	 *
 | |
| 	 * For example:
 | |
| 	 * $filters = array(
 | |
| 	 *     'Type' = array(10, 32, 29, 11, ...);
 | |
| 	 *     'Label' = array(20, 314, 349, 91, 82, ...);
 | |
| 	 *        ...
 | |
| 	 * );
 | |
| 	 *
 | |
| 	 * @var    array
 | |
| 	 * @since  2.5
 | |
| 	 */
 | |
| 	public $filters = array();
 | |
| 
 | |
| 	/**
 | |
| 	 * The start date filter.
 | |
| 	 *
 | |
| 	 * @var    string
 | |
| 	 * @since  2.5
 | |
| 	 */
 | |
| 	public $date1;
 | |
| 
 | |
| 	/**
 | |
| 	 * The end date filter.
 | |
| 	 *
 | |
| 	 * @var    string
 | |
| 	 * @since  2.5
 | |
| 	 */
 | |
| 	public $date2;
 | |
| 
 | |
| 	/**
 | |
| 	 * The start date filter modifier.
 | |
| 	 *
 | |
| 	 * @var    string
 | |
| 	 * @since  2.5
 | |
| 	 */
 | |
| 	public $when1;
 | |
| 
 | |
| 	/**
 | |
| 	 * The end date filter modifier.
 | |
| 	 *
 | |
| 	 * @var    string
 | |
| 	 * @since  2.5
 | |
| 	 */
 | |
| 	public $when2;
 | |
| 
 | |
| 	/**
 | |
| 	 * Method to instantiate the query object.
 | |
| 	 *
 | |
| 	 * @param   array  $options  An array of query options.
 | |
| 	 *
 | |
| 	 * @since   2.5
 | |
| 	 * @throws  Exception on database error.
 | |
| 	 */
 | |
| 	public function __construct($options)
 | |
| 	{
 | |
| 		// Get the input string.
 | |
| 		$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.
 | |
| 		$this->language = !empty($options['language']) ? $options['language'] : FinderIndexerHelper::getDefaultLanguage();
 | |
| 		$this->language = FinderIndexerHelper::getPrimaryLanguage($this->language);
 | |
| 
 | |
| 		// Get the matching mode.
 | |
| 		$this->mode = 'AND';
 | |
| 
 | |
| 		// Initialize the temporary date storage.
 | |
| 		$this->dates = new JRegistry;
 | |
| 
 | |
| 		// 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']))
 | |
| 		{
 | |
| 			$this->processStaticTaxonomy($options['filter']);
 | |
| 		}
 | |
| 
 | |
| 		// Process the dynamic taxonomy filters.
 | |
| 		if (isset($options['filters']) && !empty($options['filters']))
 | |
| 		{
 | |
| 			$this->processDynamicTaxonomy($options['filters']);
 | |
| 		}
 | |
| 
 | |
| 		// Get the date 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))
 | |
| 		{
 | |
| 			$this->processDates($d1, $d2, $w1, $w2);
 | |
| 		}
 | |
| 
 | |
| 		// Process the input string.
 | |
| 		$this->processString($this->input, $this->language, $this->mode);
 | |
| 
 | |
| 		// Get the number of matching terms.
 | |
| 		foreach ($this->included as $token)
 | |
| 		{
 | |
| 			$this->terms += count($token->matches);
 | |
| 		}
 | |
| 
 | |
| 		// Remove the temporary date storage.
 | |
| 		unset($this->dates);
 | |
| 
 | |
| 		/*
 | |
| 		 * Lastly, determine whether this query can return a result set.
 | |
| 		 */
 | |
| 		// Check if we have a query string.
 | |
| 		if (!empty($this->input))
 | |
| 		{
 | |
| 			$this->search = true;
 | |
| 		}
 | |
| 		// Check if we can search without a query string.
 | |
| 		elseif ($this->empty && (!empty($this->filter) || !empty($this->filters) || !empty($this->date1) || !empty($this->date2)))
 | |
| 		{
 | |
| 			$this->search = true;
 | |
| 		}
 | |
| 		// We do not have a valid search query.
 | |
| 		else
 | |
| 		{
 | |
| 			$this->search = false;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Method to convert the query object into a URI string.
 | |
| 	 *
 | |
| 	 * @param   string  $base  The base URI. [optional]
 | |
| 	 *
 | |
| 	 * @return  string  The complete query URI.
 | |
| 	 *
 | |
| 	 * @since   2.5
 | |
| 	 */
 | |
| 	public function toURI($base = null)
 | |
| 	{
 | |
| 		// Set the base if not specified.
 | |
| 		if (empty($base))
 | |
| 		{
 | |
| 			$base = 'index.php?option=com_finder&view=search';
 | |
| 		}
 | |
| 
 | |
| 		// Get the base URI.
 | |
| 		$uri = JUri::getInstance($base);
 | |
| 
 | |
| 		// Add the static taxonomy filter if present.
 | |
| 		if (!empty($this->filter))
 | |
| 		{
 | |
| 			$uri->setVar('f', $this->filter);
 | |
| 		}
 | |
| 
 | |
| 		// Get the filters in the request.
 | |
| 		$input = JFactory::getApplication()->input;
 | |
| 		$t = $input->request->get('t', array(), 'array');
 | |
| 
 | |
| 		// Add the dynamic taxonomy filters if present.
 | |
| 		if (!empty($this->filters))
 | |
| 		{
 | |
| 			foreach ($this->filters as $nodes)
 | |
| 			{
 | |
| 				foreach ($nodes as $node)
 | |
| 				{
 | |
| 					if (!in_array($node, $t))
 | |
| 					{
 | |
| 						continue;
 | |
| 					}
 | |
| 					$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'))
 | |
| 		{
 | |
| 			// Get the menu item id.
 | |
| 			$query = array(
 | |
| 				'view' => $uri->getVar('view'),
 | |
| 				'f' => $uri->getVar('f'),
 | |
| 				'q' => $uri->getVar('q')
 | |
| 			);
 | |
| 			$item = FinderHelperRoute::getItemid($query);
 | |
| 
 | |
| 			// Add the menu item id if present.
 | |
| 			if ($item !== null)
 | |
| 			{
 | |
| 				$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.
 | |
| 	 *
 | |
| 	 * @since   2.5
 | |
| 	 */
 | |
| 	public function getExcludedTermIds()
 | |
| 	{
 | |
| 		$results = array();
 | |
| 
 | |
| 		// Iterate through the excluded tokens and compile the matching terms.
 | |
| 		for ($i = 0, $c = count($this->excluded); $i < $c; $i++)
 | |
| 		{
 | |
| 			$results = array_merge($results, $this->excluded[$i]->matches);
 | |
| 		}
 | |
| 
 | |
| 		// Sanitize the terms.
 | |
| 		$results = array_unique($results);
 | |
| 		JArrayHelper::toInteger($results);
 | |
| 
 | |
| 		return $results;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Method to get a list of included search term ids.
 | |
| 	 *
 | |
| 	 * @return  array  An array of included term ids.
 | |
| 	 *
 | |
| 	 * @since   2.5
 | |
| 	 */
 | |
| 	public function getIncludedTermIds()
 | |
| 	{
 | |
| 		$results = array();
 | |
| 
 | |
| 		// Iterate through the included tokens and compile the matching terms.
 | |
| 		for ($i = 0, $c = count($this->included); $i < $c; $i++)
 | |
| 		{
 | |
| 			// Check if we have any terms.
 | |
| 			if (empty($this->included[$i]->matches))
 | |
| 			{
 | |
| 				continue;
 | |
| 			}
 | |
| 
 | |
| 			// Get the term.
 | |
| 			$term = $this->included[$i]->term;
 | |
| 
 | |
| 			// Prepare the container for the term if necessary.
 | |
| 			if (!array_key_exists($term, $results))
 | |
| 			{
 | |
| 				$results[$term] = array();
 | |
| 			}
 | |
| 
 | |
| 			// Add the matches to the stack.
 | |
| 			$results[$term] = array_merge($results[$term], $this->included[$i]->matches);
 | |
| 		}
 | |
| 
 | |
| 		// Sanitize the terms.
 | |
| 		foreach ($results as $key => $value)
 | |
| 		{
 | |
| 			$results[$key] = array_unique($results[$key]);
 | |
| 			JArrayHelper::toInteger($results[$key]);
 | |
| 		}
 | |
| 
 | |
| 		return $results;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Method to get a list of required search term ids.
 | |
| 	 *
 | |
| 	 * @return  array  An array of required term ids.
 | |
| 	 *
 | |
| 	 * @since   2.5
 | |
| 	 */
 | |
| 	public function getRequiredTermIds()
 | |
| 	{
 | |
| 		$results = array();
 | |
| 
 | |
| 		// Iterate through the included tokens and compile the matching terms.
 | |
| 		for ($i = 0, $c = count($this->included); $i < $c; $i++)
 | |
| 		{
 | |
| 			// Check if the token is required.
 | |
| 			if ($this->included[$i]->required)
 | |
| 			{
 | |
| 				// Get the term.
 | |
| 				$term = $this->included[$i]->term;
 | |
| 
 | |
| 				// Prepare the container for the term if necessary.
 | |
| 				if (!array_key_exists($term, $results))
 | |
| 				{
 | |
| 					$results[$term] = array();
 | |
| 				}
 | |
| 
 | |
| 				// Add the matches to the stack.
 | |
| 				$results[$term] = array_merge($results[$term], $this->included[$i]->matches);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Sanitize the terms.
 | |
| 		foreach ($results as $key => $value)
 | |
| 		{
 | |
| 			$results[$key] = array_unique($results[$key]);
 | |
| 			JArrayHelper::toInteger($results[$key]);
 | |
| 		}
 | |
| 
 | |
| 		return $results;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * 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
 | |
| 	 * search form.
 | |
| 	 *
 | |
| 	 * @param   integer  $filterId  The id of static filter.
 | |
| 	 *
 | |
| 	 * @return  boolean  True on success, false on failure.
 | |
| 	 *
 | |
| 	 * @since   2.5
 | |
| 	 * @throws  Exception on database error.
 | |
| 	 */
 | |
| 	protected function processStaticTaxonomy($filterId)
 | |
| 	{
 | |
| 		// Get the database object.
 | |
| 		$db = JFactory::getDbo();
 | |
| 
 | |
| 		// Initialize user variables
 | |
| 		$user = JFactory::getUser();
 | |
| 		$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);
 | |
| 
 | |
| 		$db->setQuery($query);
 | |
| 		$return = $db->loadObject();
 | |
| 
 | |
| 		// Check the returned filter.
 | |
| 		if (empty($return))
 | |
| 		{
 | |
| 			return false;
 | |
| 		}
 | |
| 
 | |
| 		// Set the filter.
 | |
| 		$this->filter = (int) $filterId;
 | |
| 
 | |
| 		// Get a parameter object for the filter date options.
 | |
| 		$registry = new JRegistry;
 | |
| 		$registry->loadString($return->params);
 | |
| 		$params = $registry;
 | |
| 
 | |
| 		// 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);
 | |
| 		$filters = array_unique($filters);
 | |
| 		JArrayHelper::toInteger($filters);
 | |
| 
 | |
| 		// Remove any values of zero.
 | |
| 		if (array_search(0, $filters, true) !== false)
 | |
| 		{
 | |
| 			unset($filters[array_search(0, $filters, true)]);
 | |
| 		}
 | |
| 
 | |
| 		// Check if we have any real input.
 | |
| 		if (empty($filters))
 | |
| 		{
 | |
| 			return 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->clear()
 | |
| 			->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.state = 1')
 | |
| 			->where('t1.access IN (' . $groups . ')')
 | |
| 			->where('t1.id IN (' . implode(',', $filters) . ')')
 | |
| 			->where('t2.state = 1')
 | |
| 			->where('t2.access IN (' . $groups . ')');
 | |
| 
 | |
| 		// Load the filters.
 | |
| 		$db->setQuery($query);
 | |
| 		$results = $db->loadObjectList();
 | |
| 
 | |
| 		// Sort the filter ids by branch.
 | |
| 		foreach ($results as $result)
 | |
| 		{
 | |
| 			$this->filters[$result->branch][$result->title] = (int) $result->id;
 | |
| 		}
 | |
| 
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * 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
 | |
| 	 * taxonomy filter.
 | |
| 	 *
 | |
| 	 * @param   array  $filters  An array of taxonomy node ids.
 | |
| 	 *
 | |
| 	 * @return  boolean  True on success.
 | |
| 	 *
 | |
| 	 * @since   2.5
 | |
| 	 * @throws  Exception on database error.
 | |
| 	 */
 | |
| 	protected function processDynamicTaxonomy($filters)
 | |
| 	{
 | |
| 		// Initialize user variables
 | |
| 		$user = JFactory::getUser();
 | |
| 		$groups = implode(',', $user->getAuthorisedViewLevels());
 | |
| 
 | |
| 		// Remove duplicates and sanitize.
 | |
| 		$filters = array_unique($filters);
 | |
| 		JArrayHelper::toInteger($filters);
 | |
| 
 | |
| 		// Remove any values of zero.
 | |
| 		if (array_search(0, $filters, true) !== false)
 | |
| 		{
 | |
| 			unset($filters[array_search(0, $filters, true)]);
 | |
| 		}
 | |
| 
 | |
| 		// Check if we have any real input.
 | |
| 		if (empty($filters))
 | |
| 		{
 | |
| 			return true;
 | |
| 		}
 | |
| 
 | |
| 		// Get the database object.
 | |
| 		$db = JFactory::getDbo();
 | |
| 		$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.state = 1')
 | |
| 			->where('t1.access IN (' . $groups . ')')
 | |
| 			->where('t1.id IN (' . implode(',', $filters) . ')')
 | |
| 			->where('t2.state = 1')
 | |
| 			->where('t2.access IN (' . $groups . ')');
 | |
| 
 | |
| 		// Load the filters.
 | |
| 		$db->setQuery($query);
 | |
| 		$results = $db->loadObjectList();
 | |
| 
 | |
| 		// Cleared filter branches.
 | |
| 		$cleared = array();
 | |
| 
 | |
| 		/*
 | |
| 		 * 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))
 | |
| 			{
 | |
| 				// Clear the branch.
 | |
| 				$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;
 | |
| 		}
 | |
| 
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Method to process the query date filters to determine start and end
 | |
| 	 * date limitations.
 | |
| 	 *
 | |
| 	 * @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.
 | |
| 	 *
 | |
| 	 * @since   2.5
 | |
| 	 */
 | |
| 	protected function processDates($date1, $date2, $when1, $when2)
 | |
| 	{
 | |
| 		// Clean up the inputs.
 | |
| 		$date1 = JString::trim(JString::strtolower($date1));
 | |
| 		$date2 = JString::trim(JString::strtolower($date2));
 | |
| 		$when1 = JString::trim(JString::strtolower($when1));
 | |
| 		$when2 = JString::trim(JString::strtolower($when2));
 | |
| 
 | |
| 		// Get the time offset.
 | |
| 		$offset = JFactory::getApplication()->getCfg('offset');
 | |
| 
 | |
| 		// Array of allowed when values.
 | |
| 		$whens = array('before', 'after', 'exact');
 | |
| 
 | |
| 		// The value of 'today' is a special case that we need to handle.
 | |
| 		if ($date1 === JString::strtolower(JText::_('COM_FINDER_QUERY_FILTER_TODAY')))
 | |
| 		{
 | |
| 			$today = JFactory::getDate('now', $offset);
 | |
| 			$date1 = $today->format('%Y-%m-%d');
 | |
| 		}
 | |
| 
 | |
| 		// Try to parse the date string.
 | |
| 		$date = JFactory::getDate($date1, $offset);
 | |
| 
 | |
| 		// Check if the date was parsed successfully.
 | |
| 		if ($date->toUnix() !== null)
 | |
| 		{
 | |
| 			// Set the date filter.
 | |
| 			$this->date1 = $date->toSQL();
 | |
| 			$this->when1 = in_array($when1, $whens) ? $when1 : 'before';
 | |
| 		}
 | |
| 
 | |
| 		// The value of 'today' is a special case that we need to handle.
 | |
| 		if ($date2 === JString::strtolower(JText::_('COM_FINDER_QUERY_FILTER_TODAY')))
 | |
| 		{
 | |
| 			$today = JFactory::getDate('now', $offset);
 | |
| 			$date2 = $today->format('%Y-%m-%d');
 | |
| 		}
 | |
| 
 | |
| 		// Try to parse the date string.
 | |
| 		$date = JFactory::getDate($date2, $offset);
 | |
| 
 | |
| 		// Check if the date was parsed successfully.
 | |
| 		if ($date->toUnix() !== null)
 | |
| 		{
 | |
| 			// Set the date filter.
 | |
| 			$this->date2 = $date->toSQL();
 | |
| 			$this->when2 = in_array($when2, $whens) ? $when2 : 'before';
 | |
| 		}
 | |
| 
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * 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.
 | |
| 	 *
 | |
| 	 * @since   2.5
 | |
| 	 * @throws  Exception on database error.
 | |
| 	 */
 | |
| 	protected function processString($input, $lang, $mode)
 | |
| 	{
 | |
| 		// Clean up the input string.
 | |
| 		$input = html_entity_decode($input, ENT_QUOTES, 'UTF-8');
 | |
| 		$input = JString::strtolower($input);
 | |
| 		$input = preg_replace('#\s+#mi', ' ', $input);
 | |
| 		$input = JString::trim($input);
 | |
| 		$debug = JFactory::getConfig()->get('debug_lang');
 | |
| 
 | |
| 		/*
 | |
| 		 * 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.
 | |
| 		 */
 | |
| 		$patterns = array(
 | |
| 			'before' => JText::_('COM_FINDER_FILTER_WHEN_BEFORE'),
 | |
| 			'after' => JText::_('COM_FINDER_FILTER_WHEN_AFTER')
 | |
| 		);
 | |
| 
 | |
| 		// Add the taxonomy branch titles to the possible patterns.
 | |
| 		foreach (FinderIndexerTaxonomy::getBranchTitles() as $branch)
 | |
| 		{
 | |
| 			// Add the pattern.
 | |
| 			$patterns[$branch] = JString::strtolower(JText::_(FinderHelperLanguage::branchSingular($branch)));
 | |
| 		}
 | |
| 
 | |
| 		// Container for search terms and phrases.
 | |
| 		$terms = array();
 | |
| 		$phrases = array();
 | |
| 
 | |
| 		// Cleared filter branches.
 | |
| 		$cleared = array();
 | |
| 
 | |
| 		/*
 | |
| 		 * 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.
 | |
| 		 */
 | |
| 		$quotes = html_entity_decode('‘’'', ENT_QUOTES, 'UTF-8');
 | |
| 		$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
 | |
| 		 * to be valid.
 | |
| 		 */
 | |
| 		foreach ($patterns as $modifier => $pattern)
 | |
| 		{
 | |
| 			$matches = array();
 | |
| 
 | |
| 			if ($debug)
 | |
| 			{
 | |
| 				$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.
 | |
| 				switch ($modifier)
 | |
| 				{
 | |
| 					// Handle a before and after date filters.
 | |
| 					case 'before':
 | |
| 					case 'after':
 | |
| 					{
 | |
| 						// Get the time offset.
 | |
| 						$offset = JFactory::getApplication()->getCfg('offset');
 | |
| 
 | |
| 						// Array of allowed when values.
 | |
| 						$whens = array('before', 'after', 'exact');
 | |
| 
 | |
| 						// The value of 'today' is a special case that we need to handle.
 | |
| 						if ($value === JString::strtolower(JText::_('COM_FINDER_QUERY_FILTER_TODAY')))
 | |
| 						{
 | |
| 							$today = JFactory::getDate('now', $offset);
 | |
| 							$value = $today->format('%Y-%m-%d');
 | |
| 						}
 | |
| 
 | |
| 						// Try to parse the date string.
 | |
| 						$date = JFactory::getDate($value, $offset);
 | |
| 
 | |
| 						// Check if the date was parsed successfully.
 | |
| 						if ($date->toUnix() !== null)
 | |
| 						{
 | |
| 							// Set the date filter.
 | |
| 							$this->date1 = $date->toSQL();
 | |
| 							$this->when1 = in_array($modifier, $whens) ? $modifier : 'before';
 | |
| 						}
 | |
| 
 | |
| 						break;
 | |
| 					}
 | |
| 
 | |
| 					// Handle a taxonomy branch filter.
 | |
| 					default:
 | |
| 						{
 | |
| 						// Try to find the node id.
 | |
| 						$return = FinderIndexerTaxonomy::getNodeByTitle($modifier, $value);
 | |
| 
 | |
| 						// Check if the node id was found.
 | |
| 						if ($return)
 | |
| 						{
 | |
| 							// Check if the branch has been cleared.
 | |
| 							if (!in_array($modifier, $cleared))
 | |
| 							{
 | |
| 								// Clear the branch.
 | |
| 								$this->filters[$modifier] = array();
 | |
| 
 | |
| 								// Add the branch to the cleared list.
 | |
| 								$cleared[] = $modifier;
 | |
| 							}
 | |
| 
 | |
| 							// Add the filter to the list.
 | |
| 							$this->filters[$modifier][$return->title] = (int) $return->id;
 | |
| 						}
 | |
| 
 | |
| 						break;
 | |
| 						}
 | |
| 				}
 | |
| 
 | |
| 				// Clean up the input string again.
 | |
| 				$input = str_replace($matches[0], '', $input);
 | |
| 				$input = preg_replace('#\s+#mi', ' ', $input);
 | |
| 				$input = JString::trim($input);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		/*
 | |
| 		 * Extract the tokens enclosed in double quotes so that we can handle
 | |
| 		 * them as phrases.
 | |
| 		 */
 | |
| 		if (JString::strpos($input, '"') !== false)
 | |
| 		{
 | |
| 			$matches = array();
 | |
| 
 | |
| 			// Extract the tokens enclosed in double quotes.
 | |
| 			if (preg_match_all('#\"([^"]+)\"#mi', $input, $matches))
 | |
| 			{
 | |
| 				/*
 | |
| 				 * 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.
 | |
| 					$pos = JString::strpos($input, $matches[0][$key]);
 | |
| 					$len = JString::strlen($matches[0][$key]);
 | |
| 
 | |
| 					// Add any terms that are before this phrase to the stack.
 | |
| 					if (JString::trim(JString::substr($input, 0, $pos)))
 | |
| 					{
 | |
| 						$terms = array_merge($terms, explode(' ', JString::trim(JString::substr($input, 0, $pos))));
 | |
| 					}
 | |
| 
 | |
| 					// Strip out everything up to and including the phrase.
 | |
| 					$input = JString::substr($input, $pos + $len);
 | |
| 
 | |
| 					// Clean up the input string again.
 | |
| 					$input = preg_replace('#\s+#mi', ' ', $input);
 | |
| 					$input = JString::trim($input);
 | |
| 
 | |
| 					// Get the number of words in the phrase.
 | |
| 					$parts = explode(' ', $match);
 | |
| 
 | |
| 					// Check if the phrase is longer than three words.
 | |
| 					if (count($parts) > 3)
 | |
| 					{
 | |
| 						/*
 | |
| 						 * 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
 | |
| 						 * of it.
 | |
| 						 */
 | |
| 						for ($i = 0, $c = count($parts); $i < $c; $i += 2)
 | |
| 						{
 | |
| 							// Set up the chunk.
 | |
| 							$chunk = array();
 | |
| 
 | |
| 							// The chunk has to be assembled based on how many
 | |
| 							// pieces are available to use.
 | |
| 							switch ($c - $i)
 | |
| 							{
 | |
| 								/*
 | |
| 								 * 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
 | |
| 								 * chunk.
 | |
| 								 */
 | |
| 								case 1:
 | |
| 									break 2;
 | |
| 
 | |
| 								// If there words are left, we use them both as
 | |
| 								// the last chunk of the phrase and we're done.
 | |
| 								case 2:
 | |
| 									$chunk[] = $parts[$i];
 | |
| 									$chunk[] = $parts[$i + 1];
 | |
| 									break;
 | |
| 
 | |
| 								// If there are three or more words left, we
 | |
| 								// build a three word chunk and continue on.
 | |
| 								default:
 | |
| 									$chunk[] = $parts[$i];
 | |
| 									$chunk[] = $parts[$i + 1];
 | |
| 									$chunk[] = $parts[$i + 2];
 | |
| 									break;
 | |
| 							}
 | |
| 
 | |
| 							// If the chunk is not empty, add it as a phrase.
 | |
| 							if (count($chunk))
 | |
| 							{
 | |
| 								$phrases[] = implode(' ', $chunk);
 | |
| 								$terms[] = implode(' ', $chunk);
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 					else
 | |
| 					{
 | |
| 						// The phrase is <= 3 words so we can use it as is.
 | |
| 						$phrases[] = $match;
 | |
| 						$terms[] = $match;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Add the remaining terms if present.
 | |
| 		if (!empty($input))
 | |
| 		{
 | |
| 			$terms = array_merge($terms, explode(' ', $input));
 | |
| 		}
 | |
| 
 | |
| 		// An array of our boolean operators. $operator => $translation
 | |
| 		$operators = array(
 | |
| 			'AND' => JString::strtolower(JText::_('COM_FINDER_QUERY_OPERATOR_AND')),
 | |
| 			'OR' => JString::strtolower(JText::_('COM_FINDER_QUERY_OPERATOR_OR')),
 | |
| 			'NOT' => JString::strtolower(JText::_('COM_FINDER_QUERY_OPERATOR_NOT'))
 | |
| 		);
 | |
| 
 | |
| 		// If language debugging is enabled you need to ignore the debug strings in matching.
 | |
| 		if (JDEBUG)
 | |
| 		{
 | |
| 			$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.
 | |
| 				$op = array_search($terms[$i + 1], $operators);
 | |
| 
 | |
| 				// Handle the AND operator.
 | |
| 				if ($op === 'AND' && isset($terms[$i + 2]))
 | |
| 				{
 | |
| 					// Tokenize the current term.
 | |
| 					$token = FinderIndexerHelper::tokenize($terms[$i], $lang, true);
 | |
| 					$token = $this->getTokenData($token);
 | |
| 
 | |
| 					// Set the required flag.
 | |
| 					$token->required = true;
 | |
| 
 | |
| 					// Add the current token to the stack.
 | |
| 					$this->included[] = $token;
 | |
| 					$this->highlight = array_merge($this->highlight, array_keys($token->matches));
 | |
| 
 | |
| 					// Skip the next token (the mode operator).
 | |
| 					$this->operators[] = $terms[$i + 1];
 | |
| 
 | |
| 					// Tokenize the term after the next term (current plus two).
 | |
| 					$other = FinderIndexerHelper::tokenize($terms[$i + 2], $lang, true);
 | |
| 					$other = $this->getTokenData($other);
 | |
| 
 | |
| 					// Set the required flag.
 | |
| 					$other->required = true;
 | |
| 
 | |
| 					// Add the token after the next token to the stack.
 | |
| 					$this->included[] = $other;
 | |
| 					$this->highlight = array_merge($this->highlight, array_keys($other->matches));
 | |
| 
 | |
| 					// Remove the processed phrases if possible.
 | |
| 					if (($pk = array_search($terms[$i], $phrases)) !== false)
 | |
| 					{
 | |
| 						unset($phrases[$pk]);
 | |
| 					}
 | |
| 					if (($pk = array_search($terms[$i + 2], $phrases)) !== false)
 | |
| 					{
 | |
| 						unset($phrases[$pk]);
 | |
| 					}
 | |
| 
 | |
| 					// Remove the processed terms.
 | |
| 					unset($terms[$i]);
 | |
| 					unset($terms[$i + 1]);
 | |
| 					unset($terms[$i + 2]);
 | |
| 
 | |
| 					// Adjust the loop.
 | |
| 					$i += 2;
 | |
| 					continue;
 | |
| 				}
 | |
| 				// Handle the OR operator.
 | |
| 				elseif ($op === 'OR' && isset($terms[$i + 2]))
 | |
| 				{
 | |
| 					// Tokenize the current term.
 | |
| 					$token = FinderIndexerHelper::tokenize($terms[$i], $lang, true);
 | |
| 					$token = $this->getTokenData($token);
 | |
| 
 | |
| 					// Set the required flag.
 | |
| 					$token->required = false;
 | |
| 
 | |
| 					// Add the current token to the stack.
 | |
| 					if (count($token->matches))
 | |
| 					{
 | |
| 						$this->included[] = $token;
 | |
| 						$this->highlight = array_merge($this->highlight, array_keys($token->matches));
 | |
| 					}
 | |
| 					else
 | |
| 					{
 | |
| 						$this->ignored[] = $token;
 | |
| 					}
 | |
| 
 | |
| 					// Skip the next token (the mode operator).
 | |
| 					$this->operators[] = $terms[$i + 1];
 | |
| 
 | |
| 					// Tokenize the term after the next term (current plus two).
 | |
| 					$other = FinderIndexerHelper::tokenize($terms[$i + 2], $lang, true);
 | |
| 					$other = $this->getTokenData($other);
 | |
| 
 | |
| 					// Set the required flag.
 | |
| 					$other->required = false;
 | |
| 
 | |
| 					// Add the token after the next token to the stack.
 | |
| 					if (count($other->matches))
 | |
| 					{
 | |
| 						$this->included[] = $other;
 | |
| 						$this->highlight = array_merge($this->highlight, array_keys($other->matches));
 | |
| 					}
 | |
| 					else
 | |
| 					{
 | |
| 						$this->ignored[] = $other;
 | |
| 					}
 | |
| 
 | |
| 					// Remove the processed phrases if possible.
 | |
| 					if (($pk = array_search($terms[$i], $phrases)) !== false)
 | |
| 					{
 | |
| 						unset($phrases[$pk]);
 | |
| 					}
 | |
| 					if (($pk = array_search($terms[$i + 2], $phrases)) !== false)
 | |
| 					{
 | |
| 						unset($phrases[$pk]);
 | |
| 					}
 | |
| 
 | |
| 					// Remove the processed terms.
 | |
| 					unset($terms[$i]);
 | |
| 					unset($terms[$i + 1]);
 | |
| 					unset($terms[$i + 2]);
 | |
| 
 | |
| 					// Adjust the loop.
 | |
| 					$i += 2;
 | |
| 					continue;
 | |
| 				}
 | |
| 			}
 | |
| 			// Handle an orphaned OR operator.
 | |
| 			elseif (isset($terms[$i + 1]) && array_search($terms[$i], $operators) === 'OR')
 | |
| 			{
 | |
| 				// Skip the next token (the mode operator).
 | |
| 				$this->operators[] = $terms[$i];
 | |
| 
 | |
| 				// Tokenize the next term (current plus one).
 | |
| 				$other = FinderIndexerHelper::tokenize($terms[$i + 1], $lang, true);
 | |
| 				$other = $this->getTokenData($other);
 | |
| 
 | |
| 				// Set the required flag.
 | |
| 				$other->required = false;
 | |
| 
 | |
| 				// Add the token after the next token to the stack.
 | |
| 				if (count($other->matches))
 | |
| 				{
 | |
| 					$this->included[] = $other;
 | |
| 					$this->highlight = array_merge($this->highlight, array_keys($other->matches));
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					$this->ignored[] = $other;
 | |
| 				}
 | |
| 
 | |
| 				// Remove the processed phrase if possible.
 | |
| 				if (($pk = array_search($terms[$i + 1], $phrases)) !== false)
 | |
| 				{
 | |
| 					unset($phrases[$pk]);
 | |
| 				}
 | |
| 
 | |
| 				// Remove the processed terms.
 | |
| 				unset($terms[$i]);
 | |
| 				unset($terms[$i + 1]);
 | |
| 
 | |
| 				// Adjust the loop.
 | |
| 				$i += 1;
 | |
| 				continue;
 | |
| 			}
 | |
| 			// Handle the NOT operator.
 | |
| 			elseif (isset($terms[$i + 1]) && array_search($terms[$i], $operators) === 'NOT')
 | |
| 			{
 | |
| 				// Skip the next token (the mode operator).
 | |
| 				$this->operators[] = $terms[$i];
 | |
| 
 | |
| 				// Tokenize the next term (current plus one).
 | |
| 				$other = FinderIndexerHelper::tokenize($terms[$i + 1], $lang, true);
 | |
| 				$other = $this->getTokenData($other);
 | |
| 
 | |
| 				// Set the required flag.
 | |
| 				$other->required = false;
 | |
| 
 | |
| 				// Add the next token to the stack.
 | |
| 				if (count($other->matches))
 | |
| 				{
 | |
| 					$this->excluded[] = $other;
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					$this->ignored[] = $other;
 | |
| 				}
 | |
| 
 | |
| 				// Remove the processed phrase if possible.
 | |
| 				if (($pk = array_search($terms[$i + 1], $phrases)) !== false)
 | |
| 				{
 | |
| 					unset($phrases[$pk]);
 | |
| 				}
 | |
| 
 | |
| 				// Remove the processed terms.
 | |
| 				unset($terms[$i]);
 | |
| 				unset($terms[$i + 1]);
 | |
| 
 | |
| 				// Adjust the loop.
 | |
| 				$i += 1;
 | |
| 				continue;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		/*
 | |
| 		 * 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++)
 | |
| 		{
 | |
| 			// Tokenize the phrase.
 | |
| 			$token = FinderIndexerHelper::tokenize($phrases[$i], $lang, true);
 | |
| 			$token = $this->getTokenData($token);
 | |
| 
 | |
| 			// Set the required flag.
 | |
| 			$token->required = true;
 | |
| 
 | |
| 			// Add the current token to the stack.
 | |
| 			$this->included[] = $token;
 | |
| 			$this->highlight = array_merge($this->highlight, array_keys($token->matches));
 | |
| 
 | |
| 			// Remove the processed term if possible.
 | |
| 			if (($pk = array_search($phrases[$i], $terms)) !== false)
 | |
| 			{
 | |
| 				unset($terms[$pk]);
 | |
| 			}
 | |
| 
 | |
| 			// Remove the processed phrase.
 | |
| 			unset($phrases[$i]);
 | |
| 		}
 | |
| 
 | |
| 		/*
 | |
| 		 * Handle any remaining tokens using the standard processing mechanism.
 | |
| 		 */
 | |
| 		if (!empty($terms))
 | |
| 		{
 | |
| 			// Tokenize the terms.
 | |
| 			$terms = implode(' ', $terms);
 | |
| 			$tokens = FinderIndexerHelper::tokenize($terms, $lang, false);
 | |
| 
 | |
| 			// 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)
 | |
| 			{
 | |
| 				// Get the token data.
 | |
| 				$token = $this->getTokenData($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)
 | |
| 				{
 | |
| 					$this->included[] = $token;
 | |
| 					$this->highlight = array_merge($this->highlight, array_keys($token->matches));
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					$this->ignored[] = $token;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * 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.
 | |
| 	 *
 | |
| 	 * @since   2.5
 | |
| 	 * @throws  Exception on database error.
 | |
| 	 */
 | |
| 	protected function getTokenData($token)
 | |
| 	{
 | |
| 		// Get the database object.
 | |
| 		$db = JFactory::getDbo();
 | |
| 
 | |
| 		// 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.
 | |
| 		 */
 | |
| 		if ($token->phrase)
 | |
| 		{
 | |
| 			// Add the phrase to the query.
 | |
| 			$query->where('t.term = ' . $db->quote($token->term))
 | |
| 				->where('t.phrase = 1');
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			// Add the term to the query.
 | |
| 			$query->where('t.term = ' . $db->quote($token->term))
 | |
| 				->where('t.phrase = 0');
 | |
| 
 | |
| 			// Clone the query, replace the WHERE clause.
 | |
| 			$sub = clone($query);
 | |
| 			$sub->clear('where');
 | |
| 			$sub->where('t.stem = ' . $db->quote($token->stem));
 | |
| 			$sub->where('t.phrase = 0');
 | |
| 
 | |
| 			// Union the two queries.
 | |
| 			$query->union($sub);
 | |
| 		}
 | |
| 
 | |
| 		// Get the terms.
 | |
| 		$db->setQuery($query);
 | |
| 		$matches = $db->loadObjectList();
 | |
| 
 | |
| 		// Setup the container.
 | |
| 		$token->matches = array();
 | |
| 
 | |
| 		// Check the matching terms.
 | |
| 		if (!empty($matches))
 | |
| 		{
 | |
| 			// 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
 | |
| 			$query->clear()
 | |
| 				->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);
 | |
| 
 | |
| 			// Get the terms.
 | |
| 			$db->setQuery($query);
 | |
| 			$results = $db->loadObjectList();
 | |
| 
 | |
| 			// Check if any similar terms were found.
 | |
| 			if (empty($results))
 | |
| 			{
 | |
| 				return $token;
 | |
| 			}
 | |
| 
 | |
| 			// Stack for sorting the similar terms.
 | |
| 			$suggestions = array();
 | |
| 
 | |
| 			// Get the levnshtein distance for all suggested terms.
 | |
| 			foreach ($results as $sk => $st)
 | |
| 			{
 | |
| 				// Get the levenshtein distance between terms.
 | |
| 				$distance = levenshtein($st->term, $token->term);
 | |
| 
 | |
| 				// Make sure the levenshtein distance isn't over 50.
 | |
| 				if ($distance < 50)
 | |
| 				{
 | |
| 					$suggestions[$sk] = $distance;
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			// Sort the suggestions.
 | |
| 			asort($suggestions, SORT_NUMERIC);
 | |
| 
 | |
| 			// Get the closest match.
 | |
| 			$keys = array_keys($suggestions);
 | |
| 			$key = $keys[0];
 | |
| 
 | |
| 			// Add the suggested term.
 | |
| 			$token->suggestion = $results[$key]->term;
 | |
| 		}
 | |
| 
 | |
| 		return $token;
 | |
| 	}
 | |
| }
 | 
