Your IP : 216.73.216.43


Current Path : /home/cbequiperp/www/plugins/search/flexiadvsearch/
Upload File :
Current File : /home/cbequiperp/www/plugins/search/flexiadvsearch/flexiadvsearch.php

<?php
/**
 * @version 1.0 $Id$
 * @package Joomla
 * @subpackage FLEXIcontent
 * @copyright (C) 2011 flexicontent.org
 * @license GNU/GPL v3
 *
 * FLEXIcontent is a derivative work of the excellent QuickFAQ component
 * @copyright (C) 2008 Christoph Lukes
 * see www.schlu.net for more information
 *
 * FLEXIcontent is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

// no direct access
defined( '_JEXEC' ) or die( 'Restricted access' );

use Joomla\String\StringHelper;
use Joomla\Utilities\ArrayHelper;

jimport('cms.plugin.plugin');

require_once(JPATH_ADMINISTRATOR.DS.'components'.DS.'com_flexicontent'.DS.'defineconstants.php');

FLEXI_J40GE
	? require_once (JPATH_SITE.DS.'components'.DS.'com_content'.DS.'src'.DS.'Helper'.DS.'RouteHelper.php')
	: require_once (JPATH_SITE.DS.'components'.DS.'com_content'.DS.'helpers'.DS.'route.php');
require_once(JPATH_SITE.DS.'components'.DS.'com_flexicontent'.DS.'helpers'.DS.'route.php');
if (!FLEXI_J40GE) require_once(JPATH_ADMINISTRATOR.DS.'components'.DS.'com_search'.DS.'helpers'.DS.'search.php');

require_once(JPATH_SITE.DS.'components'.DS.'com_flexicontent'.DS.'classes'.DS.'flexicontent.fields.php');
require_once(JPATH_SITE.DS.'components'.DS.'com_flexicontent'.DS.'classes'.DS.'flexicontent.helper.php');

/**
 * Content Search plugin
 *
 * @package		FLEXIcontent.Plugin
 * @subpackage	Search.flexiadvsearch
 * @since		1.6
 */
class plgSearchFlexiadvsearch extends JPlugin
{
	var $autoloadLanguage = false;

	/**
	 * Constructor
	 *
	 * @access      public
	 * @param       object  $subject The object to observe
	 * @param       array   $config  An array that holds the plugin configuration
	 * @since       1.5
	 */
	public function __construct(& $subject, $config)
	{
		parent::__construct($subject, $config);

		static $language_loaded = null;
		if (!$this->autoloadLanguage && $language_loaded === null) $language_loaded = JPlugin::loadLanguage('plg_search_flexiadvsearch', JPATH_ADMINISTRATOR);

		// Get the COMPONENT only parameter
		$this->_params = new JRegistry();
		$this->_params->merge(JComponentHelper::getParams('com_flexicontent'));

		// Merge the active menu parameters
		$menu = JFactory::getApplication()->getMenu()->getActive();

		if ($menu)
		{
			$this->_params->merge($menu->getParams());
		}
	}


	/**
	* @return array An array of search areas
	*/
	public function onContentSearchAreas()
	{
		static $areas = array
		(
			'flexicontent' => 'FLEXICONTENT'
		);

		return $areas;
	}


	/**
	 * Search method
	 *
	 * The sql must return the following fields that are used in a common display routine:
	 *
	 *   href, title, section, created, text, browsernav
	 *
	 * @param string Target search string
	 * @param string matching option, natural|natural_expanded|exact|any|all
	 * @param string ordering option, newest|oldest|popular|alpha|category
	 * @param mixed An array if restricted to areas, null if search all
	 */
	public function onContentSearch($text, $phrase = '', $ordering = '', $areas = null)
	{
		// Initialize variables
		$app      = JFactory::getApplication();
		$jinput   = JFactory::getApplication()->input;

		$option = $jinput->getCmd('option', '');
		$view   = $jinput->getCmd('view', '');

		$db       = JFactory::getDbo();
		$user     = JFactory::getUser();

		$app->setUserState('fc_view_total_'.$view, 0);
		$app->setUserState('fc_view_limit_max_'.$view, 0);

		// Check if not requested search areas, inside this search areas of this plugin
		if ( is_array($areas) && !array_intersect($areas, array_keys($this->onContentSearchAreas())) )
		{
			return array();
		}


		// Get parameters
		$params  = $this->_params;

		// some parameter shortcuts for SQL query
		$show_noauth  = $params->get('show_noauth', 0);
		$orderby_override = $params->get('orderby_override', 0);
		$orderby_override_2nd = $params->get('orderby_override_2nd', 0);
		$search_prefix = $params->get('add_search_prefix') ? 'vvv' : '';


		/**
		 * Some parameter shortcuts common among search view and advanced search plugin
		 */

		$canseltypes = (int) $params->get('canseltypes', 1);
		$txtmode     = (int) $params->get('txtmode', 0);  // 0: BASIC Index, 1: ADVANCED Index without search fields user selection, 2: ADVANCED Index with search fields user selection

		// Get if text searching according to specific (single) content type
		$show_txtfields = (int) $params->get('show_txtfields', 1);  // 0: hide, 1: according to content, 2: use custom configuration
		$show_txtfields = !$txtmode ? 0 : $show_txtfields;  // disable this flag if using BASIC index for text search

		// Get if filtering according to specific (single) content type
		$show_filters   = (int) $params->get('show_filters', 1);  // 0: hide, 1: according to content, 2: use custom configuration

		// Force single type selection and showing the content type selector
		$type_based_search = $show_filters === 1 || $show_txtfields === 1;
		$canseltypes = $type_based_search ? 1 : $canseltypes;


		/**
		 * Get Content Types allowed for user selection in the Search Form
		 * Also retrieve their configuration, plus the currently selected types
		 */

		// Get them from configuration
		$contenttypes = $params->get('contenttypes', array(), 'array');

		// Sanitize them as integers and as an array
		$contenttypes = ArrayHelper::toInteger($contenttypes);

		// Make sure these are unique too
		$contenttypes = array_unique($contenttypes);

		// Check for zero content types (can occur during sanitizing content ids to integers)
		foreach($contenttypes as $i => $v)
		{
			if (!$contenttypes[$i])
			{
				unset($contenttypes[$i]);
			}
		}

		// Force hidden content type selection if only 1 content type was initially configured
		$canseltypes = count($contenttypes) === 1 ? 0 : $canseltypes;
		$params->set('canseltypes', $canseltypes);  // SET "type selection FLAG" back into parameters

		// Type data and configuration (parameters), if no content types specified then all will be retrieved
		$typeData = flexicontent_db::getTypeData($contenttypes);
		$contenttypes = array();

		foreach($typeData as $tdata)
		{
			$contenttypes[] = $tdata->id;
		}

		// Get Content Types to use either those currently selected in the Search Form, or those hard-configured in the search menu item
		if ($canseltypes)
		{
			// Get them from user request data
			$form_contenttypes = $jinput->get('contenttypes', array(), 'array');

			// Sanitize them as integers and as an array
			$form_contenttypes = ArrayHelper::toInteger($form_contenttypes);

			// Make sure these are unique too
			$form_contenttypes = array_unique($form_contenttypes);

			// Check for zero content type (can occur during sanitizing content ids to integers)
			foreach($form_contenttypes as $i => $v)
			{
				if (!$form_contenttypes[$i])
				{
					unset($form_contenttypes[$i]);
				}
			}

			// Limit to allowed item types (configuration) if this is empty
			$form_contenttypes = array_intersect($contenttypes, $form_contenttypes);

			// If we found some allowed content types then use them otherwise keep the configuration defaults
			if (!empty($form_contenttypes))
			{
				$contenttypes = $form_contenttypes;
			}
		}

		// Type based seach, get a single content type (first one, if more than 1 were given ...)
		if ($type_based_search && $canseltypes && !empty($form_contenttypes))
		{
			$single_contenttype = reset($form_contenttypes);
			$contenttypes = $form_contenttypes = array($single_contenttype);
		}
		else
		{
			$single_contenttype = false;
		}



		/**
		 * Text Search Fields of the search form
		 */

		if (!$txtmode)
		{
			$txtflds = array();
			$fields_text = array();
		}

		else
		{
			$txtflds = '';

			if ($show_txtfields === 1)
			{
				$txtflds = $single_contenttype
					? $typeData[$single_contenttype]->params->get('searchable', '')
					: '';
			}
			elseif ($show_txtfields)
			{
				$txtflds = $params->get('txtflds', '');
			}

			// Sanitize them
			$txtflds = preg_replace("/[\"'\\\]/u", "", $txtflds);
			$txtflds = array_unique(preg_split("/\s*,\s*/u", $txtflds));
			if ( !strlen($txtflds[0]) ) unset($txtflds[0]);

			// Create a comma list of them
			$txtflds_list = count($txtflds) ? "'".implode("','", $txtflds)."'" : '';

			// Retrieve field properties/parameters, verifying the support to be used as Text Search Fields
			// This will return all supported fields if field limiting list is empty
			$fields_text = FlexicontentFields::getSearchFields($key='id', $indexer='advanced', $txtflds_list, $contenttypes, $load_params=true, 0, 'search');

			// If all entries of field limiting list were invalid, get ALL
			if (empty($fields_text))
			{
				if (!empty($contenttypes))
				{
					$fields_text = FlexicontentFields::getSearchFields($key='id', $indexer='advanced', null, $contenttypes, $load_params=true, 0, 'search');
				}
				else
				{
					$fields_text = array();
				}
			}
		}


		/**
		 * Filter Fields of the search form
		 */

		// Get them from type configuration or from search menu item
		$filtflds = '';

		if ($show_filters === 1)
		{
			$filtflds = $single_contenttype
				? $typeData[$single_contenttype]->params->get('filters', '')
				: '';
		}
		elseif ($show_filters)
		{
			$filtflds = $params->get('filtflds', '');
		}

		// Sanitize them
		$filtflds = preg_replace("/[\"'\\\]/u", "", $filtflds);
		$filtflds = array_unique(preg_split("/\s*,\s*/u", $filtflds));

		foreach ($filtflds as $i => $v)
		{
			if (!$v)
			{
				unset($filtflds[$i]);
			}
		}

		// Create a comma list of them
		$filtflds_list = count($filtflds) ? "'" . implode("','", $filtflds) . "'" : '';


		/**
		 * Retrieve field properties/parameters, verifying they support to be used as Filter Fields
		 * This will return all supported fields if field limiting list is empty
		 */

		if (count($filtflds))
		{
			$filters_tmp = FlexicontentFields::getSearchFields($key='name', $indexer='advanced', $filtflds_list, $contenttypes, $load_params=true, 0, 'filter');

			// Use custom order
			$filters = array();

			if ($canseltypes && $show_filters)
			{
				foreach($filtflds as $field_name)
				{
					if (empty($filters_tmp[$field_name]))
					{
						continue;
					}

					$filter_id = $filters_tmp[$field_name]->id;
					$filters[$filter_id] = $filters_tmp[$field_name];
				}
			}

			else
			{
				// Index by filter_id in this case too (for consistency, although we do not use the array index ?)
				foreach( $filters_tmp as $filter)
				{
					$filters[$filter->id] = $filter;
				}
			}

			unset($filters_tmp);
		}


		/**
		 * If configured filters were either not found or were invalid for the current content type(s)
		 * then retrieve all fields marked as filterable for the give content type(s)
		 * this is useful to list per content type filters automatically, even when not set or misconfigured
		 */

		if (empty($filters))
		{
			// If filters are type based and a type was not selected yet, then do not set any filters
			if ($type_based_search && $canseltypes && empty($form_contenttypes))
			{
				$filters = array();
			}

			// Set filters according to currently used content types
			else
			{
				$filters = !empty($contenttypes)
					? FlexicontentFields::getSearchFields($key='id', $indexer='advanced', null, $contenttypes, $load_params=true, 0, 'filter')
					: array();
			}
		}


		/**
		 * Load Plugin parameters
		 */

		$plugin = JPluginHelper::getPlugin('search', 'flexiadvsearch');
		$pluginParams = new JRegistry($plugin->params);

		// Shortcuts for plugin parameters
		$search_limit    = $params->get( 'search_limit', $pluginParams->get( 'search_limit', 20 ) );      // Limits the returned results of this seach plugin
		$filter_lang     = $params->get( 'filter_lang', $pluginParams->get( 'filter_lang', 1 ) );         // Language filtering enabled
		$search_archived = $params->get( 'search_archived', $pluginParams->get( 'search_archived', 1 ) ); // Include archive items into the search
		$browsernav      = $params->get( 'browsernav', $pluginParams->get( 'browsernav', 2 ) );           // Open search in window (for value 1)


		// ***
		// *** Various other variable USED in the SQL query like (a) current frontend language and (b) -this- plugin specific ordering, (c) null / now dates, (d) etc
		// ***

		// Get current frontend language (fronted user selected)
		$lang = flexicontent_html::getUserCurrentLang();

		// NULL and CURRENT dates,
		// NOTE: the current date needs to use built-in MYSQL function, otherwise filter caching can not work because the CURRENT DATETIME is continuously different !!!
		//$now = JFactory::getDate()->toSql();
		$_nowDate = 'UTC_TIMESTAMP()'; //$db->Quote($now);
		$nullDate = $db->getNullDate();

		// Section name
		$searchFlexicontent = JText::_( 'FLEXICONTENT' );

		// REMOVED / COMMENTED OUT this feature:
		// Require any OR all Filters ... this can be user selectable
		//$show_filtersop = $params->get('show_filtersop', 1);
		//$default_filtersop = $params->get('default_filtersop', 'all');
		//$FILTERSOP = !$show_filtersop ? $default_filtersop : $app->input->getCmd('filtersop', $default_filtersop);


		// ***
		// *** Create WHERE clause part for Text Search
		// ***

		$select_relevance = array();
		$text_search = $this->_buildTextSearch($text, $phrase, $txtmode, $select_relevance);


		// ***
		// *** Create ORDER clause
		// ***

		// FLEXIcontent search view, use FLEXIcontent ordering
		$orderby_join = '';
		$orderby_col = '';
		if ($app->input->getCmd('option', '') == 'com_flexicontent')
		{
			// Get defaults
			$request_var = $orderby_override || $orderby_override_2nd ? 'orderby' : '';
			$default_order = $app->input->getCmd('filter_order', 'i.title');
			$default_order_dir = $app->input->getCmd('filter_order_Dir', 'ASC');

			$order = '';
			$orderby = flexicontent_db::buildItemOrderBy(
				$params,
				$order, $request_var, $_config_param='orderby',
				$_item_tbl_alias = 'i', $_relcat_tbl_alias = 'rel',
				$default_order, $default_order_dir, $sfx='', $support_2nd_lvl=true
			);

			// Create JOIN for ordering items by a custom field (Level 1)
			if ( 'field' == $order[1] )
			{
				$orderbycustomfieldid = (int)$params->get('orderbycustomfieldid', 0);
				$orderbycustomfieldint = (int)$params->get('orderbycustomfieldint', 0);
				if ($orderbycustomfieldint === 4)
				{
					$orderby_join .= '
						LEFT JOIN (
							SELECT rf.item_id, SUM(fdat.hits) AS file_hits
							FROM #__flexicontent_fields_item_relations AS rf
							LEFT JOIN #__flexicontent_files AS fdat ON fdat.id = rf.value
					 		WHERE rf.field_id='.$orderbycustomfieldid.'
					 		GROUP BY rf.item_id
					 	) AS dl ON dl.item_id = i.id';
				}
				else $orderby_join .= ' LEFT JOIN #__flexicontent_fields_item_relations AS f ON f.item_id = i.id AND f.field_id='.$orderbycustomfieldid;
			}

			if ( 'custom:' == substr($order[1], 0, 7) )
			{
				$order_parts = preg_split("/:/", $order[1]);
				$_field_id = (int) @ $order_parts[1];
				$_o_method = @ $order_parts[2];

				if ($_field_id && count($order_parts) === 4)
				{
					if ($_o_method=='file_hits')
					{
						$orderby_join .= '
							LEFT JOIN (
								SELECT rf.item_id, SUM(fdat.hits) AS file_hits
								FROM #__flexicontent_fields_item_relations AS rf
								LEFT JOIN #__flexicontent_files AS fdat ON fdat.id = rf.value
						 		WHERE rf.field_id='.$_field_id.'
						 		GROUP BY rf.item_id
						 	) AS dl ON dl.item_id = i.id';
					}
					else $orderby_join .= ' LEFT JOIN #__flexicontent_fields_item_relations AS f ON f.item_id = i.id AND f.field_id='.$_field_id;
				}
			}

			// Create JOIN for ordering items by a custom field (Level 2)
			if ( 'field' == $order[2] )
			{
				$orderbycustomfieldid_2nd = (int)$params->get('orderbycustomfieldid'.'_2nd', 0);
				$orderbycustomfieldint_2nd = (int)$params->get('orderbycustomfieldint'.'_2nd', 0);
				if ($orderbycustomfieldint_2nd === 4)
				{
					$orderby_join .= '
						LEFT JOIN (
							SELECT f2.item_id, SUM(fdat2.hits) AS file_hits2
							FROM #__flexicontent_fields_item_relations AS f2
							LEFT JOIN #__flexicontent_files AS fdat2 ON fdat2.id = f2.value
					 		WHERE f2.field_id='.$orderbycustomfieldid_2nd.'
					 		GROUP BY f2.item_id
					 	) AS dl2 ON dl2.item_id = i.id';
				}
				else $orderby_join .= ' LEFT JOIN #__flexicontent_fields_item_relations AS f2 ON f2.item_id = i.id AND f2.field_id='.$orderbycustomfieldid_2nd;
			}

			if ( 'custom:' == substr($order[2], 0, 7) )
			{
				$order_parts = preg_split("/:/", $order[2]);
				$_field_id = (int) @ $order_parts[1];
				$_o_method = @ $order_parts[2];

				if ($_field_id && count($order_parts) === 4)
				{
					if ($_o_method=='file_hits')
					{
						$orderby_join .= '
							LEFT JOIN (
								SELECT f2.item_id, SUM(fdat2.hits) AS file_hits2
								FROM #__flexicontent_fields_item_relations AS f2
								LEFT JOIN #__flexicontent_files AS fdat2 ON fdat2.id = f2.value
						 		WHERE f2.field_id='.$_field_id.'
						 		GROUP BY f2.item_id
						 	) AS dl2 ON dl2.item_id = i.id';
					}
					else $orderby_join .= ' LEFT JOIN #__flexicontent_fields_item_relations AS f2 ON f2.item_id = i.id AND f2.field_id='.$_field_id;
				}
			}

			// Create JOIN for ordering items by author's name
			if ( in_array('author', $order) || in_array('rauthor', $order) )
			{
				$orderby_col = '';
				$orderby_join .= ' LEFT JOIN #__users AS u ON u.id = i.created_by';
			}

			// Create JOIN for ordering items by a most commented
			if ( in_array('commented', $order) )
			{
				$orderby_col   = ', COUNT(DISTINCT com.id) AS comments_total';
				$orderby_join .= ' LEFT JOIN #__jcomments AS com ON com.object_id = i.id AND com.object_group="com_flexicontent" AND com.published="1"';
			}

			// Create JOIN for ordering items by a most rated
			if ( in_array('rated', $order) )
			{
				$rating_join = null;
				$orderby_col   = ', ' . flexicontent_db::buildRatingOrderingColumn($rating_join);
				$orderby_join .= ' LEFT JOIN ' . $rating_join;
			}

			// Create JOIN for ordering items by their ordering attribute (in item's main category)
			if ( in_array('order', $order) )
			{
				$orderby_join .= ' LEFT JOIN #__flexicontent_cats_item_relations AS rel ON rel.itemid = i.id AND rel.catid = i.catid';
			}
		}

		// non-FLEXIcontent search view, use general ordering of search plugins (this is a parameter passed to this onContentSearch() function)
		else
		{
			switch ( $ordering )
			{
				//case 'relevance': $orderby = ' ORDER BY score DESC, i.title ASC'; break;
				case 'oldest':   $orderby = 'i.created ASC'; break;
				case 'popular':  $orderby = 'i.hits DESC'; break;
				case 'alpha':    $orderby = 'i.title ASC'; break;
				case 'category': $orderby = 'c.title ASC, i.title ASC'; break;
				case 'newest':   $orderby = 'i.created DESC'; break;
				default:         $orderby = 'i.created DESC'; break;
			}
			$orderby = ' ORDER BY '. $orderby;
		}

		// Add relevance columns to ORDER BY and to SELECT clauses
		if ($select_relevance)
		{
			$priorities = array();
			$n = count($select_relevance);
			foreach ($select_relevance as $s)
			{
				$priorities['priority' . $n] = ' (' . $s . ') AS priority' . $n;
				$n--;
			}

			$select_relevance = ', ' . implode(', ', $priorities) ;
			$ord_priorities = implode(' DESC, ', array_keys($priorities)) . ' DESC, ';
			$orderby = str_replace( 'ORDER BY ', 'ORDER BY ' . $ord_priorities, $orderby);
		}
		else
		{
			$select_relevance = '';
		}


		// ***
		// *** Create JOIN clause and WHERE clause part for filtering by current (viewing) access level
		// ***

		$joinaccess	= '';
		$andaccess	= '';
		$select_access = '';

		// Extra access columns for main category and content type (item access will be added as 'access')
		$select_access .= ',  c.access as category_access, ty.access as type_access';

		if ( !$show_noauth )
		{
			// User not allowed to LIST unauthorized items
			$aid_arr = JAccess::getAuthorisedViewLevels($user->id);
			$aid_list = implode(",", $aid_arr);
			$andaccess .= ' AND ty.access IN (0,'.$aid_list.')';
			$andaccess .= ' AND  c.access IN (0,'.$aid_list.')';
			$andaccess .= ' AND  i.access IN (0,'.$aid_list.')';
			$select_access .= ', 1 AS has_access';
		}

		else
		{
			// Access Flags for: content type, main category, item
			$aid_arr = JAccess::getAuthorisedViewLevels($user->id);
			$aid_list = implode(",", $aid_arr);
			$select_access .= ', '
				.' CASE WHEN '
				.'  ty.access IN ('.$aid_list.') AND '
				.'   c.access IN ('.$aid_list.') AND '
				.'   i.access IN ('.$aid_list.') '
				.' THEN 1 ELSE 0 END AS has_access';
		}


		// ***
		// *** Create WHERE clause part for filtering by current active language, and current selected contend types ( !! although this is possible via a filter too ...)
		// ***

		$andlang = '';
		if (	$app->isClient('site') &&
					( FLEXI_FISH || (FLEXI_J16GE && $app->getLanguageFilter()) ) &&
					$filter_lang  // Language filtering enabled
		) {
			$andlang .= ' AND ( ie.language LIKE ' . $db->Quote( $lang .'%' ) . (FLEXI_J16GE ? ' OR ie.language="*" ' : '') . ' ) ';
		}

		// Filter by currently selected content types
		$andcontenttypes = count($contenttypes) ? ' AND ie.type_id IN ('. implode(",", $contenttypes) .') ' : '';


		// ***
		// *** Create the AND-WHERE clause parts for the currentl active Field Filters
		// ***

		$return_sql = 2;
		$filters_where = array();
		foreach($filters as $field)
		{
			// Get value of current filter, and SKIP it if value is EMPTY
			$filtervalue = $app->input->get('filter_'.$field->id, '', 'array');
			$empty_filtervalue_array  = is_array($filtervalue)  && !strlen(trim(implode('',$filtervalue)));
			$empty_filtervalue_string = !is_array($filtervalue) && !strlen(trim($filtervalue));
			if ($empty_filtervalue_array || $empty_filtervalue_string) continue;

			// Call field filtering of advanced search to find items matching the field filter (an SQL SUB-QUERY is returned)
			$field_filename = $field->iscore ? 'core' : $field->field_type;
			$filtered = FLEXIUtilities::call_FC_Field_Func($field_filename, 'getFilteredSearch', array( &$field, &$filtervalue, &$return_sql ));

			// An empty return value means no matching values were found
			$filtered = empty($filtered) ? ' AND 0 ' : $filtered;

			// A string mean a subquery was returned, while an array means that item ids we returned
			$filters_where[$field->id] = is_array($filtered) ?  ' AND i.id IN ('. implode(',', $filtered) .')' : $filtered;

			/*if ($filters_where[$field->id]) {
				echo "\n<br/>Filter:". $field->name ." : ";   print_r($filtervalue);
				echo "<br>".$filters_where[$field->id]."<br/>";
			}*/
		}
		//echo "\n<br/><br/>Filters Active: ". count($filters_where)."<br/>";
		//echo "<pre>"; print_r($filters_where);
		//exit;


		// ***
		// *** Create Filters JOIN clauses and AND-WHERE clause parts
		// ***

		// JOIN clause - USED - to limit returned 'text' to the text of TEXT-SEARCHABLE only fields ... (NOT shared with filters)
		if ( !$txtmode )
		{
			$onBasic_textsearch    = $text_search;
			$onAdvanced_textsearch = '';
			$join_textsearch = '';
			$join_textfields = '';
		}

		else
		{
			$onBasic_textsearch    = '';
			$onAdvanced_textsearch = $text_search;
			$join_textsearch = ' JOIN #__flexicontent_advsearch_index as ts ON ts.item_id = i.id '.(count($fields_text) ? 'AND ts.field_id IN ('. implode(',',array_keys($fields_text)) .')' : '');
			$join_textfields = ' JOIN #__flexicontent_fields as txtf ON txtf.id=ts.field_id';
		}

		// JOIN clauses ... (shared with filters)
		$join_clauses =  ''
			. ' JOIN #__categories AS c ON c.id = i.catid'
			. ' JOIN #__flexicontent_items_ext AS ie ON ie.item_id = i.id'
			. ' JOIN #__flexicontent_types AS ty ON ie.type_id = ty.id'
			;

		$join_clauses_with_text =  ''
			. ' JOIN #__categories AS c ON c.id = i.catid'
			. ' JOIN #__flexicontent_items_ext AS ie ON ie.item_id = i.id' . $onBasic_textsearch
			. ' JOIN #__flexicontent_types AS ty ON ie.type_id = ty.id'
			. ($text_search ?
				$join_textsearch . $onAdvanced_textsearch .
				$join_textfields : '');

		$catid = $jinput->getInt('cid', 0);
		$cids = array();

		if ($catid)
		{
			$cids[] = $catid;
			$query = 'SELECT id,lft,rgt FROM `#__categories` WHERE id = ' . (int) $catid;

			if ($cat = $db->setQuery($query)->loadObject())
			{
				$query = 'SELECT sub.id FROM `#__categories` as sub '
					. ' WHERE sub.lft >= ' . (int) $cat->lft . ' AND sub.rgt <= ' . (int) $cat->rgt . ' AND published = 1 ';

				$subs = $db->setQuery($query)->loadColumn();
				if (is_array($subs))
				{
					$subs = ArrayHelper::toInteger($subs);
					$cids = array_merge($cids, $subs);
				}
			}
		}

		// AND-WHERE sub-clauses ... (shared with filters)
		$where_conf = ' WHERE 1 '
			. (count($cids) > 0 ? ' AND i.catid IN (' . implode(',', $cids) . ')' : '')
			. ' AND i.state IN (1,-5'. ($search_archived ? ','.(FLEXI_J16GE ? 2:-1) :'' ) .') '
			. ' AND c.published = 1 '
			. ' AND ( i.publish_up IS NULL OR i.publish_up = '.$db->Quote($nullDate).' OR i.publish_up <= '.$_nowDate.' )'
			. ' AND ( i.publish_down IS NULL OR i.publish_down = '.$db->Quote($nullDate).' OR i.publish_down >= '.$_nowDate.' )'
			. $andaccess
			. $andlang
			. $andcontenttypes
			;

		// AND-WHERE sub-clauses for text search ... (shared with filters)
		$and_where_filters = count($filters_where) ? implode( " ", $filters_where) : '';


		// ***
		// *** Set variables used by filters creation mechanism
		// ***

		global $fc_searchview;
		$fc_searchview['join_clauses'] = $join_clauses;
		$fc_searchview['join_clauses_with_text'] = $join_clauses_with_text;
		$fc_searchview['where_conf_only'] = $where_conf;   // WHERE of the view (mainly configuration dependent)
		$fc_searchview['filters_where'] = $filters_where;  // WHERE of the filters
		$fc_searchview['search'] = $text_search;  // WHERE for text search
		$fc_searchview['params'] = $params; // view's parameters


		// ***
		// *** Execute search query.  NOTE this is skipped it if (a) no text-search and no (b) no filters are active
		// ***

		// Do not check for 'contentypes' this are based on configuration and not on form submitted data,
		// considering contenttypes or other configuration based parameters, will return all items on initial search view display !
		if ( !count($filters_where) && !strlen($text) /*&& !strlen($andcontenttypes)*/ ) return array();

		$print_logging_info = $params->get('print_logging_info');
		if ( $print_logging_info ) { global $fc_run_times; $start_microtime = microtime(true); }


		// ***
		// *** Overcome possible group concat limitation
		// ***

		$query="SET SESSION group_concat_max_len = 9999999";
		$db->setQuery($query);
		$db->execute();


		// ***
		// *** Get the items
		// ***

		$query = 'SELECT SQL_CALC_FOUND_ROWS i.id'
			. $orderby_col
			. $select_relevance
			. ' FROM #__flexicontent_items_tmp AS i'
			. $join_clauses_with_text
			. $orderby_join
			. $joinaccess
			. $where_conf
			. $and_where_filters
			. ' GROUP BY i.id '
			. $orderby
			;
		//echo "Adv search plugin main SQL query: ".nl2br($query)."<br/><br/>";

		// NOTE: The plugin will return a PRECONFIGURED limited number of results, the SEARCH VIEW to do the pagination, splicing (appropriately) the data returned by all search plugins
		try {
			// Get items, we use direct query because some extensions break the SQL_CALC_FOUND_ROWS, so let's bypass them (at this point it is OK)
			// *** Usage of FOUND_ROWS() will fail when (e.g.) Joom!Fish or Falang are installed, in this case we will be forced to re-execute the query ...
			// PLUS, we don't need Joom!Fish or Falang layer at --this-- STEP which may slow down the query considerably in large sites
			$query_limited = $query . ' LIMIT '.$search_limit.' OFFSET 0';
			$rows = flexicontent_db::directQuery($query_limited);
			$item_ids = array();
			foreach ($rows as $row) $item_ids[] = $row->id;

			// Get current items total for pagination
			$db->setQuery("SELECT FOUND_ROWS()");
			$fc_searchview['view_total'] = $db->loadResult();
			$app->setUserState('fc_view_total_'.$view, $fc_searchview['view_total']);
		}

		catch (Exception $e) {
			// Get items via normal joomla SQL layer
			$db->setQuery(str_replace('SQL_CALC_FOUND_ROWS', '', $query), 0, $search_limit);
			$item_ids = $db->loadColumn(0);
		}

		if ( !count($item_ids) ) return array();  // No items found


		// ***
		// *** Get the item data
		// ***

		$query_data = 'SELECT i.id, i.title AS title, i.created, i.id AS fc_item_id, i.access, ie.type_id, i.language'
			. ', c.title AS maincat_title, c.alias AS maincat_alias'  // Main category data
			. ( !$txtmode ?
				', ie.search_index AS text' :
				', GROUP_CONCAT(ts.search_index ORDER BY txtf.ordering ASC SEPARATOR \' \') AS text'
				)
			. ', CASE WHEN CHAR_LENGTH(i.alias) THEN CONCAT_WS(\':\', i.id, i.alias) ELSE i.id END as slug'
			. ', CASE WHEN CHAR_LENGTH(c.alias) THEN CONCAT_WS(\':\', c.id, c.alias) ELSE c.id END as categoryslug'
			. ', CONCAT_WS( " / ", '. $db->Quote($searchFlexicontent) .', c.title, i.title ) AS section'
			. $select_access
			. ' FROM #__flexicontent_items_tmp AS i'
			. $join_clauses     // without on-join for basic text search
			. $join_textsearch  // without on-join for advanced text search
			. $join_textfields  // we need the text searchable fields to do ordering of text search fields above (minor effect)
			//. $orderby_join
			//. $joinaccess
			. ' WHERE i.id IN ('.implode(',',$item_ids).') '
			. ' GROUP BY i.id '
			. ' ORDER BY FIELD(i.id, '. implode(',', $item_ids) .')'
		;
		//require_once(JPATH_SITE.DS.'components'.DS.'com_flexicontent'.DS.'librairies'.DS.'SqlFormatter'.DS.'SqlFormatter.php');
		//echo str_replace('PPP_', '#__', SqlFormatter::format(str_replace('#__', 'PPP_', $query)))."<br/>";

		$list = $db->setQuery($query_data)->loadObjectList();

		if ( $print_logging_info ) @$fc_run_times['search_query_runtime'] += round(1000000 * 10 * (microtime(true) - $start_microtime)) / 10;


		// ***
		// *** Create item links and other variables
		// ***

		//echo "<pre>"; print_r($list); echo "</pre>";
		if( $list )
		{
			if ( count($list) >= $search_limit )
				$app->setUserState('fc_view_limit_max_'.$view, $search_limit);

			$item_cats = FlexicontentFields::_getCategories($list);
			foreach($list as $key => $item)
			{
				$item->text = preg_replace('/\b'.$search_prefix.'/', '', $item->text);
				$item->categories = isset($item_cats[$item->id])  ?  $item_cats[$item->id] : array();  // in case of item categories missing

				// If joomla article view is allowed allowed and then search view may optional create Joomla article links
				if( $typeData[$item->type_id]->params->get('allow_jview', 0) == 1 && $typeData[$item->type_id]->params->get('search_jlinks', 1) )
				{
					$item->href = JRoute::_(ContentHelperRoute::getArticleRoute($item->slug, $item->categoryslug, $item->language));
				}
				else
				{
					$item->href = JRoute::_(FlexicontentHelperRoute::getItemRoute($item->slug, $item->categoryslug, 0, $item));
				}
				$item->browsernav = $browsernav;
			}
		}

		return $list;
	}


	/**
	 * Method to build the part of WHERE clause related to Alpha Index
	 *
	 * @access private
	 * @return array
	 */
	function _buildTextSearch($text = null, $phrase = null, $txtmode = 0, & $select_relevance = array())
	{
		$app    = JFactory::getApplication();
		$option = $app->input->getCmd('option', '');
		$db     = JFactory::getDbo();

		static $text_search = null;

		if ($text_search !== null)
		{
			return $text_search;
		}

		$text_search = '';


		/**
		 * Create query CLAUSE for Text Search
		 */

		if (!strlen($text))
		{
			$q = $app->input->getString('q', '');
			$q = $q !== parse_url(@$_SERVER["REQUEST_URI"], PHP_URL_PATH) ? $q : '';

			$original_text = $app->input->getString('filter', $q);
		}
		else
		{
			$original_text = $text;
		}

		// Set _relevant _active_* FLAG
		$this->_active_search = $text;

		// Text search using LIKE %word% (compatibility for language without spaces)
		$filter_word_like_any = (int) $this->_params->get('filter_word_like_any', 0);

		// Text search relevance [title] or [title, search index]
		$filter_word_relevance_order = (int) $this->_params->get('filter_word_relevance_order', 0);

		if ($phrase === null)
		{
			$default_searchphrase = $this->_params->get('default_searchphrase', 'all');
			$phrase = $app->input->get('searchphrase', $app->input->get('p', $default_searchphrase, 'word'), 'word');
		}

		$si_tbl = !$txtmode
			? 'flexicontent_items_ext'
			: 'flexicontent_advsearch_index';

		// Try to add space between words for current language using a dictionary
		$lang_handler = FlexicontentFields::getLangHandler(JFactory::getLanguage()->getTag());

		if ($lang_handler)
		{
			$text = implode(' ', $lang_handler->get_segment_array($clear_previous = true, trim($original_text)));
		}
		else
		{
			$text = trim($original_text);
		}

		// Prefix the words for short word / stop words matching
		$search_prefix = $this->_params->get('add_search_prefix') ? 'vvv' : '';
		$text_np = $text;
		$text = preg_replace('/(\b[^\s,\.]+\b)/u', $search_prefix.'$0', $text_np);

		// Split to words
		$words_np = preg_split('/\s\s*/u', $text_np);
		$words = preg_split('/\s\s*/u', $text);

		if (strlen($text))
		{
			$ts = !$txtmode ? 'ie' : 'ts';
			$escaped_text_np = $db->escape($text_np, true);
			$quoted_text_np  = $db->Quote($escaped_text_np, false);

			$escaped_text = $db->escape($text, true);
			$quoted_text  = $db->Quote($escaped_text, false);
			$exact_text   = $db->Quote('%' . $escaped_text . '%', false);

			$isprefix = $phrase !== 'exact';
			$stopwords = array();
			$shortwords = array();

			/*
			 * LIKE %word% search (needed by languages without spaces),
			 * (1) Skip / Ignore this for languages with spaces,
			 * (2) Skip / Ignore this if current language supports splitting via dictionary
			 */
			if ($filter_word_like_any
				&& in_array(flexicontent_html::getUserCurrentLang(), array('zh', 'jp', 'ja', 'th'))
				&& ! FlexicontentFields::getLangHandler(JFactory::getLanguage()->getTag(), $_hasHandlerOnly = true)
			)
			{
				$_index_match = ' LOWER ('.$ts.'.search_index) LIKE '.$db->Quote( '%'.$escaped_text.'%', false );
				$_title_relev = ' LOWER (i.title) LIKE '.$db->Quote( '%'.$escaped_text.'%', false );
			}

			/*
			 * FullText search
			 */
			else
			{
				if (!$search_prefix)
				{
					$words = flexicontent_db::removeInvalidWords($words, $stopwords, $shortwords, $si_tbl, 'search_index', $isprefix);
				}

				// TODO: Check this if using advanced search index we do not have stop words or too short words
				if ($si_tbl=='flexicontent_advsearch_index')
				{
					$words = array_merge($words, $stopwords, $shortwords);
					$stopwords = $shortwords = array();
				}

				// Abort if all words are stop-words or too short, we could try to execute a query that only contains a LIKE %...% , but it would be too slow
				if (empty($words))
				{
					return ' AND 0 = 1 ';
				}

				switch ($phrase)
				{
				case 'natural':
					$_index_match = ' MATCH ('.$ts.'.search_index) AGAINST ('.$quoted_text.' IN NATURAL LANGUAGE MODE) ';
					$_title_relev = ' MATCH (i.title) AGAINST ('.$quoted_text_np.' IN NATURAL LANGUAGE MODE) ';
					$_index_relev = ' MATCH ('.$ts.'.search_index) AGAINST ('.$quoted_text.' IN NATURAL LANGUAGE MODE) ';
					break;

				case 'natural_expanded':
					$_index_match = ' MATCH ('.$ts.'.search_index) AGAINST ('.$quoted_text.' WITH QUERY EXPANSION) ';
					$_title_relev = ' MATCH (i.title) AGAINST ('.$quoted_text_np.' WITH QUERY EXPANSION) ';
					$_index_relev = ' MATCH ('.$ts.'.search_index) AGAINST ('.$quoted_text.' WITH QUERY EXPANSION) ';
					break;

				case 'exact':
					// Speed optimization ... 2-level searching: first require ALL words via FULLTEXT index, then require exact text via LIKE %phrase%
					$newtext = '+' . implode(' +', $words);
					$escaped_text = $db->escape($newtext, true);
					$quoted_text  = $db->Quote($escaped_text, false);

					// Relevance by title, try to match (EXACT) words, via OR ignoring stop words and short words ...
					$newtext_np = implode(' ', $words_np);
					$escaped_text_np = $db->escape($newtext_np, true);
					$quoted_text_np  = $db->Quote($escaped_text_np, false);

					$_index_match = ' MATCH ('.$ts.'.search_index) AGAINST ('.$quoted_text.' IN BOOLEAN MODE) AND '.$ts.'.search_index LIKE '.$exact_text;
					$_title_relev = ' MATCH (i.title) AGAINST ('.$quoted_text_np.' IN BOOLEAN MODE) ';
					$_index_relev = ' MATCH ('.$ts.'.search_index) AGAINST ('.$quoted_text.' IN BOOLEAN MODE) ';
					break;

				case 'all':
					$nospace_languages = array('th-TH');

					$is_nospace_language = in_array(JFactory::getLanguage()->getTag(), $nospace_languages);

					// TODO check if not using the * for THAI is appropriate & needed
					$newtext = $is_nospace_language
						? '+' . implode( ' +', $words ) . ''  // This is worked for Thai language.
						: '+' . implode( '* +', $words ) . '*';  // This is not worked for Thai language.

					$escaped_text = $db->escape($newtext, true);
					$quoted_text  = $db->Quote($escaped_text, false);

					// Relevance by title, try to match (PREFIXED) words, via OR ignoring stop words and short words ...
					$newtext_np = implode( '* ', $words_np ) . '*';
					$escaped_text_np = $db->escape($newtext_np, true);
					$quoted_text_np  = $db->Quote($escaped_text_np, false);

					if ($is_nospace_language)
					{
						// Text nLH (no-Language-Handler, aka NO SPACES added to guess and seperate words)
						$text_nLH = trim($original_text);  
						$escaped_text_nLH = $db->escape($text_nLH, true);
						$quoted_text_nLH  = $db->Quote($escaped_text_nLH, false);

						$_index_match = " (MATCH (".$ts.".search_index) AGAINST (".$quoted_text." IN BOOLEAN MODE) OR i.title LIKE '%".$quoted_text_nLH."%') ";
						$_title_relev = " (MATCH (i.title) AGAINST (".$quoted_text_np." IN BOOLEAN MODE) OR i.title LIKE '%".$quoted_text_nLH."%') ";
						$_index_relev = " (MATCH (".$ts.".search_index) AGAINST (".$quoted_text." IN BOOLEAN MODE) OR i.title LIKE '%".$quoted_text_nLH."%') ";
					}
					else
					{
						$_index_match = ' MATCH ('.$ts.'.search_index) AGAINST ('.$quoted_text.' IN BOOLEAN MODE) ';
						$_title_relev = ' MATCH (i.title) AGAINST ('.$quoted_text_np.' IN BOOLEAN MODE) ';
						$_index_relev = ' MATCH ('.$ts.'.search_index) AGAINST ('.$quoted_text.' IN BOOLEAN MODE) ';
					}
					break;

				case 'any':
				default:
					$newtext = implode( '* ', $words ) . '*';
					$escaped_text = $db->escape($newtext, true);
					$quoted_text  = $db->Quote($escaped_text, false);

					// Relevance by title, try to match (PREFIXED) words, via OR ignoring stop words and short words ...
					$newtext_np = implode( '* ', $words_np ) . '*';
					$escaped_text_np = $db->escape($newtext_np, true);
					$quoted_text_np  = $db->Quote($escaped_text_np, false);

					$_index_match = ' MATCH ('.$ts.'.search_index) AGAINST ('.$quoted_text.' IN BOOLEAN MODE) ';
					$_title_relev = ' MATCH (i.title) AGAINST ('.$quoted_text_np.' IN BOOLEAN MODE) ';
					$_index_relev = ' MATCH ('.$ts.'.search_index) AGAINST ('.$quoted_text.' IN BOOLEAN MODE) ';
					break;
				}
			}

			// Title relevance clause, Search index relevance clause ... (currently search index relevance not DONE)
			if ($filter_word_relevance_order > 0)
			{
				$select_relevance['rel_title'] = $_title_relev;
			}

			if ($filter_word_relevance_order > 1)
			{
				$select_relevance['rel_index'] = $_index_relev;
			}

			// Indicate ignored words
			$app->input->set('ignoredwords', implode(' ', $stopwords));
			$app->input->set('shortwords', implode(' ', $shortwords));

			// Construct TEXT SEARCH limitation clause
			$text_search = ' AND '. $_index_match;
		}

		return $text_search;
	}
}