Your IP : 216.73.216.43


Current Path : /home/cbequiperp/www/components/com_flexicontent/classes/
Upload File :
Current File : /home/cbequiperp/www/components/com_flexicontent/classes/flexicontent.fields.php

<?php
/**
 * @package         FLEXIcontent
 * @version         3.3
 *
 * @author          Emmanuel Danan, Georgios Papadakis, Yannick Berges, others, see contributor page
 * @link            https://flexicontent.org
 * @copyright       Copyright © 2017, FLEXIcontent team, All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined( '_JEXEC' ) or die( 'Restricted access' );

use Joomla\String\StringHelper;
use Joomla\Utilities\ArrayHelper;
use Joomla\CMS\Plugin\PluginHelper;

// Include constants file
require_once (JPATH_ADMINISTRATOR.DS.'components'.DS.'com_flexicontent'.DS.'defineconstants.php');

class FlexicontentFields
{
	/**
	 * Function to render the field display variables for the given items
	 *
	 * @param 	int 		$item_id
	 * @return 	string  : the HTML of the item view, also the CSS / JS file would have been loaded
	 * @since 1.5
	 */
	static function renderFields( $item_per_field=true, $item_ids=array(), $field_names=array(), $view=FLEXI_ITEMVIEW, $methods=array(), $cfparams=array() )
	{
		JTable::addIncludePath(JPATH_ADMINISTRATOR.DS.'components'.DS.'com_flexicontent'.DS.'tables');
		require_once(JPATH_SITE.DS.'components'.DS.'com_flexicontent'.DS.'classes'.DS.'flexicontent.helper.php');


		// ***************************
		// Check if no data were given
		// ***************************

		if ( empty($item_ids) || empty($field_names) ) return false;

		// Get item data, needed for rendering fields
		$db = JFactory::getDbo();

		$unique_item_ids = array_unique(array_map('intval', $item_ids));
		$item_ids_list = implode("," , $unique_item_ids) ;

		$query = 'SELECT i.id, i.*, ie.*, '
			. ' 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'
			. ' FROM #__content AS i'
			. ' LEFT JOIN #__flexicontent_items_ext AS ie ON ie.item_id = i.id'
			. ' LEFT JOIN #__categories AS c ON c.id = i.catid'
			. ' WHERE i.id IN ('. $item_ids_list .')'
			//. ' GROUP BY i.id'
			;
		$db->setQuery($query);
		$items = $db->loadObjectList();
		if (empty($items)) return false;

		foreach ($items as $i => $item) $_item_id_map[$item->id] = & $items[$i];

		$return = array();
		foreach ($field_names as $i => $field_name)
		{
			$method = isset( $methods[$i] ) ? $methods[$i] : 'display';
			if ( $item_per_field )
			{
				if ( !isset( $_item_id_map[ $item_ids[$i] ] ) )  { /*echo "not found item: ".$item_ids[$i] ." <br/>";*/ continue;}

				// Render Display variable of Field for respective item
				$_item = & $_item_id_map[$item_ids[$i]];
				FlexicontentFields::getFieldDisplay($_item, $field_name, $values=null, $method, $view);

				// Add to return array
				$return[$_item->id][$field_name][$method] = isset($_item->fields[$field_name]->$method)
					? $_item->fields[$field_name]->$method
					: null;
			}
			else
			{
				// Render Display variable of Field for all items
				FlexicontentFields::getFieldDisplay($items, $field_name, $values=null, $method, $view);

				// Add to return array
				foreach ($items as $item)
				{
					$return[$item->id][$field_name][$method] = isset($item->fields[$field_name]->$method)
						? $item->fields[$field_name]->$method
						: null;
				}
			}
		}
		return $return;
	}


	/**
	 * Method to bind fields to an items object
	 *
	 * @access private
	 * @return object
	 * @since 1.5
	 */
	static function & getFields(&$_items, $view = FLEXI_ITEMVIEW, $params = null, $aid = false, $use_tmpl = true)
	{
		static $expired_cleaned = false;

		if (!$_items) return $_items;
		if (!is_array($_items))  $items = array( & $_items );  else  $items = & $_items ;

		$jinput    = JFactory::getApplication()->input;
		$user      = JFactory::getUser();
		$cparams   = JComponentHelper::getParams('com_flexicontent');
		$print_logging_info = $cparams->get('print_logging_info');

		if ( $print_logging_info ) {
			global $fc_run_times;
			$start_microtime = microtime(true);
		}

		// Calculate access for current user if it was not given or if given access is invalid
		$aid = is_array($aid) ? $aid : JAccess::getAuthorisedViewLevels($user->id);

		$vars = null;
		FlexicontentFields::getItemFields($items, $vars, $view, $aid);

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

		$_rendered = array();
		if ($params)  // NULL/empty parameters mean only retrieve field values
		{
			$always_create_fields_display = $cparams->get('always_create_fields_display',0);
			$request_view = $jinput->get('view', '', 'cmd');

			// CHECK if 'always_create_fields_display' enabled and create the display for all item's fields
			// *** This should be normally set to ZERO (never), to avoid a serious performance penalty !!!

			// 0: never, 1: always, 2: only in item view, 3: never unless in a template position,  this effects function:  renderPositions()
			if ($always_create_fields_display==1 || ($always_create_fields_display==2 && $request_view==FLEXI_ITEMVIEW && $view==FLEXI_ITEMVIEW) )
			{
				$field_names = array();
				foreach ($items as $i => $item)
				{
					if ($items[$i]->fields)
					{
						foreach ($items[$i]->fields as $field)
						{
							$values = isset($items[$i]->fieldvalues[$field->id]) ? $items[$i]->fieldvalues[$field->id] : array();
							$field 	= FlexicontentFields::renderField($items[$i], $field, $values, $method='display', $view);
							$field_names[$field->name] = 1;
						}
					}
				}
				foreach ($field_names as $field_name => $_ignore) {
					$_rendered['ALL'][$field_name] = 1;
				}
			}

			// Render field positions
			$items = FlexicontentFields::renderPositions($items, $view, $params, $use_tmpl, $_rendered);
		}
		return $items;
	}


	/**
	 * Method to get fields configuration data by field ids
	 *
	 * @access private
	 * @return object
	 * @since 3
	 */
	static function & indexFieldsByIds($fields, $item=null, $force=false)
	{
		if ( $item && !$force && isset($item->fieldsByIds) )  return $item->fieldsByIds;

		$byIds = array();
		foreach($fields as $_field)
		{
			$byIds[$_field->id] = $_field;
		}
		if ($item) $item->fieldsByIds = & $byIds;

		return $byIds;
	}


	/**
	 * Method to get fields data by field type
	 *
	 * @access public
	 * @return object
	 * @since 3
	 */
	static function getFieldsByType($field_types, $check_access=true, $index_col = 'id')
	{
		if (!count($field_types))
		{
			return array();
		}

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

		$field_types_quoted = array();

		foreach ($field_types as $field_type)
		{
			$field_types_quoted[] = $db->Quote($field_type);
		}

		// Field's has_access flag
		if ($check_access)
		{
			$aid_arr = JAccess::getAuthorisedViewLevels($user->id);
			$aid_list = implode(",", $aid_arr);
			$select_access = ', CASE WHEN fi.access IN (0,'.$aid_list.') THEN 1 ELSE 0 END AS has_access';
		}
		else
			$select_access = '';

		$query 	= 'SELECT fi.*'
			. $select_access
			. ' FROM #__flexicontent_fields AS fi'
			. ' WHERE fi.field_type IN ('.implode(",", $field_types_quoted).') '
			;
		$db->setQuery($query);
		$fields = $db->loadObjectList($index_col);

		return $fields;
	}


	/**
	 * Method to get fields configuration data by field ids
	 *
	 * @access private
	 * @return object
	 * @since 3
	 */
	static function & getFieldsByIds($field_ids, $check_access=true)
	{
		if (!count($field_ids))
		{
			$fields = array();
			return $fields;
		}

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

		$field_ids = ArrayHelper::toInteger($field_ids);

		// Field's has_access flag
		if ($check_access)
		{
			$aid_arr = JAccess::getAuthorisedViewLevels($user->id);
			$aid_list = implode(",", $aid_arr);
			$select_access = ', CASE WHEN fi.access IN (0,'.$aid_list.') THEN 1 ELSE 0 END AS has_access';
		}
		else
			$select_access = '';

		$query 	= 'SELECT fi.*'
			. $select_access
			. ' FROM #__flexicontent_fields AS fi'
			. ' WHERE fi.id IN ('.implode(",", $field_ids).') '
			;
		$db->setQuery($query);
		$fields = $db->loadObjectList('id');

		return $fields;
	}


	/**
	 * Method to get fields values data by field ids + item ids
	 *
	 * @access private
	 * @return object
	 * @since 3
	 */
	static function & getFieldValsById($field_ids, $item_ids, $version=0)
	{
		$db = JFactory::getDbo();
		$field_ids = ArrayHelper::toInteger($field_ids);
		$item_ids  = ArrayHelper::toInteger($item_ids);

		$query = 'SELECT item_id, field_id, value, valueorder, suborder'
			. ($version ? ' FROM #__flexicontent_items_versions':' FROM #__flexicontent_fields_item_relations')
			. ' WHERE item_id IN ('.implode(",", $item_ids).') '
			. ($field_ids ? ' AND field_id IN ('.implode(",", $field_ids).') ' : '')
			. ($version ? ' AND version=' . (int)$version:'')
			. ' AND value > "" '
			. ' ORDER BY field_id, valueorder, suborder'
			;
		$db->setQuery($query);
		$values = $db->loadObjectList();

		$fieldvalues = array();
		if ($values) foreach ($values as $v)
		{
			$fieldvalues[$v->item_id][$v->field_id][$v->valueorder - 1][$v->suborder - 1] = $v->value;
		}

		foreach ($fieldvalues as & $iv)
		{
			foreach ($iv as & $fv)
			{
				foreach ($fv as & $ov)
				{
					// Maybe examine parameters and avoid this if either of 'allow_multiple' , 'use_ingroup' is set
					if (count($ov) === 1)
					{
						$ov = reset($ov);
					}
				}
				unset($ov);
			}
			unset($fv);
		}
		unset($iv);

		return $fieldvalues;
	}


	/**
	 * Method to fetch the fields from an item object
	 *
	 * @access private
	 * @return object
	 * @since 1.5
	 */
	static function & getItemFields(&$items, &$vars=null, $view=FLEXI_ITEMVIEW, $aid=false)
	{
		if (empty($items))
		{
			return $items;
		}

		static $type_fields = array();

		$dispatcher = JEventDispatcher::getInstance();
		$db   = JFactory::getDbo();
		$user = JFactory::getUser();
		$nullDate = $db->getNulldate();

		// This is optimized regarding the use of SINGLE QUERY to retrieve the core item data
		if ($vars==null)
		{
			$vars['tags']       = FlexicontentFields::_getTags($items, $view);
			$vars['cats']       = FlexicontentFields::_getCategories($items, $view);
			$vars['favourites'] = FlexicontentFields::_getFavourites($items, $view);
			$vars['favoured']   = FlexicontentFields::_getFavoured($items, $view);
			$vars['authors']    = FlexicontentFields::_getAuthors($items, $view);
			$vars['modifiers']  = FlexicontentFields::_getModifiers($items, $view);
			$vars['typenames']  = FlexicontentFields::_getTypenames($items, $view);
			$vars['votes']      = FlexicontentFields::_getVotes($items, $view);
			$vars['custom']     = FlexicontentFields::_getCustomValues($items, $view);
		}

		foreach ($items as $i => $item)
		{
			if (!FLEXI_J16GE && $item->sectionid != FLEXI_SECTION) continue;

			$item_id = $item->id;

			$cats      = isset($vars['cats'][$item_id])      ? $vars['cats'][$item_id]             : array();
			$tags      = isset($vars['tags'][$item_id])      ? $vars['tags'][$item_id]             : array();
			$favourites= isset($vars['favourites'][$item_id])? $vars['favourites'][$item_id]->favs : 0;
			$favoured  = isset($vars['favoured'][$item_id])  ? $vars['favoured'][$item_id]->fav    : 0;
			$author    = isset($vars['authors'][$item_id])   ? $vars['authors'][$item_id]          : '';
			$modifier  = isset($vars['modifiers'][$item_id]) ? $vars['modifiers'][$item_id]        : '';
			$typename  = isset($vars['typenames'][$item_id]) ? $vars['typenames'][$item_id]        : '';
			$vote      = isset($vars['votes'][$item_id])     ? $vars['votes'][$item_id]            : '';
			$custom    = isset($vars['custom'][$item_id])    ? $vars['custom'][$item_id]           : array();

			if ( empty($item->type_id) )
			{
				if (JDEBUG) JFactory::getApplication()->enqueueMessage('Item with id: ' .$item->id. ' has empty type, please edit it and set a type', 'warning');
			}

			// ONCE per Content Item Type (skip this if item has no type)
			else if ( $item->type_id && !isset($type_fields[$item->type_id]) )
			{
				// Field's has_access flag
				$aid_arr = is_array($aid) ? $aid : JAccess::getAuthorisedViewLevels($user->id);
				$aid_list = implode(",", $aid_arr);
				$select_access = ', CASE WHEN fi.access IN (0,'.$aid_list.') THEN 1 ELSE 0 END AS has_access';

				$query 	= 'SELECT fi.*'
					. $select_access
					. ' FROM #__flexicontent_fields AS fi'
					. ' JOIN #__flexicontent_fields_type_relations AS ftrel ON ftrel.field_id = fi.id AND ftrel.type_id = '.$item->type_id
					. ' WHERE fi.published = 1'
					. ' GROUP BY fi.id'
					. ' ORDER BY ftrel.ordering, fi.ordering, fi.name'
					;
				$db->setQuery($query);
				$type_fields[$item->type_id] = $db->loadObjectList('name');
				//echo "<pre>";  print_r( array_keys($type_fields[$item->type_id]) ); exit;
			}

			// Add custom fields, if these were found
			$item->fields = array();
			if (!empty($type_fields[$item->type_id]))
			{
				foreach($type_fields[$item->type_id] as $field_name => $field_data)
				{
					$item->fields[$field_name] = clone($field_data);
				}
			}

			if (!isset($item->parameters))
			{
				try
				{
					$item->parameters = new JRegistry($item->attribs);
				}
				catch (Exception $e)
				{
					$item->parameters = flexicontent_db::check_fix_JSON_column('attribs', 'content', 'id', $item->id, $item->attribs);
				}
			}

			// Property 'params' is an alias of property 'parameters'
			$item->params = $item->parameters;

			$item->text  = $item->introtext . chr(13) . chr(13) . $item->fulltext;
			$item->tags  = $tags;
			$item->cats  = $cats;
			$item->favs  = $favourites;
			$item->fav   = $favoured;

			$item->creator 	= !empty($author->alias) ? $author->alias : (!empty($author->name) ? $author->name : '');
			$item->author		= & $item->creator;  // An alias ... of creator
			$item->modifier	= !empty($modifier->name)      ? $modifier->name  : $item->creator;   // If never modified, set modifier to be the creator
			$item->modified	= $item->modified != $nullDate ? $item->modified  : $item->created;   // If never modified, set modification date to be the creation date

			$item->cmail 		= !empty($author->email)     ? $author->email     : '' ;
			$item->cuname 	= !empty($author->username)  ? $author->username  : '' ;
			$item->mmail		= !empty($modifier->email)   ? $modifier->email   : $item->cmail;
			$item->muname		= !empty($modifier->muname)  ? $modifier->muname  : $item->cuname;

			$item->typename	= !empty($typename->name)    ? $typename->name 	: JText::_('Article');
			$item->vote			= !empty($vote) ? $vote : '';

			// some aliases to much CORE field names
			$item->categories    = & $item->cats;
			$item->favourites    = & $item->favs;
			$item->document_type = & $item->typename;
			$item->voting        = & $item->vote;

			// custom field values
			$item->fieldvalues = $custom;
		}

		return $items;
	}


	/*
	* Create editing HTML of a field
	*/
	static function getFieldFormDisplay($field, $item, $user)
	{
		// ***
		// *** Apply CONTENT TYPE customizations to CORE FIELDS, e.g a type-specific label & description
		// *** for CUSTOM fields only do basic initialization like language filtering on label & description
		// ***

		FlexicontentFields::loadFieldConfig($field, $item);

		if ($field->iscore)
		{

			// Special case: create MAINTEXT field (description field), by calling the display function of the textarea field (will also check for tabs)
			switch ($field->field_type)
			{
				case 'maintext':
					if (isset($item->item_translations))
					{
						$shortcode = substr($item->language ,0,2);

						foreach ($item->item_translations as $lang_id => $t)
						{
							if ($shortcode == $t->shortcode) continue;
							$field->name = array('jfdata',$t->shortcode,'text');
							$field->value[0] = html_entity_decode($t->fields->text->value, ENT_QUOTES, 'UTF-8');
							FLEXIUtilities::call_FC_Field_Func('textarea', 'onDisplayField', array(&$field, &$item) );
							$t->fields->text->tab_labels = $field->tab_labels;
							$t->fields->text->html = $field->html;
							unset( $field->tab_labels );
							unset( $field->html );
						}
					}

					// NOTE: We use the text created by the model and not the text retrieved by the CORE plugin code, which maybe overwritten with JoomFish/Falang data
					$field->name = 'text';
					$field->value[0] = $item->text; // do not decode special characters this was handled during saving !

					// Render the field's (form) HTML
					FLEXIUtilities::call_FC_Field_Func('textarea', 'onDisplayField', array(&$field, &$item) );
					break;

				default:
					// Render the field's (form) HTML (if implemented)
					FLEXIUtilities::call_FC_Field_Func('core', 'onDisplayField', array(&$field, &$item) );
					break;
			}

			return;
		}

		// Check field is assigned to current item type
		else if (!isset($item->fields[$field->name]))
		{
			$field->html = null; //array('<span class="alert alert-info">' . JText::_('FLEXI_NOT_ASSIGNED'). ' ' . JText::_('FLEXI_TO') . ' ' . JText::_('FLEXI_TYPE') . '</span>');
			return;
		}


		// ***
		// *** Create editing HTML of the field NOTE: this is DONE only for CUSTOM fields, since form
		// *** field html is created by the form itself for all CORE fields, (except for 'text' field)
		// ***

		// Check for field configured to be inside a field group and skip it
		if ($field->parameters->get('use_ingroup', 0) && empty($field->ingroup))
		{
			$field->formhidden = 3;
			return;
		}

		$is_editable = !$field->valueseditable || $user->authorise('flexicontent.editfieldvalues', 'com_flexicontent.field.' . $field->id);
		if ($is_editable)
		{
			FLEXIUtilities::call_FC_Field_Func($field->field_type, 'onDisplayField', array( &$field, &$item ));
			if ($field->untranslatable)
			{
				$msg = !isset($field->html) ?
					'<div class="fc_val_plg_not_fould_or_upublished_mssg alert alert-info fc-warning fc-iblock" style="margin:0 2px 6px 2px; max-width: unset;">'.JText::_( 'FLEXI_PLEASE_PUBLISH_THIS_PLUGIN' ).'</div>' :
					'<div class="fc_val_translation_shared_mssg alert alert-info fc-small fc-iblock" style="margin:0 2px 6px 2px; max-width: unset;">'. JText::_('FLEXI_FIELD_VALUE_IS_NON_TRANSLATABLE') . '</div>' ;

				if (!is_array($field->html))
				{
					$field->html = $msg .' <div class="fcclear"></div> '. $field->html;
				}
				else
				{
					foreach($field->html as $i => & $field_html)  $field->html[$i] = $msg .' <div class="fcclear"></div> '. $field_html;
					unset($field_html);
				}
			}
		}

		// Non-editable message only
		else if ($field->valueseditable==1)
		{
			$msg = '<div class="alert alert-info fc-small fc-iblock">' . JText::_($field->parameters->get('no_acc_msg_form') ? $field->parameters->get('no_acc_msg_form') : 'FLEXI_NO_ACCESS_LEVEL_TO_EDIT_FIELD') . '</div>';

			// Handle non-editable field inside fieldgroup
			if (!empty($field->ingroup))
			{
				$field->html = array();
				foreach($field->value as $i => $v) $field->html[$i]= $msg;
			}
			else
			{
				$field->html = $msg;
			}
		}

		// Non-editable message only + display values
		else if ($field->valueseditable==2)
		{
			FLEXIUtilities::call_FC_Field_Func($field->field_type, 'onDisplayFieldValue', array( &$field, $item ));

			$msg = '<div class="alert alert-info fc-small fc-iblock">' . JText::_($field->parameters->get('no_acc_msg_form') ? $field->parameters->get('no_acc_msg_form') : 'FLEXI_NO_ACCESS_LEVEL_TO_EDIT_FIELD') . '</div>';
			if (!is_array($field->display))
			{
				$field->html = $msg .' <div class="fcclear"></div> <div class="fc-non-editable-value">'. $field->display .'</div>';
			}
			else
			{
				$field->html = array();
				foreach($field->display as $i => & $field_display)  $field->html[$i] = $msg .' <div class="fcclear"></div> <div class="fc-non-editable-value">'. $field_display .'</div>';
				unset($field_display);
			}
		}

		else if ($field->valueseditable==3)
		{
			FLEXIUtilities::call_FC_Field_Func($field->field_type, 'onDisplayFieldValue', array( &$field, $item ));
			if (!is_array($field->display))
			{
				$field->html = '<div class="fc-non-editable-value">'. $field->display .'</div>';
			}
			else
			{
				$field->html = array();
				foreach($field->display as $i => & $field_display)  $field->html[$i] = '<div class="fc-non-editable-value">'. $field_display .'</div>';
				unset($field_display);
			}
		}

		else if ($field->valueseditable==4)
		{
			$field->html = '';
			$field->formhidden = 4;
		}
	}


	/**
	 * Method to render (display method) a field on demand and return the display
	 *
	 * @access public
	 * @return object
	 * @since 1.5.5
	 */
	static function getFieldDisplay(&$item_arr, $fieldname, $single_item_vals=null, $method='display', $view = FLEXI_ITEMVIEW)
	{
		// 1. Convert to array of items if not an array already
		if ( empty($item_arr) ) {
			$err_msg = __FUNCTION__."(): empty item data given";
			return $err_msg;
		}
		else if ( !is_array($item_arr) )
		{
			$items = array( & $item_arr );
		}
		else
		{
			$items = & $item_arr;
		}

		// 2. Make sure that fields have been created for all given items
		$_items = array();
		foreach ($items as $i => $item)  if (!isset($item->fields))  $_items[] = & $items[$i];
		if ( count($_items) )  FlexicontentFields::getFields($_items, $view);

		// 3. Check and create HTML display for the given field name
		$_method_html = array();
		foreach ($items as $item)
		{
			// Check if we have already created the display and skip current item
			if ( isset($item->onDemandFields[$fieldname]->{$method}) )
			{
				$_method_html[$item->id] = $item->onDemandFields[$fieldname]->{$method};
				continue;
			}

			// Find the field inside item
			foreach ($item->fields as $field)
			{
				if ( !empty($field->name) && $field->name==$fieldname ) break;
			}

			// Check for not found field, and skip it, this is either due to no access or wrong name ...
			$item->onDemandFields[$fieldname] = new stdClass();
			if ( empty($field->name) || $field->name!=$fieldname)
			{
				$item->onDemandFields[$fieldname]->label = '';
				$item->onDemandFields[$fieldname]->noaccess = true;
				$item->onDemandFields[$fieldname]->errormsg = 'field not assigned to this type of item or current user has no access';
				$item->onDemandFields[$fieldname]->{$method} = '';
				$_method_html[$item->id] = '';
				continue;
			}

			// Get field's values if they were custom values were not given
			if ( $single_item_vals!==null && count($items) == 1 ) {
				// $values is used only if rendering a single item
				$values = $single_item_vals;
			} else {
				$values = isset($item->fieldvalues[$field->id]) ? $item->fieldvalues[$field->id] : array();
			}

			// Set other field data like label and field itself !!!
			$item->onDemandFields[$fieldname]->label = $field->label;
			$item->onDemandFields[$fieldname]->noaccess = false;
			$item->onDemandFields[$fieldname]->field = & $field;

			// Render the (display) method of the field
			if (!isset($field->{$method}))
			{
				$field = FlexicontentFields::renderField($item, $field, $values, $method, $view);
			}
			if (!isset($field->{$method}))
			{
				$field->{$method} = '';
			}

			// Only cache the result, only if using no values were given, thus DB values were used
			if ( $single_item_vals===null )
			{
				$item->onDemandFields[$fieldname]->{$method} = $field->{$method};
				$_method_html[$item->id] = $field->{$method};
			}
		}

		// Return field(s) HTML (in case of multiple items this will be an array indexable by item ids
		if ( !is_array($item_arr) )
		{
	   // Not isset should occur only when fieldname was not found
			$_method_html = isset($_method_html[$item_arr->id]) ? $_method_html[$item_arr->id] : '';
		}
		return $_method_html;
	}



	/**
	 * Method to render a field
	 *
	 * @access public
	 * @return object
	 * @since 1.5
	 */
	static function renderField(&$_item, &$_field, &$values, $method='display', $view=FLEXI_ITEMVIEW, $skip_trigger_plgs = false, $event_row = false)
	{
		static $_trigger_plgs_ft = array();
		static $_created = array();
		$app = JFactory::getApplication();
		$request_view = $app->input->get('view', '', 'cmd');

		// Field's source code, can use this HTTP request variable, to detect who rendered the fields (e.g. they can detect rendering from 'module')
		$app->input->set('flexi_callview', $view);

		static $cparams = null;
		if ($cparams === null)
		{
			$cparams = JComponentHelper::getParams( 'com_flexicontent' );
		}

		static $aid;
		if ($aid === null)
		{
			$aid = JAccess::getAuthorisedViewLevels(JFactory::getUser()->id);
		}

		if (is_array($_item) && is_string($_field)) ;  // ok
		else if (is_object($_item) && is_object($_field)) ; // ok
		else
		{
			$field->$method = '<div class="alert alert-warning">renderField() must be called with: renderField(array of items, field_name, ...) or renderField(item object, field object, ...)</div>';
			return null;
		}

		// If $method (e.g. display method) is already created,
		// then return the field object without recreating the method
		if ( is_object($_field) && isset($_field->{$method}) ) return $_field;

		// Single item call
		if (!is_array($_item))
		{
			$all_items = array( & $_item );
			$field_name = $_field->name;
			$first_item_field = & $_field;
		}

		// Multi-item call
		else
		{
			$all_items = & $_item ;
			$field_name = $_field;

			$first_item_field = false;
			foreach($all_items as $item)
			{
				if (isset($item->fields[$field_name]))
				{
					$first_item_field = & $item->fields[$field_name];
					break;   // found break out, field found
				}
			}
			if (!$first_item_field) return null;  // none of the items has the field, return
		}


		// Skip items that have already created the given 'method' for this 'field' and for the given view
		// we also use VIEW so that we can reder different displays of the field e.g. item VIEW and module view
		$items = array();
		foreach ($all_items as $_item_)
		{
			// Commented out, TODO: examine if we can return cached value here !!
			//if (isset($_created[$view][$method][$field_name][$_item_->id])) continue;  // Skip this item
			$items[] = $_item_;
			$_created[$view][$method][$field_name][$_item_->id] = 1;
		}

		// Check if item array is empty (all items already rendered)
		if (empty($items))
		{
			return !is_object($_field) ? null : $_field;
		}


		// ***********************************************************************************************************
		// Create field parameters (and values) in an optimized way, and also apply Type Customization for CORE fields
		// ***********************************************************************************************************
		foreach($items as $item)
		{
			// Only rendering 1 item the field object was given, skip current item if it does not have the desired field
			if (is_object($_field))
				$field = $_field;
			else if ( isset($item->fields[$field_name]) )
				$field = $item->fields[$field_name];
			else
				continue;

			$field->item_id = (int)$item->id;  // Some code may make use of this

			// CHECK IF only rendering single field object for a single item  -->  thus we need to use custom values if these were given !
			// NOTE: values are overwritten by onDisplayCoreFieldValue() of CORE fields, and only used by onDisplayFieldValue() of CUSTOM fields

			// CUSTOM VALUEs give for single field rendering, TODO (maybe): in future we may make values an array indexed by item ID
			if ( is_object($_field) && $values!==null )
			{
				$field->value = $values;
			}

			// CUSTOM VALUEs not given or rendering multiple items
			else if (!isset($field->value))
			{
				$field->value = isset($item->fieldvalues[$field->id]) ? $item->fieldvalues[$field->id] : array();
			}

			FlexicontentFields::loadFieldConfig($field, $item);
		}


		// **********************************************
		// Return no access message if user has no ACCESS
		// **********************************************

		// Calculate has_access flag if it is missing ... FLEXI_ACCESS ... no longer supported here ...
		if ( !isset($first_item_field->has_access) ) {
			$first_item_field->has_access = in_array($first_item_field->access, $aid);
		}
		if ( !$first_item_field->has_access )
		{
			// Get configuration out of the field of the first item, any CONFIGURATION that is different
			// per content TYPE, must not use this, instead it must be retrieved inside the item loops
			$show_acc_msg = $first_item_field->parameters->get('show_acc_msg', 0);
			$no_acc_msg = $first_item_field->parameters->get('no_acc_msg');
			$no_acc_msg = JText::_( $no_acc_msg ? $no_acc_msg : 'FLEXI_FIELD_NO_ACCESS');

			static $login_link = null;

			if ($show_acc_msg == 2 && $login_link === null)
			{
				if (JFactory::getUser()->guest)
				{
					$uri      = JUri::getInstance();
					$return   = strtr(base64_encode($uri->toString()), '+/=', '-_,');          // Current URL as return URL (but we will for id / cid)
					$fcreturn = serialize( array('id' => $app->input->getInt('id'), 'cid' => $app->input->getInt('cid')) );  // a special url parameter, used by some SEF code
					$login_link = JComponentHelper::getParams( 'com_flexicontent' )->get('login_page', 'index.php?option=com_users&view=login')
						. '&return='.$return
						. '&fcreturn='.base64_encode($fcreturn);
				}
				else
				{
					$login_link = '';
				}
			}

			$no_acc_msg .= $show_acc_msg == 2
				? '<a class="fc_no_field_access_login_link" href="' . $login_link . '">' . JText::_('FLEXI_LOGIN_FIRST') . '</a>'
				: '';

			foreach($items as $item)
			{
				// Only rendering 1 item the field object was given, skip current item if it does not have the desired field
				if (is_object($_field))
					$field = $_field;
				else if ( isset($item->fields[$field_name]) )
					$field = $item->fields[$field_name];
				else
					continue;

				// Only add no access message if field has a value
				if (!empty($field->value))
					$field->$method = $show_acc_msg ? '<span class="fc-noauth fcfield_inaccessible_'.$field->id.'">'.$no_acc_msg.'</span>' : '';
				else
					$field->$method = '';
			}

			// Return field only if single item was given (with a field object)
			return !is_object($_field) ? null : $_field;
		}


		// ***************************************************************************************************
		// Create field HTML by calling the appropriate DISPLAY-CREATING field plugin method.
		// NOTE 1: We will not pass the 'values' method parameter to the display-creating field method,
		//         instead we have set it above as the 'value' field property
		// NOTE 2: For CUSTOM fields the 'values' method parameter is prefered over the 'value' field property
		//         For CORE field, both the above ('values' method parameter and 'value' field property) are
		//         ignored and instead the other method parameters are used, along with the ITEM properties
		// ****************************************************************************************************
		// Log content plugin and other performance information

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

		if ($first_item_field->iscore == 1)  // CORE field
		{
			//$results = $dispatcher->trigger('onDisplayCoreFieldValue', array( &$_field, $_item, &$_item->parameters, $_item->tags, $_item->cats, $_item->favs, $_item->fav, $_item->vote ));
			//FLEXIUtilities::call_FC_Field_Func('core', 'onDisplayCoreFieldValue', array( &$_field, & $_item, &$_item->parameters, $_item->tags, $_item->cats, $_item->favs, $_item->fav, $_item->vote, null, $method ) );
			$items_params = null;
			FLEXIUtilities::call_FC_Field_Func('core', 'onDisplayCoreFieldValue', array( &$_field, &$items, &$items_params, false, false, false, false, false, null, $method ) );
		}
		else                      // NON CORE field
		{
			// DOES NOT support multiple items YET, do it 1 at a time
			foreach($items as $item)
			{
				// Only rendering 1 item the field object was given, skip current item if it does not have the desired field
				if (is_object($_field))
					$field = $_field;
				else if ( isset($item->fields[$field_name]) )
					$field = $item->fields[$field_name];
				else
					continue;

				//$results = $dispatcher->trigger('onDisplayFieldValue', array( &$field, $item ));
				FLEXIUtilities::call_FC_Field_Func($field->field_type, 'onDisplayFieldValue', array(&$field, $item, null, $method) );
				if ($field->parameters->get('use_ingroup', 0) && empty($field->ingroup) && is_array($field->$method)) $field->$method = implode('', $field->$method);
			}
		}
		if ($print_logging_info) {
			$field_render_time = round(1000000 * 10 * (microtime(true) - $start_microtime)) / 10;
			if ( isset($fc_run_times['render_subfields'][$item->id."_".$field->id]) ) {
				$field_render_time = $field_render_time - $fc_run_times['render_subfields'][$item->id."_".$field->id];
				@$fc_run_times['render_subfields'][$field->field_type] += $fc_run_times['render_subfields'][$item->id."_".$field->id];
				unset($fc_run_times['render_subfields'][$item->id."_".$field->id]);
			}
			@$fc_run_times['render_field'][$field->field_type] += $field_render_time;
		}


		// *****************************************
		// Trigger content plugins on the field text
		// *****************************************

		$skip_trigger_plgs = $method === 'csv_export' ? true : $skip_trigger_plgs;

		if ( !$skip_trigger_plgs && !isset($_trigger_plgs_ft[$field_name]) )
		{
			$_t = $first_item_field->parameters->get('trigger_onprepare_content', 0);
			if ($request_view=='category' && $view=='category') $_t = $_t && $first_item_field->parameters->get('trigger_plgs_incatview', 1);
			$_trigger_plgs_ft[$field_name] = $_t;
		}

		// DOES NOT support multiple items, do it 1 at a time
		if ( !$skip_trigger_plgs && $_trigger_plgs_ft[$field_name] )
		{
			foreach($items as $item)
			{
				// Only rendering 1 item the field object was given, skip current item if it does not have the desired field
				if (is_object($_field))
					$field = $_field;
				else if ( isset($item->fields[$field_name]) )
					$field = $item->fields[$field_name];
				else
					continue;

				if ($print_logging_info)  $start_microtime = microtime(true);
				FlexicontentFields::triggerContentPlugins($field, $item, $method, $view, $event_row);
				if ( $print_logging_info ) @$fc_run_times['content_plg'] += round(1000000 * 10 * (microtime(true) - $start_microtime)) / 10;
			}
		}

		// Return field only if single item was given (with a field object)
		return !is_object($_field) ? null : $_field;
	}


	/**
	 * Method to selectively trigger content plugins for the text of the specified field
	 *
	 * @access public
	 * @return object
	 * @since 1.5
	 */
	static function triggerContentPlugins(&$field, &$item, $method, $view=FLEXI_ITEMVIEW, $event_row = false)
	{
		$debug = false;
		static $_plgs_loaded = array();
		static $_fields_plgs = array();

		static $_initialize = false;
		static $_layout, $_view, $_option;
		static $dispatcher, $fcdispatcher;

		$jinput = JFactory::getApplication()->input;

		//$flexiparams = JComponentHelper::getParams('com_flexicontent');
		//$print_logging_info = $flexiparams->get('print_logging_info');
		// Log content plugin and other performance information
		//if ($print_logging_info) 	global $fc_run_times;

		if (!$_initialize)
		{
			// Include com_content helper files, these are needed by some content plugins
			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');
			FLEXI_J40GE
				? require_once (JPATH_SITE.DS.'components'.DS.'com_content'.DS.'src'.DS.'Helper'.DS.'QueryHelper.php')
				: require_once (JPATH_SITE.DS.'components'.DS.'com_content'.DS.'helpers'.DS.'query.php');

			// some request and other variables
			$_layout = $jinput->get('layout', '', 'cmd');
			$_view   = $jinput->get('view', '', 'cmd');
			$_option = $jinput->get('option', '', 'cmd');
			$_initialize = true;

			// ***********************************************************************
			// We use a custom Dispatcher to allow selective Content Plugin triggering
			// ***********************************************************************
			require_once (JPATH_SITE.DS.'components'.DS.'com_flexicontent'.DS.'helpers'.DS.'dispatcher.php');
			$dispatcher   = JEventDispatcher::getInstance();
			$fcdispatcher = FCDispatcher::getInstance_FC($debug);
		}

		// Use limitstart only for maintext core field
		$is_maintext = $field->iscore && $field->field_type === 'maintext';
		$limitstart = $is_maintext
			? $jinput->get('limitstart', 0, 'int')
			: null;

		// CASE: FLEXIcontent item view:
		// Set triggering 'context' to 'com_content.article', (and also set the 'view' request variable)
		if ($view == 'item')
		{
			$jinput->set('view', 'article');
		  $context = 'com_content.article';

			// Needed by legacy non-updated plugins
			!FLEXI_J40GE ? JRequest::setVar('view', 'article') : null;
		}

		// ALL OTHER CASES: (FLEXIcontent category, FLEXIcontent module, etc),
		// Set triggering 'context' to 'com_content.category', (and also set the 'view' request variable)
		else
		{
			$jinput->set('layout', 'blog');
			$jinput->set('view', 'category');
		  $context = 'com_content.category';

			// Needed by legacy non-updated plugins
			!FLEXI_J40GE ? JRequest::setVar('view', 'category') : null;
		}

		// Set the 'option' to 'com_content' but set a flag 'isflexicontent' to indicate triggering from inside FLEXIcontent ... code
		$jinput->set('option', 'com_content');
		$jinput->set('isflexicontent', 'yes');
		$jinput->set('layout_fc', $_layout);

		// Needed by legacy non-updated plugins
		!FLEXI_J40GE ? JRequest::setVar('option', 'com_content') : null;


		if ($debug) echo "<br><br>Executing plugins for <b>".$field->name."</b>:<br>";

		if (empty($_fields_plgs[$field->name]))
		{
			// Make sure the necessary plugin are already loaded, but do not try to load them again since this will harm performance
			if (!$field->parameters->get('plugins'))
			{
				$_plgs = null;

				if (empty($_plgs_loaded['__ALL__']))
				{
					JPluginHelper::importPlugin('content', $plugin = null, $autocreate = true, $dispatcher);
					$_plgs_loaded['__ALL__'] = 1;
				}
			}
			else
			{
				$_plgs = $field->parameters->get('plugins');
				$_plgs = $_plgs ? $_plgs : array();

				// Compatibility because old versions did not JSON encode the parameters
				$_plgs = is_array($_plgs) ? $_plgs : explode('|', $_plgs);

				if (empty($_plgs_loaded['__ALL__']))
				{
					foreach ($_plgs as $_plg)
					{
						if (empty($_plgs_loaded[$_plg]))
						{
							JPluginHelper::importPlugin('content', $_plg, $autocreate = true, $dispatcher);
							$_plgs_loaded[$_plg] = 1;
						}
					}
				}
			}

			$_fields_plgs[$field->name] = $_plgs;
		}

		$plg_arr = $_fields_plgs[$field->name];


		/**
		 * Create record object to be used for plugin triggering
		 */

		// A row was given by 3rd party extension, use its data !
		if ($event_row)
		{
			$record = clone($event_row);
		}

		// Create a new record object
		else
		{
			$record = new stdClass;
		}


		/**
		 * Initialize Record object  to be used for plugin triggering
		 */

		// Field's display HTML will be used as text of plugin triggering
		$record->text = isset($field->{$method}) ? $field->{$method} : '';

		// Needed by some plugins that do not use or clear ->text property
		$record->introtext = $record->text;

		// Some plugins expect this
		if (isset($item->readmore_link))
		{
			$record->readmore_link = $item->readmore_link;
		}


		/**
		 * Set needed record properties, expected by plugins
		 */
		$record->title = $item->title;
		$record->language = $item->language;
		$record->created_by = $item->created_by;

		$record->id = $item->id;
		$record->slug = isset($item->slug)
			? $item->slug
			: (isset($record->slug) ? $record->slug : $item->id);

		$record->catid = $item->catid;
		$record->catslug = isset($item->categoryslug)
			? $item->categoryslug
			: (isset($record->catslug) ? $record->catslug : $item->catid);

		$record->state = $item->state;
		$record->access = $item->access;
		$record->sectionid = 0;

		$record->fieldid = $field->id;
		$record->type_id = $item->type_id;


		/**
		 * Trigger content plugins on field's HTML display, as if they were a "joomla article"
		 */
		if (FLEXI_J40GE) PluginHelper::importPlugin('content');
		$results = FLEXI_J40GE
			? JFactory::getApplication()->triggerEvent('onContentPrepare', array($context, &$record, &$item->parameters, $limitstart))
			: $fcdispatcher->trigger('onContentPrepare', array ($context, &$record, &$item->parameters, $limitstart), $plg_arr);

		// Get pluing triggering result
		$field->{$method} = $record->text;


		/**
		 * Restore state
		 */

		// Restore 'layout', 'view' and 'option' request variables
		$jinput->set('layout', $_layout);
		$jinput->set('view', $_view);
		$jinput->set('option', $_option);

		// Needed by legacy non-updated plugins
		!FLEXI_J40GE ? JRequest::setVar('layout', $_layout) : null;
		!FLEXI_J40GE ? JRequest::setVar('view', $_view) : null;
		!FLEXI_J40GE ? JRequest::setVar('option', $_option) : null;
	}


	/**
	 * Method to get the fields in their positions
	 *
	 * @access private
	 * @return object
	 * @since 1.5
	 */
	static function &renderPositions(&$items, $view = FLEXI_ITEMVIEW, $params = null, $use_tmpl = true, & $_rendered = array())
	{
		if (!$items) return;
		if (!$params) return $items;

		if ($view == 'category')			$layout = 'clayout';
		if ($view == FLEXI_ITEMVIEW)	$layout = 'ilayout';

		$app = JFactory::getApplication();
		$request_view = $app->input->get('view', '', 'cmd');

		// Field's source code, can use this HTTP request variable, to detect who rendered the fields (e.g. they can detect rendering from 'module')
		$app->input->set('flexi_callview', $view);

		if ( $use_tmpl && ($view == 'category' || $view == FLEXI_ITEMVIEW) )
		{
		  $fbypos = flexicontent_tmpl::getFieldsByPositions($params->get($layout, 'grid'), $view);
		  //$onDemandOnly = false;
		}

		// $view == 'module', or other
		else {
			// Create a fake template position, for fields defined via parameters
		  $fbypos[0] = new stdClass();
		  $fbypos[0]->fields = explode(',', $params->get('fields', ''));
		  $fbypos[0]->methods = explode(',', $params->get('methods', ''));
		  $fbypos[0]->position = $view;
		  //$onDemandOnly = true;
		}

		$always_create_fields_display = $params->get('always_create_fields_display',0);

		// Render some fields by default, this is done for compatibility reasons, but avoid rendering these fields again (2nd time),
		// since some of them may also be in template positions. NOTE: this is not needed since renderField() should detect this case
		if ( /*!$onDemandOnly &&*/  $always_create_fields_display != 3) { // value 3 means never create for any view (blog template incompatible)

			$item = reset($items); // get the first item ... so that we can get the name of CORE fields out of it

		  // 'description' item field is implicitly used by category layout of some templates (blog), render it
		  $custom_values = null;
		  if ($view == 'category') {
		    if (isset($item->fields['text']) && !isset($_rendered['ALL']['text'])) {
		    	$_field_name_ = 'text';
		    	FlexicontentFields::renderField($items, $_field_name_, $custom_values, $method='display', $view);
		    }
		    $_rendered['ALL']['text'] = 1;
		  }
			// 'core' item fields are IMPLICITLY used by some item layout of some templates (blog), render them
			else if ($view == FLEXI_ITEMVIEW) {
				foreach ($item->fields as $field) {
					if ($field->iscore && !isset($_rendered['ALL'][$field->name])) {
						$_field_name_ = $field->name;
						FlexicontentFields::renderField($items, $_field_name_, $custom_values, $method='display', $view);
					}
				}
		    $_rendered['ALL']['core'] = 1;
			}
		}


		// *** RENDER fields on DEMAND, (if present in template positions)
		foreach ($fbypos as $pos) {
		  // RENDER fields if they are present in a template position (or in a dummy template position ... e.g. when called by module)
			foreach ($pos->fields as $c => $f) {

				// CORE/CUSTOM: Render field (if already rendered above, the function will return result immediately)
				$method = (isset($pos->methods[$c]) && $pos->methods[$c]) ? $pos->methods[$c] : 'display';

				// Render ANY CORE field with single call for all items, CORE fields are assigned to ALL types,
				// try to get field out of first item, if it does not exist, then field is a CUSTOM field
				$item = reset($items);
				$field = @ $item->fields[$f];

				if ($field && $field->iscore)
				{
					// Check if already rendered
					if ( !isset($_rendered['ALL']['core']) && !isset($_rendered['ALL'][$f]) )
					{
						// No custom values for CORE fields, values are decided inside the CORE field
						$values = null;
						FlexicontentFields::renderField($items, $f, $values, $method, $view);
					}
					$_rendered['ALL'][$f] = 1;
				}

				// Render ANY CUSTOM field with per item call
				// *** TODO: (future optimization) render a field at once for ALL ITEMs of SAME content type
				else foreach ($items as $item)
				{
					// Check that field with given name: $f exists for current item (AKA, that it is assigned to the item's type)
					if ( !isset($item->fields[$f]) )  continue;

					// Check if already rendered
					if ( isset($_rendered['ALL'][$f]) || isset($_rendered[$item->id][$f]) ) continue;

					// Get field and field values, currently, custom field values can be passed only for CUSTOM fields, OR versioned CORE/CUSTOM fields too ...
					$field  = $item->fields[$f];
					$values = isset($item->fieldvalues[$field->id]) ? $item->fieldvalues[$field->id] : array();

					// Render the field's display
					$field 	= FlexicontentFields::renderField($item, $field, $values, $method, $view);
					$_rendered[$item->id][$f] = 1;
				}

				foreach ($items as $item)
				{
					// Check that field with given name: $f exists for current item (AKA, that it is assigned to the item's type)
					if ( !isset($item->fields[$f]) )  continue;
					$field = $item->fields[$f];

					// Skip field if empty display was produced
					if ( !isset($field->display) || !strlen($field->display) ) continue;

					// Set field display HTML/data in the template position,
					if (!isset($item->positions[$pos->position]))
						$item->positions[$pos->position] = new stdClass();
					$item->positions[$pos->position]->{$f} = new stdClass();

					$item->positions[$pos->position]->{$f}->id				= $field->id;
					$item->positions[$pos->position]->{$f}->id				= $field->id;
					$item->positions[$pos->position]->{$f}->name			= $field->name;
					$item->positions[$pos->position]->{$f}->label		= $field->parameters->get('display_label') ? $field->label : '';
					$item->positions[$pos->position]->{$f}->display	= $field->display;
				}
			}
		}
		return $items;
	}


	/**
	 * Method to get the values of the fields for multiple items at once
	 *
	 * @access private
	 * @return object
	 * @since 1.5
	 */
	static function _getCustomValues(&$items, $view = FLEXI_ITEMVIEW)
	{
		$versioned_item = count($items)==1 && !empty($items[0]->version_id) && !empty($items[0]->created_by);
		$version = $versioned_item ? $items[0]->version_id : 0;

		$item_ids = array();
		foreach ($items as $item) $item_ids[] = $item->id;

		$db = JFactory::getDbo();
		$query = 'SELECT field_id, value, item_id, valueorder, suborder'
				.( $version ? ' FROM #__flexicontent_items_versions':' FROM #__flexicontent_fields_item_relations')
				.' WHERE item_id IN (' . implode(',', $item_ids) .')'
				.( $version ? ' AND version=' . (int)$version:'')
				.' AND value > "" '
				.' ORDER BY item_id, field_id, valueorder, suborder'  // first 2 parts are not needed ...
				;
		$db->setQuery($query);
		$values = $db->loadObjectList();

		$fieldvalues = array();
		if ($values) foreach ($values as $v) {
			$fieldvalues[$v->item_id][$v->field_id][$v->valueorder - 1][$v->suborder - 1] = $v->value;
		}
		foreach ($fieldvalues as & $iv) {
			foreach ($iv as & $fv) {
				foreach ($fv as & $ov) {
					if (count($ov) == 1) $ov = reset($ov);
				}
				unset($ov);
			}
			unset($fv);
		}
		unset($iv);
		return $fieldvalues;
	}


	/**
	 * Method to get the custom field values for multiple items at once
	 *
	 * @access public
	 * @return array indexed by item IDs, and then index by field names
	 * @since 3.1.2
	 */
	static function getCustomFieldValues(&$items, $view = FLEXI_ITEMVIEW)
	{
		$db = JFactory::getDbo();

		$query = $db->getQuery(true)
			->select('*')
			->from('#__flexicontent_fields');

		$fields = $db->setQuery($query)->loadObjectList('id');
		$custom = FlexicontentFields::_getCustomValues($items, $view);

		$data = array();

		foreach($custom as $item_id => $fdata)
		{
			foreach($fdata as $fid => $fvalues)
			{
				if ( !isset($fields[$fid]) ) continue;

				// Make sure field values is an array
				if (!is_array($fvalues))
				{
					$fvalues = strlen($fvalues) ? array($fvalues) : array();
				}

				// Unserialize values already serialized values
				foreach ($fvalues as $i => $val)
				{
					$array = flexicontent_db::unserialize_array($val, $force_array=false, $force_value=false);

					if ($array !== false)
					{
						$fvalues[$i] = $array;
					}
				}

				$data[$item_id][$fields[$fid]->name] = $fvalues;
			}
		}

		return $data;
	}


	/**
	 * Method to get the tags
	 *
	 * @access	private
	 * @return	object
	 * @since	1.5
	 */
	static function _getTags(&$items, $view = FLEXI_ITEMVIEW)
	{
		$db = JFactory::getDbo();

		// ***************************************************************
		// SPECIAL CASE for versioned fields in items view when previewing
		// ***************************************************************

		$versioned_item = count($items)==1 && !empty($items[0]->version_id) && !empty($items[0]->tags);

		if ($versioned_item)
		{
			$item = $items[0];
			if ( !count($item->tags) ) return array();
			$item->tags = ArrayHelper::toInteger($item->tags);

			$query 	= 'SELECT DISTINCT t.id AS _id, t.id, t.name, jt.id,jt.title, jt.description,'
				. ' CASE WHEN CHAR_LENGTH(t.alias) THEN CONCAT_WS(\':\', t.id, t.alias) ELSE t.id END as slug'
				. ' FROM #__flexicontent_tags AS t'
				. ' LEFT JOIN #__tags AS jt ON jt.id = t.jtag_id'
				. ' WHERE t.id IN (' . implode(',', $item->tags) . ')'
				. ' AND t.published = 1'
				;
			$tags = $db->setQuery($query)->loadObjectList();

			foreach($tags as $tag)
			{
				$tag->id = $tag->_id;
				$tag->name = $tag->title ?: $tag->name;
				unset($tag->_id);
			}

			$taglists[$item->id] = array_reverse( $tags );
			return $taglists;
		}


		// *************************
		// Get itemid to tagid pairs
		// *************************

		$cids = array();
		foreach ($items as $item)
		{
			$cids[] = $item->id;
		}

		if (empty($cids)) return array();
		$cids = ArrayHelper::toInteger($cids);

		$query = 'SELECT t.tid, t.itemid'
			. ' FROM #__flexicontent_tags_item_relations AS t'
			. ' WHERE t.itemid IN (' . implode(',', $cids) .')';
		$item_tagids = $db->setQuery($query)->loadObjectList();

		if ( empty($item_tagids) ) return array();


		// ***************************
		// Get single copy of tag data
		// ***************************

		$query = 'SELECT DISTINCT t.id AS _id, t.name, jt.id, jt.title, jt.description, '
			. ' CASE WHEN CHAR_LENGTH(t.alias) THEN CONCAT_WS(\':\', t.id, t.alias) ELSE t.id END as slug'
			. ' FROM #__flexicontent_tags AS t'
			. ' JOIN #__flexicontent_tags_item_relations AS i ON i.tid = t.id'
			. ' LEFT JOIN #__tags AS jt ON jt.id = t.jtag_id'
			. ' WHERE i.itemid IN (' . implode(',', $cids) . ')'
			. ' AND t.published = 1'
			;
		$tags = $db->setQuery($query)->loadObjectList('_id');

		foreach($tags as $tag)
		{
			$tag->id = $tag->_id;
			$tag->name = $tag->title ?: $tag->name;
			unset($tag->_id);
		}

		// Create an array of every item's tag data
		$taglists = array();

		foreach ($item_tagids as $it)
		{
			if ( !empty($tags[$it->tid]) )
			{
				$taglists[$it->itemid][] = $tags[$it->tid];
			}
		}

		// Workaround for not having "order" column in tags assignments table (should work in MySql, but no guarantee)
		foreach ($taglists as $itemid => $taglist)
		{
			$taglists[$itemid] = array_reverse($taglists[$itemid]);
		}

		return $taglists;
	}

	/**
	 * Method to get the categories
	 *
	 * @access	private
	 * @return	object
	 * @since	1.5
	 */
	static function _getCategories(&$items, $view = FLEXI_ITEMVIEW)
	{
		$db = JFactory::getDbo();

		// ***************************************************************
		// SPECIAL CASE for versioned fields in items view when previewing
		// ***************************************************************

		$versioned_item = count($items)==1 && !empty($items[0]->version_id) && !empty($items[0]->categories);

		if ($versioned_item)
		{
			$item = $items[0];
			if ( !count($item->categories) ) return array();
			$item->categories = ArrayHelper::toInteger($item->categories);

			$query = 'SELECT DISTINCT c.id, c.title,'
				. ' CASE WHEN CHAR_LENGTH(c.alias) THEN CONCAT_WS(\':\', c.id, c.alias) ELSE c.id END as slug'
				. ' FROM #__categories AS c'
				. ' WHERE c.id IN (' . implode(',', $item->categories) . ')'
				//. ' AND c.published = 1'   // Get unpublished cats too
				;
			$cats = $db->setQuery($query)->loadObjectList();

			$catlists[$item->id] = array_reverse( $cats );
			return $catlists;
		}

		// *************************
		// Get itemid to tagid pairs
		// *************************

		$cids = array();
		foreach ($items as $item)
		{
			$cids[] = $item->id;
		}

		if (empty($cids)) return array();
		$cids = ArrayHelper::toInteger($cids);

		$query = 'SELECT c.catid, c.itemid'
			. ' FROM #__flexicontent_cats_item_relations AS c'
			. ' WHERE c.itemid IN (' . implode(',', $cids) .')';
		$db->setQuery( $query );
		$item_catids = $db->loadObjectList();

		if ( empty($item_catids) ) return array();


		// ***************************
		// Get single copy of cat data
		// ***************************

		$query = 'SELECT DISTINCT c.id, c.title, CASE WHEN CHAR_LENGTH(c.alias) THEN CONCAT_WS(\':\', c.id, c.alias) ELSE c.id END as slug'
			. ' FROM #__categories AS c'
			. ' JOIN #__flexicontent_cats_item_relations AS rel ON rel.catid = c.id'
			. ' WHERE rel.itemid IN (' . implode(',', $cids) . ')'
			//. ' AND c.published = 1'   // Get unpublished cats too
			;

		$db->setQuery( $query );
		$cats = $db->loadObjectList('id');

		// Create an array of every item's cat data
		$catlists = array();

		foreach ($item_catids as $ic)
		{
			if ( !empty($cats[$ic->catid]) )
			{
				$catlists[$ic->itemid][] = $cats[$ic->catid];
			}
		}

		return $catlists;
	}

	/**
	 * Method to get the nr of favourites
	 *
	 * @access	private
	 * @return	object
	 * @since	1.5
	 */
	static function _getFavourites(&$items, $view = FLEXI_ITEMVIEW)
	{
		$db = JFactory::getDbo();
		$cids = array();
		foreach ($items as $item) { array_push($cids, $item->id); }

		// Favourites via DB
		$query 	= 'SELECT DISTINCT itemid, 1 AS favs FROM #__flexicontent_favourites'
				. " WHERE type = 0 AND itemid IN ('" . implode("','", $cids) . "')"
				;
		$db->setQuery($query);
		$favourites = $db->loadObjectList('itemid');

		// Also add favourites via cookie (Only current user is considered)
		$favs = flexicontent_favs::getInstance()->getRecords($_favs_type = 'item');

		foreach($favs as $itemid => $f)
		{
			$favourites[(int)$itemid] = (object) array('itemid' => (int) $itemid, 'favs' => 1);
		}

		return $favourites;
	}

	/**
	 * Method to get the favourites of an user
	 *
	 * @access	private
	 * @return	object
	 * @since	1.5
	 */
	static function _getFavoured(&$items, $view = FLEXI_ITEMVIEW)
	{
		$user = JFactory::getUser();
		$db = JFactory::getDbo();

		$cids = array();
		foreach ($items as $item) { array_push($cids, $item->id); }

		// Favourites via DB
		$query 	= 'SELECT DISTINCT itemid, 1 AS fav FROM #__flexicontent_favourites'
			. ' WHERE type = 0 AND itemid IN (' . implode(',', $cids) . ')'
			. ' AND userid = ' . ((int)$user->id)
			;
		$db->setQuery($query);
		$favoured = $db->loadObjectList('itemid');

		// Also add favourites via cookie
		$favs = flexicontent_favs::getInstance()->getRecords($_favs_type = 'item');

		foreach($favs as $itemid => $f)
		{
			$favoured[(int)$itemid] = (object) array('itemid' => (int) $itemid, 'fav' => 1);
		}

		return $favoured;
	}

	/**
	 * Method to get the modifiers of the items
	 *
	 * @access	private
	 * @return	object
	 * @since	1.5
	 */
	static function _getModifiers(&$items, $view = FLEXI_ITEMVIEW)
	{
		// This is fix for versioned field of modifier in items view when previewing
		$versioned_item = count($items)==1 && !empty($items[0]->version_id) && !empty($items[0]->modified_by);

		$db = JFactory::getDbo();
		$cids = array();
		foreach ($items as $item) { array_push($cids, $item->id); }

		$query 	= 'SELECT i.id, u.name, u.username, u.email FROM #__content AS i'
			. ' LEFT JOIN #__users AS u ON '  .  ( $versioned_item ? 'u.id = '.$items[0]->modified_by : 'u.id = i.modified_by' )
			. " WHERE i.id IN ('" . implode("','", $cids) . "')"
			;
		$db->setQuery($query);
		$modifiers = $db->loadObjectList('id');

		return $modifiers;
	}

	/**
	 * Method to get the authors of the items
	 *
	 * @access	private
	 * @return	object
	 * @since	1.5
	 */
	static function _getAuthors(&$items, $view = FLEXI_ITEMVIEW)
	{
		// This is fix for versioned fields in items view when previewing
		$versioned_item = count($items)==1 && !empty($items[0]->version_id) && !empty($items[0]->created_by);

		$db = JFactory::getDbo();
		$cids = array();
		foreach ($items as $item) { array_push($cids, $item->id); }

		$query 	= 'SELECT i.id, u.name, i.created_by_alias as alias, u.username, u.email FROM #__content AS i'
				. ' LEFT JOIN #__users AS u ON '  .  ( $versioned_item ? 'u.id = '.$items[0]->created_by : 'u.id = i.created_by' )
				. " WHERE i.id IN ('" . implode("','", $cids) . "')"
				;
		$db->setQuery($query);
		$authors = $db->loadObjectList('id');

		return $authors;
	}

	/**
	 * Method to get the types names of the items
	 *
	 * @access	private
	 * @return	object
	 * @since	1.5
	 */
	static function _getTypenames(&$items, $view = FLEXI_ITEMVIEW)
	{
		$db = JFactory::getDbo();

		$type_ids = array();
		foreach ($items as $item) { $type_ids[$item->type_id]=1; }
		$type_ids = array_keys($type_ids);

		$query 	= 'SELECT id, name FROM #__flexicontent_types'
				. " WHERE id IN ('" . implode("','", $type_ids) . "')"
				;
		$db->setQuery($query);
		$types = $db->loadObjectList('id');

		$typenames = array();
		foreach ($items as $item) {
			$typenames[$item->id] = new stdClass();
			$typenames[$item->id]->name = isset($types[$item->type_id]) ? $types[$item->type_id]->name : 'without type';
		}

		return $typenames;
	}

	/**
	 * Method to get the votes of the items
	 *
	 * @access	private
	 * @return	object
	 * @since	1.5
	 */
	static function _getVotes(&$items, $view = FLEXI_ITEMVIEW)
	{
		$db = JFactory::getDbo();
		$cids = array();

		foreach ($items as $item)
		{
			array_push($cids, $item->id);
		}

		$query 	= 'SELECT * FROM #__content_rating'
				. " WHERE content_id IN ('" . implode("','", $cids) . "')"
				;
		$db->setQuery($query);
		$votes = $db->loadObjectList('content_id');

		$query 	= 'SELECT *, field_id as extra_id FROM #__flexicontent_items_extravote'
				. " WHERE content_id IN ('" . implode("','", $cids) . "')"
				;
		$db->setQuery($query);
		$extra_votes= $db->loadObjectList();

		// Assign each item 's extra votes to the item's votes as member variable "extra"
		foreach ($extra_votes as $extra_vote)
		{
			$votes[$extra_vote->content_id]->extra[$extra_vote->extra_id] = $extra_vote;
		}

		return $votes;
	}



	// ***********************************************************
	// Methods for creating field configuration in an OPTMIZED way
	// ***********************************************************

	// Method to create field parameters in an optimized way, and also apply Type Customization for CORE fields
	static function loadFieldConfig(&$field, &$item, $name='', $field_type='', $label='', $desc='', $iscore=1)
	{
		$app = JFactory::getApplication();
		$db = JFactory::getDbo();

		static $tparams = array();
		static $tinfo   = array();
		static $fdata   = array();

		static $no_typeparams = null;
		if ($no_typeparams)
		{
			$no_typeparams = new JRegistry();
		}

		static $is_form=null;

		if ($is_form===null)
		{
			$is_form = (
				in_array($app->input->getCmd('task', ''), array('add', 'edit')) &&
				$app->input->getCmd('view', '') === 'item' &&
				$app->input->getCmd('option', '') === 'com_flexicontent'
			) || (
				$app->input->getCmd('layout', '') === 'form' &&
				$app->input->getCmd('view', '') === 'article' &&
				$app->input->getCmd('option', '') === 'com_content'
			);
		}

		// Create basic field data if no field given
		if (!empty($name))
		{
			$field->iscore = $iscore;
			$field->name = $name;
			$field->field_type = $field_type;
			$field->label = $label;
			$field->description = $desc;
			$field->attribs = '';
		}

		// Get Content Type parameters if not already retrieved
		$type_id = $item
			? $item->type_id
			: 0;

		if ($type_id && ( !isset($tinfo[$type_id]) || !isset($tparams[$type_id]) ) )
		{
			$tinfo[$type_id] = $tparams[$type_id] = null;
			FlexicontentFields::_getTypeToCoreFieldParams ($type_id, $tinfo[$type_id], $tparams[$type_id]);
		}

		// Set Content Type parameters otherwise set empty defaults (e.g. new item form with not typeid set)
		$type_data_exist = $type_id && $tinfo[$type_id] && $tparams[$type_id] ;

		$typename   = $type_data_exist  ?  $tinfo[$type_id]['typename']    :  '';
		$typealias  = $type_data_exist  ?  $tinfo[$type_id]['typealias']   :  '';
		$tindex     = $type_data_exist  ?  $typename.'_'.$typealias        :  'no_type';
		if ($type_data_exist)  $typeparams = & $tparams[$type_id];  else  $typeparams = & $no_typeparams;


		// Create the (CREATED ONCE per field) SHARED object that will contain: (a) label, (b) description, (c) all (merged) field parameters
		// Create parameters once per custom field OR once per pair of:  Core FIELD type - Item CONTENT type
		if ( !isset($fdata[$tindex][$field->name]) )
		{
			if ( !$field->iscore || !$type_id )
			{
				// CUSTOM field or CORE field with no type
				$fdata[$tindex][$field->name] = new stdClass();
				$fdata[$tindex][$field->name]->parameters = new JRegistry($field->attribs);
				if ($field->field_type=='maintext' && $fdata[$tindex][$field->name]->parameters->get('trigger_onprepare_content', '')==='')
				{
					$fdata[$tindex][$field->name]->parameters->set('trigger_onprepare_content', 1);  // Default for maintext (description field) is to trigger plugins
				}
			}

			else
			{
				$pn_prefix = $field->field_type!='maintext' ? $field->name : $field->field_type;

				// Initialize an empty object, and create parameters object of the field
				$fdata[$tindex][$field->name] = new stdClass();
				$fdata[$tindex][$field->name]->parameters = new JRegistry($field->attribs);

				// SET a type specific label, description for the current CORE  field (according to current language)
				$field_label_type = $tparams[$type_id]->get($pn_prefix.'_label', '');
				$field_desc_type = $tparams[$type_id]->get($pn_prefix.($is_form ? '_desc' : '_viewdesc'), '');
				FlexicontentFields::_getLangSpecificValue ($type_id, $field_label_type, 'label', $fdata[$tindex][$field->name]);
				FlexicontentFields::_getLangSpecificValue ($type_id, $field_desc_type, 'description', $fdata[$tindex][$field->name]);

				// Override field parameters with Type specific Parameters
				if ( isset($tinfo[$type_id]['params'][$pn_prefix]) )
				{
					foreach ($tinfo[$type_id]['params'][$pn_prefix] as $param_name => $param_value)
					{
						$fdata[$tindex][$field->name]->parameters->set( $param_name, $param_value) ;
					}
				}

				// SPECIAL CASE: check if it exists a FAKE (custom) field that customizes CORE field per Content Type
				$query = "SELECT attribs, published FROM #__flexicontent_fields WHERE name=".$db->Quote($field->name."_".$typealias);
				$db->setQuery($query);  //echo $query;
				$data = $db->loadObject(); //print_r($data);
				if ($data && $data->published)
				{
					JFactory::getApplication()->enqueueMessage(__FUNCTION__."(): Please unpublish plugin with name: ".$field->name."_".$typealias." it is used for customizing a core field",'error');
				}

				// Finally merge custom field parameters with the type specific parameters ones
				if ($data)
				{
					$ts_params = new JRegistry($data->attribs);
					$fdata[$tindex][$field->name]->parameters->merge($ts_params);
				}
			}
		}

		// Set custom label, description or maintain default
		$field->label       =  isset($fdata[$tindex][$field->name]->label)        ?  $fdata[$tindex][$field->name]->label        :  $field->label;
		$field->description =  isset($fdata[$tindex][$field->name]->description)  ?  $fdata[$tindex][$field->name]->description  :  $field->description;
		$field->label       = JText::_($field->label);
		$field->description = JText::_($field->description);

		// Finally set field's parameters, but to clone ... or not to clone, better clone to allow customizations for individual item fields ...
		$field->parameters = clone($fdata[$tindex][$field->name]->parameters);

		return $field;
	}


	// Method to override PARAMETER VALUES with their Type Specific values
	static function _getTypeToCoreFieldParams ($type_id, & $tinfo, & $tparams) {

		static $core_field_names = null;
		if ( $core_field_names == null ) {
			$query = "SELECT field_type FROM #__flexicontent_fields WHERE iscore=1";
			//echo $query;
			$db = JFactory::getDbo();
			$db->setQuery($query);
			$core_field_names = $db->loadColumn();

			$core_field_names[] = 'maintext';
			$core_field_names = array_flip($core_field_names);
			unset($core_field_names['text']);
		}

		$query = 'SELECT t.attribs, t.name, t.alias FROM #__flexicontent_types AS t WHERE t.id = ' . $type_id;
		$db = JFactory::getDbo();
		$db->setQuery($query);
		$typedata = $db->loadObject();
		if ( $typedata ) {
			$tinfo['typename']  = $typedata->name;
			$tinfo['typealias'] = $typedata->alias;
			$tparams = new JRegistry($typedata->attribs);

			$_tparams = $tparams->toArray();
			$tinfo['params'] = array();

			foreach ($_tparams as $param_name => $param_value) {
				$res = preg_split('/_/', $param_name, 2);
				if ( count($res) < 2 ) continue;

				$o_field_type = $res[0];  $o_param_name = $res[1];
				if ( !isset($core_field_names[$o_field_type]) ) continue;

				//echo "$o_field_type _ $o_param_name = $param_value <br>\n";
				$skipparam = false;

				if ( strlen($param_value) ) {
					/*$skipparam = in_array($o_param_name, array('label','desc','viewdesc'));
					if ($skipparam) continue;*/
					$tinfo['params'][$o_field_type][$o_param_name] = $param_value;
					//echo "$o_field_type _ $o_param_name = $param_value <br>\n";
				}
			}
			//echo "<pre>"; print_r($tinfo['params']); echo "</pre>";
		}
	}


	// Method get a language specific value from given Content Type (or other) Data
	static function _getLangSpecificValue ($type_id, $type_prop_value, $prop_name, & $fdata)
	{
		//--. Get a 2 character language tag
		static $lang = null;
		$lang = substr(JFactory::getLanguage()->getTag(), 0,2);

		// --. SET a type specific label for the current field

		// a. Try field label to get for current language
		$result = preg_match("/(\[$lang\])=([^[]+)/i", $type_prop_value, $matches);
		if ($result)
		{
			$fdata->{$prop_name} = $matches[2];
		}

		else if ($type_prop_value)
		{
			// b. Try to get default for all languages
			$result = preg_match("/(\[default\])=([^[]+)/i", $type_prop_value, $matches);
			if ($result)
			{
				$fdata->{$prop_name} = $matches[2];
			}

			// c. Check that no languages specific string are defined
			else
			{
				$result = preg_match("/(\[??\])=([^[]+)/i", $type_prop_value, $matches);
				if (!$result) {
					$fdata->{$prop_name} = $type_prop_value;
				}
			}
		}

		// d. Maintain field 's default label
		else ;
	}



	// **************************************************************************************
	// Methods (a) for INDEXED FIELDs, and (b) for replacement field values / item properties
	// **************************************************************************************

	// Common method to get the column expressions used to create the field's elements
	static function indexedField_getColsExprs($field, $item, $field_elements)
	{
		$q = preg_replace('/\b(as\s+)(value|text|image|valgrp|state)\b\s*(,)?\s*/i', 'AS \2\3 ', $field_elements);
		$q = preg_replace('/^\s*(select)\b\s*/i', '', $q);
		$q = substr($q, 0, stripos($q, 'from'));
		$q = preg_split('/(AS value,?\s*|AS text,?\s*|AS image,?\s*|AS valgrp,?\s*|AS state,?\s*)/i', $q, -1, PREG_SPLIT_DELIM_CAPTURE);
		array_pop($q);

		$cols = array();
		$step = 0;
		$prev = '';
		foreach($q as $d)
		{
			if ($step % 2 == 1)
			{
				$d = preg_replace('/\b(as\s+)(value|text|image|valgrp|state)\b\s*(,)?\s*/i', '\2', $d);
				$cols[$d] = $prev;
			}
			$prev = $d;
			$step++;
		}
		return $cols;
	}


	// Common method to get the allowed element values (field values with index,label,... properties) for fields that use indexed values
	static function indexedField_getElements($field, $item, $extra_props=array(), &$item_pros=true, $is_filter=false, $and_clause=false, $iLang = false)
	{
		static $_elements_cache = array();

		// static variable (array) so that we load overrides only once per language
		static $lang_overrides;
		$lang_filter_values = $field->parameters->get('lang_filter_values', 1);

		// Check if we have loaded the override file already, (aka we load it only once)
		if ( $iLang && !isset($lang_overrides[$iLang]) && $lang_filter_values )
		{
			$lang_overrides_site[$iLang]  = JLanguageHelper::parseIniFile(JPATH_SITE . '/language/overrides/' . $iLang . '.override.ini');
			$lang_overrides_admin[$iLang] = JLanguageHelper::parseIniFile(JPATH_ADMINISTRATOR . '/language/overrides/' . $iLang . '.override.ini');

			/**
			 * Load frontend language overrides of item's language
			 * then overwrite with backend language overrides 
			 * we do this because ... anyway search index is backend only
			 */
			$lang_overrides[$iLang] = array();
			if ($lang_overrides_site)  foreach($lang_overrides_site[$iLang]  as $i => $v) $lang_overrides[$iLang][strtoupper($i)] = $v;
			if ($lang_overrides_admin) foreach($lang_overrides_admin[$iLang] as $i => $v) $lang_overrides[$iLang][strtoupper($i)] = $v;
		}

		// Get overrides of item's language
		$overrides = $iLang && $lang_filter_values && isset($lang_overrides[$iLang]) ? $lang_overrides[$iLang] : array();


		// For fields that use this parameter
		$sql_mode = (int) $field->parameters->get('sql_mode', 0);
		$canCache = ! $field->parameters->get('nocache');

		if ($canCache && isset($_elements_cache[$field->id][$iLang][$is_filter]))
		{
			return $_elements_cache[$field->id][$iLang][$is_filter];
		}

		$field_elements = $field->parameters->get('field_elements', '') ;

		$default_extra_props = array('image', 'valgrp', 'state');

		if ($is_filter)
		{
			$filter_customize_options = (int) $field->parameters->get('filter_customize_options', 0);
			$filter_custom_options    = $field->parameters->get('filter_custom_options', '');

			// Custom query for value retrieval
			if ($filter_customize_options && $filter_custom_options)
			{
				$sql_mode = $filter_customize_options === 1;
				$field_elements = $filter_custom_options;
			}

			// Default query for value retrieval
			elseif (!$field_elements)
			{
				$sql_mode = 1;
				$field_elements = 'SELECT DISTINCT value, value as text '
					. ' FROM #__flexicontent_fields_item_relations '
					. ' WHERE field_id={field->id} AND value != ""'
				;
			}

			// Set parameters may be used later
			$field->parameters->set('sql_mode', $sql_mode);
			$field->parameters->set('field_elements', $field_elements);
		}

		// TODO: examine this in combination with canCache
		//$field_elements = FlexicontentFields::replaceFieldValue( $field, $item, $field_elements, 'field_elements' );


		// SQL mode, parameter field_elements contains an SQL query
		if ($sql_mode)
		{
			$db = JFactory::getDbo();

			// Get/verify query string, check if item properties and other replacements are allowed and replace them
			$query = preg_match('#^select#i', $field_elements) ? $field_elements : '';
			$query = FlexicontentFields::doQueryReplacements($field_elements, $field, $item, $item_pros, $canCache);

			if ($query)
			{
				$query = preg_replace('/_valgrp_in_/ui', ($and_clause ? $and_clause : ' 1 '), $query);
			}

			// Execute SQL query to retrieve the field value - label pair, and any other extra properties
			$results = false;

			if ($query)
			{
				$results = $db->setQuery($query)->loadObjectList('value');
			}

			if ($results && $lang_filter_values)
			{
				// Language filter these prefering the overrides in item's language
				foreach ($results as $val => $result)
				{
					$lang_string         = strtoupper($results[$val]->text);
					$results[$val]->text = JText::_(isset($overrides[$lang_string]) ? $overrides[$lang_string] : $results[$val]->text);
				}
			}

			// !! CHECK: DB query failed or produced an error (AN EMPTY ARRAY IS NOT AN ERROR)
			if (!$query || !is_array($results))
			{
				if ($canCache && !$and_clause)
				{
					$_elements_cache[$field->id][$iLang][$is_filter] = false;
				}

				return false;
			}
		}


		// Elements mode, parameter field_elements contain list of allowed values
		else
		{
			// Parse the elements used by field unsetting last element if empty
			$listelements = preg_split("/[\s]*%%[\s]*/", $field_elements);

			if (empty($listelements[count($listelements)-1]))
			{
				unset($listelements[count($listelements)-1]);
			}

			$props_needed = 2 + count($extra_props);

			// Split elements into their properties: value, label, extra_prop1, extra_prop2
			$listarrays = array();
			$results = array();

			foreach ($listelements as $listelement)
			{
				$listelement_props  = preg_split("/[\s]*::[\s]*/", $listelement);

				// Compatibility with previously stored elements, ignore missing 'valgrp' and 'state'
				if (count($listelement_props) < $props_needed && count($listelement_props)==3 && $extra_props[1]=='valgrp')  $listelement_props[] = null;
				if (count($listelement_props) < $props_needed && count($listelement_props)==4 && $extra_props[2]=='state')   $listelement_props[] = null;

				if (count($listelement_props) < $props_needed)
				{
					echo "Error in field: ".$field->label." while splitting element: ".$listelement." properties needed: ".$props_needed." properties found: ".count($listelement_props);
					return ($_elements_cache[$field->id][$iLang][$is_filter] = false);
				}

				$val = $listelement_props[0];
				$results[$val] = new stdClass();
				$results[$val]->value = $listelement_props[0];
				$results[$val]->text  = $listelement_props[1];

				// Language filter these prefering the overrides in item's language
				if ($lang_filter_values)
				{
					$lang_string         = strtoupper($results[$val]->text);
					$results[$val]->text = JText::_(isset($overrides[$lang_string]) ? $overrides[$lang_string] : $results[$val]->text);
				}
				
				$el_prop_count = 2;
				$_props = !empty($extra_props) ? $extra_props : $default_extra_props;

				// Optional extra properties for fields that use them
				foreach ($_props as $extra_prop)
				{
					$results[$val]->{$extra_prop} = isset($listelement_props[$el_prop_count]) ? $listelement_props[$el_prop_count] : null;
					$el_prop_count++;
				}
			}
		}

		// Return found elements, caching them if possible (if no item specific elements are used)
		if ($canCache && !$and_clause)
		{
			$_elements_cache[$field->id][$iLang][$is_filter] = & $results;
		}

		return $results;
	}


	// Common method to map element value INDEXES to value objects for fields that use indexed values
	static function indexedField_getValues(&$field, &$elements, $value_indexes, $prepost_prop='text')
	{
		// Check for empty values
		if ( !is_array($value_indexes) && !strlen($value_indexes) )
		{
			return array();
		}

		// Make sure indexes is an array
		$value_indexes = !is_array($value_indexes)
			? array($value_indexes)
			: $value_indexes;

		$pretext='';
		$posttext='';
		if ( $prepost_prop )
		{
			$pretext  = $field->parameters->get( 'pretext', '' ) ;
			$posttext = $field->parameters->get( 'posttext', '' ) ;
			$remove_space = $field->parameters->get( 'remove_space', 0 ) ;

			$pretext 	= $remove_space ? $pretext  : $pretext . ' ';
			$posttext	= $remove_space ? $posttext : ' ' . $posttext;
		}

		// Handle multiple sub-values per value
		if (is_array(reset($value_indexes)))
		{
			$_v = array();
			foreach($value_indexes as $v) $_v[] = $v;
			$value_indexes = $v;
		}

		// Get the labels of used values in an display[] array
		$values = array();
		foreach($value_indexes as $val_index)
		{
			if ( !strlen($val_index) ) continue;
			if ( !isset($elements[$val_index]) ) continue;

			$values[$val_index] = get_object_vars($elements[$val_index] );
			if ($prepost_prop)
			{
				$values[$val_index][$prepost_prop] = $pretext . $values[$val_index][$prepost_prop] . $posttext;
			}
		}

		return $values;
	}


	// Helper method to replace item properties for the SQL value mode for various fields
	static function doQueryReplacements(&$query, &$field, &$item, &$item_pros=true, &$canCache=null)
	{
		// ***
		// *** Replace item properties
		// ***

		preg_match_all("/{item->[0-9a-zA-Z_]+}/", $query, $matches);

		$canCache = count($matches[0]) == 0;
		if ( !$item_pros && count($matches[0]) )
		{
			$item_pros = count($matches[0]);
			return '';
		}

		// If needed replace item properties, loading the item if not already loaded
		if (count($matches[0]) && !$item)
		{
			if ( empty($field->item_id) ) return;

			$item = JTable::getInstance( $type = 'flexicontent_items', $prefix = '', $config = array() );
			$item->load( $field->item_id );
		}

		foreach ($matches[0] as $replacement_tag)
		{
			$replacement_value = '$'.substr($replacement_tag, 1, -1);
			eval ("\$replacement_value = \"$replacement_value\";");
			$query = str_replace($replacement_tag, $replacement_value, $query);
		}


		// ***
		// *** Replace field properties
		// ***

		if ($field)
		{
			preg_match_all("/{field->[0-9a-zA-Z_]+}/", $query, $matches);
			foreach ($matches[0] as $replacement_tag)
			{
				$replacement_value = '$'.substr($replacement_tag, 1, -1);
				eval ("\$replacement_value = \" $replacement_value\";");
				$query = str_replace($replacement_tag, $replacement_value, $query);
			}
		}

		// ***
		// *** Other replacements: current user language
		// ***

		$query = str_replace("{curr_userlang_shorttag}", flexicontent_html::getUserCurrentLang($short_tag=true), $query);
		$query = str_replace("{curr_userlang_fulltag}", flexicontent_html::getUserCurrentLang($short_tag=false), $query);

		return $query;
	}


	// Helper method to replace a field value inside a given named variable of a given item/field pair
	static function replaceFieldValue( &$field, &$item, $variable, $varname, & $cacheable = false )
	{
		static $parsed = array();
		static $d;
		static $c;

		// Parse field variable if not already parsed
		if ( !isset($parsed[$field->id][$varname]) )
		{
			$parsed[$field->id][$varname] = true;

			$result = preg_match_all("/\{\{([a-zA-Z_0-9-]+)(##)?([0-9]+)?(##)?([a-zA-Z_0-9-]+)?\}\}/", $variable, $field_matches);

			if ($result)
			{
				$d[$field->id][$varname]['fulltxt']   = $field_matches[0];
				$d[$field->id][$varname]['fieldname'] = $field_matches[1];
				$d[$field->id][$varname]['valueno']   = $field_matches[3];
				$d[$field->id][$varname]['propname']  = $field_matches[5];
			}
			else
			{
				$d[$field->id][$varname]['fulltxt']   = array();
				$d[$field->id][$varname]['valueno']   = false;
			}

			$result = preg_match_all("/\{\{(item->)([a-zA-Z_0-9]+)\}\}/", $variable, $field_matches);
			if ($result)
			{
				$c[$field->id][$varname]['fulltxt']   = $field_matches[0];
				$c[$field->id][$varname]['propname']  = $field_matches[2];
			}
			else
			{
				$c[$field->id][$varname]['fulltxt']   = array();
			}

			if ( !count($d[$field->id][$varname]['fulltxt']) && !count($c[$field->id][$varname]['fulltxt']) ) {
				$cacheable = true;
			}
		}


		// ***
		// *** Replace field values / field properties
		// ***

		foreach($d[$field->id][$varname]['fulltxt'] as $i => $fulltxt)
		{
			$fieldname = $d[$field->id][$varname]['fieldname'][$i];
			$valueno   = $d[$field->id][$varname]['valueno'][$i] ? (int) $d[$field->id][$varname]['valueno'][$i] : 0;
			$propname  = $d[$field->id][$varname]['propname'][$i] ? $d[$field->id][$varname]['propname'][$i] : '';

			$fieldid = @ $item->fields[$fieldname]->id;
			$value   = @ $item->fieldvalues[$fieldid][$valueno];

			if (!$fieldid)
			{
				$value = JFactory::getApplication()->isClient('administrator')
					? 'Field with name: '.$fieldname.' not found'
					: '';
				$variable = str_replace($fulltxt, $value, $variable);
				continue;
			}

			$is_indexable = $propname && preg_match("/^_([a-zA-Z_0-9]+)_$/", $propname, $prop_matches) && ($propname = $prop_matches[1]);

			if ($fieldid <= 14 )
			{
				if ($fieldid==13)
				{
					$value = @ $item->categories[$valueno]->{$propname};
				}
				else if ($fieldid==14)
				{
					$value = @ $item->tags[$valueno]->{$propname};
				}
			}

			elseif ($is_indexable)
			{
				if ( $propname!='value' ) // no need for value to retrieve custom elements
				{
					$extra_props = $propname!='text' ? array($propname) : array();  // this will work only if field has a single extra property
					$extra_props = array();
					if ( !isset($item->fields[$fieldname]->parameters) )
					{
						FlexicontentFields::loadFieldConfig($item->fields[$fieldname], $item);
					}
					$elements = FlexicontentFields::indexedField_getElements( $item->fields[$fieldname], $item, $extra_props );
					$value = @ $elements[$value]->{$propname};
				}
			}

			elseif ($propname)
			{
				$value = flexicontent_db::unserialize_array($value, $force_array=false, $force_value=false);
				$value = $value && isset($value[$propname]) ? $value[$propname] : '';
			}

			$variable = str_replace($fulltxt, $value, $variable);
			//echo "<pre>"; print_r($item->fieldvalues[$fieldid]); echo "</pre>"; echo "Replaced $fulltxt with ITEM field VALUE: $value <br>";
		}


		// ***
		// *** Replace item properties
		// ***

		foreach($c[$field->id][$varname]['fulltxt'] as $i => $fulltxt)
		{
			$propname = $c[$field->id][$varname]['propname'][$i];

			if (!isset($item->{$propname}))
			{
				$value = JFactory::getApplication()->isClient('administrator')
					? 'Item property with name: '.$propname.' not found'
					: '';
				$variable = str_replace($fulltxt, $value, $variable);
				continue;
			}
			$value = $item->{$propname};

			$variable = str_replace($fulltxt, $value, $variable);
			//echo "<pre>"; echo "</pre>"; echo "Replaced $fulltxt with ITEM property VALUE: $value <br>";
		}

		// Return variable after all replacements
		return $variable;
	}





	// *********************************************************************
	// Methods for getting fields that support BASIC / ADVANCED search modes
	// *********************************************************************

	// Method to get various - SETs - of search fields, according to given limitations
	// Param 'search_type' : search, filter, all-search, dirty-search, dirty-nosupport, non-search
	static function getSearchFields($key='name', $indexer='advanced', $search_fields=null, $content_types=null, $load_params=true, $item_id=0, $search_type='all-search')
	{
		$db = JFactory::getDbo();
		static $sp, $nsp;

		switch ($search_type)
		{
			// All fields marked as text-searchable (also are published)
			case 'search':
				$where = $indexer === 'basic' ? ' f.issearch IN (1,2)' : ' f.isadvsearch IN (1,2) ';
				$where = '('.$where.' AND f.published = 1)';
				break;

			// All fields marked as filterable (also are published)
			case 'filter':
				$where = $indexer === 'basic' ? ' f.isfilter IN (1,2)' : ' f.isadvfilter IN (1,2) ';
				$where = '('.$where.' AND f.published = 1)';
				break;

			// ALL fields that must enter values in search index (also are published)
			case 'all-search':
				$where = $indexer === 'basic' ? ' f.issearch IN (1,2)' : ' ( f.isadvsearch IN (1,2) OR f.isadvfilter IN (1,2) )';
				$where = '('.$where.' AND f.published = 1)';
				break;

			// ONLY 'dirty' search fields (also are published)
			case 'dirty-search':
			case 'dirty-nosupport':
				$where = $indexer === 'basic' ? ' f.issearch = 2' : ' ( f.isadvsearch = 2 OR f.isadvfilter = 2 )';
				$where = '('.$where.' AND f.published = 1)';
				break;

			// ALL non-search fields (either OFF or unpublished)
			case 'non-search':
				$where = $indexer === 'basic' ? ' f.issearch IN (-1,0)' : ' ( f.isadvsearch IN (-1,0) AND f.isadvfilter IN (-1,0) )';
				$where = '('.$where.' OR f.published <> 0)';
				break;

			default:
				die(__FUNCTION__ . "(): unknown value for 'search_type' parameter"); // nothing to TODO
		}

		$where .=
			// Limit to given search fields list
			(!empty($search_fields) && is_array($search_fields) ? " AND f.name IN (" . implode('","', $search_fields) . ") " : "") .

			// Limit to given search fields list
			(!empty($search_fields) && is_string($search_fields) ? " AND f.name IN (" . $search_fields . ") " : "") .

			// Limit to given contnt types list
			(!empty($content_types) ? " AND ftr.type_id IN (" . implode(',', $content_types) . ") " : "");

		$query = 'SELECT f.*'
			. ' FROM #__flexicontent_fields AS f'
			. ' JOIN #__flexicontent_fields_type_relations AS ftr ON ftr.field_id = f.id'
			. ' WHERE '. $where
			. ' GROUP BY f.id'

			// If single type given then retrieve ordering for fields of this type
			. ' ORDER BY ' . ($content_types && count($content_types) === 1
				? ' ftr.ordering, f.name'
				: ' f.ordering, f.name'
			)
		;

		if (!isset($sp[$query]))
		{
			$fields = $db->setQuery($query)->loadObjectList($key);

			$sp_fields = array();
			$nsp_fields = array();

			foreach ($fields as $field_id => $field)
			{
				// Skip fields not being capable of advanced/basic search
				if ($indexer === 'basic')
				{
					if (! FlexicontentFields::getPropertySupport($field->field_type, $field->iscore, $search_type === 'filter' ? 'supportfilter' : 'supportsearch'))
					{
						$nsp_fields[$field_id] = $field;
						continue;
					}
				}

				elseif ($search_type !== 'non-search')
				{
					$no_supportadvsearch = ! FlexicontentFields::getPropertySupport($field->field_type, $field->iscore, 'supportadvsearch');
					$no_supportadvfilter = ! FlexicontentFields::getPropertySupport($field->field_type, $field->iscore, 'supportadvfilter');
					$skip_field = ($no_supportadvsearch && $search_type=='search')  ||  ($no_supportadvfilter && $search_type=='filter') ||
						($no_supportadvsearch && $no_supportadvfilter && in_array($search_type, array('all-search', 'dirty-nosupport') ) );

					if ($skip_field)
					{
						$nsp_fields[$field_id] = $field;
						continue;
					}
				}

				$field->item_id = $item_id;
				$field->value   = false;

				if ($load_params)
				{
					$field->parameters = new JRegistry($field->attribs);
				}

				$sp_fields[$field_id] = $field;
			}

			$sp[$query]  = $sp_fields;
			$nsp[$query] = $nsp_fields;
		}

		return $indexer === 'advanced' && $search_type === 'dirty-nosupport'
			? $nsp[$query]
			: $sp[$query];
	}


	// Method to get properties support for CORE fields
	static function getPropertySupport_BuiltIn()
	{
		static $info = null;
		if ($info!==null) return $info;

		$info = new stdClass();
		$info->core_search= array('title', 'maintext', 'tags', 'categories'   // CORE fields as text searchable
			, 'created', 'modified', 'createdby','modifiedby', 'type'
		);
		$info->core_filters = array('tags', 'categories'   // CORE fields as filters
			, 'created', 'modified', 'createdby', 'modifiedby', 'type', 'state'
		);
		$info->core_advsearch = array('title', 'maintext', 'tags', 'categories'   // CORE fields as text searchable for search view
			, 'created', 'modified', 'createdby','modifiedby', 'type'
		);
		$info->core_advfilters = array('title', 'maintext', 'tags', 'categories'   // CORE fields as filters for search view
			, 'created', 'modified', 'createdby', 'modifiedby', 'type', 'state'
		);
		$info->indexable_fields = array('categories', 'tags', 'type', 'select', 'selectmultiple', 'checkbox', 'checkboximage', 'radio', 'radioimage');

		return $info;
	}


	// Method to get used the properties supported by given field_type
	static function getPropertySupport($field_type, $iscore, $spname=null)
	{
		static $fi = null;
		if ($fi === null) $fi = FlexicontentFields::getPropertySupport_BuiltIn();

		static $cparams = null;
		if ($cparams === null) $cparams = JComponentHelper::getParams( 'com_flexicontent' );

		static $support_ft = array();
		if ( isset( $support_ft[$field_type] ) ) return !$spname ? $support_ft[$field_type] : $support_ft[$field_type]->{$spname};

		/**
		 * Try to load existing fields with field type
		 * Make sure that the Joomla plugin that implements the type of current flexi field, has been imported
		 */

		//JPluginHelper::importPlugin('flexicontent_fields', $field_type);
		$loaded = $field_type
			? FLEXIUtilities::call_FC_Field_Func($iscore ? 'core' : $field_type, null, null)
			: false;

		// Field type was found
		if ($loaded !== false)
		{
			// Get Methods implemented by the field
			$classname	= 'plgFlexicontent_fields'.($iscore ? 'core' : $field_type);
			$classmethods	= get_class_methods($classname);

			// SEARCH/FILTER related properties
			$supportsearch    = $iscore ? in_array($field_type, $fi->core_search)     : in_array('onIndexSearch', $classmethods);
			$supportfilter    = $iscore ? in_array($field_type, $fi->core_filters)    : in_array('onDisplayFilter', $classmethods);
			$supportadvsearch = $iscore ? in_array($field_type, $fi->core_advsearch)  : in_array('onIndexAdvSearch', $classmethods);
			$supportadvfilter = $iscore ? in_array($field_type, $fi->core_advfilters) : in_array('onAdvSearchDisplayFilter', $classmethods);

			// ITEM FORM related properties
			$supportuntranslatable = !$iscore || $field_type=='maintext';
			$supportvalueseditable = !$iscore || $field_type=='maintext';
			$supportformhidden     = !$iscore || $field_type=='maintext';
			$supportedithelp       = !$iscore || $field_type=='maintext';
		}

		// New fields without field type or field type not found / not installed
		else
		{
			// SEARCH/FILTER related properties
			$supportsearch    = false;
			$supportfilter    = false;
			$supportadvsearch = false;
			$supportadvfilter = false;

			// ITEM FORM related properties
			$supportuntranslatable = !$iscore;
			$supportvalueseditable = !$iscore;
			$supportformhidden     = !$iscore;
			$supportedithelp       = !$iscore;
		}

		// This property is usable only when Translation Groups are enabled
		$supportuntranslatable = $supportuntranslatable && flexicontent_db::useAssociations();

		$support_ft[$field_type] = new stdClass();
		$support_ft[$field_type]->supportsearch = $supportsearch;
		$support_ft[$field_type]->supportfilter = $supportfilter;
		$support_ft[$field_type]->supportadvsearch = $supportadvsearch;
		$support_ft[$field_type]->supportadvfilter = $supportadvfilter;
		$support_ft[$field_type]->supportuntranslatable = $supportuntranslatable;
		$support_ft[$field_type]->supportvalueseditable = $supportvalueseditable;
		$support_ft[$field_type]->supportformhidden = $supportformhidden;
		$support_ft[$field_type]->supportedithelp = $supportedithelp;

		return !$spname ? $support_ft[$field_type] : $support_ft[$field_type]->{$spname};
	}





	// *****************************************************************************
	// Common methods for - populating - the BASIC and ADVANCED search INDEX records
	// *****************************************************************************

	// Common method to create (insert) advanced search index DB records for various fields,
	// this can be called by fields or copied inside the field to allow better customization
	static function onIndexAdvSearch(&$field, &$values, &$item, $required_props=array(), $search_props=array(), $props_spacer=' ', $filter_func='strip_tags')
	{
		FlexicontentFields::createIndexRecords($field, $values, $item, $required_props, $search_props, $props_spacer, $filter_func, $for_advsearch=1);
	}


	// Common method to create basic text search index for various fields (added as the property field->search),
	// this can be called by fields or copied inside the field to allow better customization
	static function onIndexSearch(&$field, &$values, &$item, $required_props=array(), $search_props=array(), $props_spacer=' ', $filter_func='strip_tags')
	{
		FlexicontentFields::createIndexRecords($field, $values, $item, $required_props, $search_props, $props_spacer, $filter_func, $for_advsearch=0);
	}


	// Get a pdf parser for parsing the text of CSV files to be added to the search index
	static function getCSVParser()
	{
		$cparams = JComponentHelper::getParams('com_flexicontent');
		$index_csv_files = $cparams->get('index_csv_files', 0);
		$pdfparser_path = 'helpers';

		static $parser = null;
		if ($parser !== null) return $parser;
		$parser = false;

		if (!$index_csv_files || !$pdfparser_path) return $parser;

		// Create parser
		require_once (JPATH_SITE.DS.'components'.DS.'com_flexicontent'.DS.'classes'.DS.'helpers'.DS.'csv.php');
		$parser = new flexicontent_csv();

		return $parser;
	}


	// Get a pdf parser for parsing the text of PDF files to be added to the search index
	static function getPDFParser()
	{
		$cparams = JComponentHelper::getParams('com_flexicontent');
		$index_pdf_files = $cparams->get('index_pdf_files', 0);

		static $parser = null;

		if (!$index_pdf_files || $parser !== null)
		{
			return $parser;
		}

		$parser = false;

		/**
		 * Smalot\PdfParser (no longer used replaced with smaller alternative)
		 */
		/*
		$pdfparser_path = $cparams->get('pdfparser_path', 'components/com_flexicontent/librairies/pdfparser');
		jimport('joomla.filesystem.path');
		$pdfparser_path = JPATH::clean($pdfparser_path);

		if (!$pdfparser_path)
		{
			return $parser;
		}

		if (! is_dir($pdfparser_path) || ! is_readable($pdfparser_path))
		{
			// Try relative to Joomla path
			$_pdfparser_path = JPATH::clean(JPATH_SITE.DS.$pdfparser_path);
			if (! is_dir($_pdfparser_path) || ! is_readable($_pdfparser_path))
			{
				JFactory::getApplication()->enqueueMessage('PDF parser path does not seem to be exist and to be readable: '. $pdfparser_path .' please correct path');
				return $parser;
			}
			$pdfparser_path = $_pdfparser_path;
		}

		$vendor_path = JPATH::clean($pdfparser_path.DS.'vendor');
		if (! is_dir($vendor_path) || ! is_readable($vendor_path))
		{
			JFactory::getApplication()->enqueueMessage('PDF parser path does not seem to have installed dependent libraries in vendor subfolder: '. $vendor_path .' please run composer');
			return $parser;
		}

		// Create parser
		require_once(JPATH::clean($vendor_path.DS.'autoload.php'));
		require_once(JPATH::clean($pdfparser_path.DS.'src'.DS.'Smalot'.DS.'PdfParser'.DS.'Parser.php'));
		$parser = new \Smalot\PdfParser\Parser();

		return $parser;
		*/

		// Create parser
		require_once (JPATH_SITE.DS.'components'.DS.'com_flexicontent'.DS.'classes'.DS.'helpers'.DS.'pdf.php');
		$parser = new flexicontent_pdf();

		return $parser;
	}


	// Get a language specific handler for parsing the text to be added to the search index
	// e.g. doing word segmentation for a language that does not space-separate the words
	static function getLangHandler($language, $hasHandlerOnly = false)
	{
		// Currently we have dictionary only for Thai
		$language = $language === '*'
			? flexicontent_html::getSiteDefaultLang()
			: $language;

		if ($language != 'th-TH') return false;

		/**
		 * We will always try to split languages if handler exists, meaning that we will
		 * use "filter_word_like_any" only for languages that do not have dictionary splitting
		 */
		/*if ((int) JComponentHelper::getParams('com_flexicontent')->get('filter_word_like_any', 0) !== 0)
		{
			return false;
		}*/

		jimport('joomla.filesystem.file');
		$segmenter_path = JPATH_SITE.DS.'components'.DS.'com_flexicontent'.DS.'librairies'.DS.'THSplitLib'.DS.'segment.php';

		if (!JFile::exists($segmenter_path))
		{
			return false;
		}

		// Terminate here, if only checking that language splitter handler exists
		if ($hasHandlerOnly)
		{
			return true;
		}

		require_once ($segmenter_path);

		// Apply caching to dictionary parsing regardless of cache setting ...
		$handlercache = JFactory::getCache('com_flexicontent_lang_handlers');  // Get Joomla Cache of '... lang_handlers' Caching Group
		$handlercache->setCaching(1);         // Force cache ON
		$handlercache->setLifeTime(24*3600);  // Set expire time (hard-code this to 1 day), since it is costly
		$dictionary = $handlercache->get(
			array('Segment', 'loadDictionary'),
			array()
		);
		Segment::setDictionary($dictionary);
		$handler = new Segment();

		return $handler;
	}


	// Common method to create basic/advanced search index for various fields
	static function createIndexRecords(&$field, &$values, &$item, $required_props=array(), $search_props=array(), $props_spacer=' ', $filter_func='strip_tags', $for_advsearch=0)
	{
		$fi = FlexicontentFields::getPropertySupport($field->field_type, $field->iscore);
		$db = JFactory::getDbo();

		// * Per language handlers e.g. word segmenter objects (add spaces between words for language without spaces)
		static $lang_handlers = array();
		static $pdf_parser = null;
		static $csv_parser = null;
		static $search_prefix = null;
		static $indexed_pdfs = array();
		static $pdf_count = 0;

		// Get search prefix
		if ($search_prefix === null)
		{
			$search_prefix = JComponentHelper::getParams( 'com_flexicontent' )->get('add_search_prefix') ? 'vvv' : '';   // SEARCH WORD Prefix
		}

		// Get PDF parser for indexing PDF files
		if ($pdf_parser === null && !empty($field->field_isfile))
		{
			$pdf_parser = FlexicontentFields::getPDFParser();
		}

		// Get CSV parser for indexing CSV / Excel (TODO) files
		if ($csv_parser === null && !empty($field->field_isfile))
		{
			$csv_parser = FlexicontentFields::getCSVParser();
		}


		if (!$for_advsearch)
		{
			// Check if field type supports text search, this will also skip fields wrongly marked as text searchable
			if (!$fi->supportsearch || !$field->issearch)
			{
				$field->search = array();
				return;
			}
		}

		else
		{
			$field->ai_query_vals = array();

			// Check if field type supports advanced search text searchable or filterable, this will also skip fields wrongly marked
			if ( !($fi->supportadvsearch && $field->isadvsearch) && !($fi->supportadvfilter && $field->isadvfilter) )
			{
				return;
			}
		}

		/**
		 * A null indicates that we do not have posted data, instead indexer is running
		 * and we should retrieve values from the DB executing an SQL query
		 * BUT because we have customizations (for CORE fields),
		 * - like adding metakey to description field
		 * - like adding metakey to cats and tags field (also using falang values)
		 * we must always call method searchIndex_getFieldValues()
		 */
		if ($values === null || $field->iscore)
		{
			$items_values = FlexicontentFields::searchIndex_getFieldValues($field, $item, $for_advsearch);
		}
		else
		{
			$items_values = !is_array($values) ? array($values) : $values;
			$items_values = array($field->item_id => $items_values);
		}

		// Make sure posted data is an array
		$unserialize = isset($field->unserialize)
			? $field->unserialize
			: (count($required_props) || count($search_props));

		// Create the new search data
		foreach($items_values as $itemid => $item_values)
		{
			// Get item language: (a) multi-item indexing via the search indexer or (b) single item indexing via the item save task (e.g. item form)
			$language = isset($field->items_data) ? $field->items_data[$itemid]->language : $item->language;
			if ( !isset($lang_handlers[$language]) )
			{
				$lang_handlers[$language] = FlexicontentFields::getLangHandler($language);
			}
			$lang_handler = $lang_handlers[$language];

			if ( !empty($field->isindexed) && !$field->iscore )
			{
				// Get Elements of the field these will be cached if they do not depend on the item ...
				$field->item_id = $itemid;   // in case it needs to be loaded to replace item properties in a SQL query
				$item_pros = false;

				// We are creating search index, we need to force language to language of current item
				$iLang = $item ? $item->language : $field->items_data[$field->item_id]->language;
				$iLang = $iLang === '*' ? flexicontent_html::getSiteDefaultLang() : $iLang;

				$elements = FlexicontentFields::indexedField_getElements($field, $item, $field->extra_props, $item_pros, false, false, $iLang);
				// Map index field vlaues to their real properties
				$item_values = FlexicontentFields::indexedField_getValues($field, $elements, $item_values, $prepost_prop='');
			}

			$searchindex = array();
			foreach($item_values as $vi => $v)
			{
				// Make sure multi-property data are unserialized
				if ($unserialize && !is_array($v))
				{
					$array = flexicontent_db::unserialize_array($v, $force_array=false, $force_value=false);
					if ( $array!==false )
					{
						$v = $array;
					}
				}

				// Check value is not empty
				if ( !is_array($v) && !strlen($v) ) continue;

				// If has field 'required/search' properties, then check field is multi-property (value is array)
				if ( !is_array($v) && (count($required_props) || count($search_props)) ) continue;

				// Skip multi-property fields if search properties are not specified
				if ( is_array($v) && !count($search_props) ) continue;

				// Check required properties were specified
				$required_exists = true;
				foreach ($required_props as $cp)
				{
					if ( !strlen($v[$cp] ?? '') ) $required_exists = false;
				}
				if (!$required_exists) continue;

				// Create search value
				$search_value = array();
				foreach ($search_props as $sp)
				{
					if ( isset($v[$sp]) && strlen($v[$sp]) ) $search_value[] = $v[$sp];
				}

				// Support for indexing text in PDF files
				$err_msg = '';
				$pdf_indexing_aborted = false;
				if ( $pdf_parser && !empty($field->field_isfile) && strtolower(flexicontent_upload::getExt($v['filename'])) == 'pdf' )
				{
					$abspath = JPath::clean( ($v['secure'] ? COM_FLEXICONTENT_FILEPATH : COM_FLEXICONTENT_MEDIAPATH) .DS. $v['filename'] );  //echo $abspath . "<br/>";  					//echo "<pre>"; print_r($v); echo "</pre>";
					if ( isset($indexed_pdfs[$abspath]) )
					{
						if (strlen($indexed_pdfs[$abspath])) $search_value[] = $indexed_pdfs[$abspath];
					}
					else
					{
						try {
							//JFactory::getApplication()->enqueueMessage(($for_advsearch ? 'Parsing (ADV Index)' : 'Parsing (BASIC Index)') . ': ' . $abspath, 'message');
							$pdf_count++;
							if ($pdf_count % 5 == 0)  gc_collect_cycles();   // Call garbage collector every nnn PDF file parsings
							$pdf_data = @ $pdf_parser->parseFile($abspath);
							$search_value[] = $indexed_pdfs[$abspath] = @ $pdf_data->getText();
						}
						catch (Exception $e) {
							$pdf_indexing_aborted = true;
							$indexed_pdfs[$abspath] = '';
							$err_msg = '';
							if (JFactory::getApplication()->isClient('administrator') && ($last_error = error_get_last()))
							{
								$err_msg .= implode(' ', error_get_last()) . ' <br/> ';
								if (function_exists('error_clear_last')) error_clear_last();
							}
							$err_msg .= $e->getMessage();
						}
					}
				}

				if ($pdf_indexing_aborted)
				{
					JFactory::getApplication()->enqueueMessage('<b>' . $v['filename'] . '</b> : ' . JText::_('FLEXI_PATH_PDF_PARSING_NOT_SUPPORTED_BY_SEARCH_INDEXING'), 'notice');
				}
				if ($err_msg && JDEBUG)
				{
					JFactory::getApplication()->enqueueMessage($err_msg, 'warning');
				}

				// Support for indexing text in CSV / Excel files
				$err_msg = '';
				$csv_indexing_aborted = false;
				if ( $csv_parser && !empty($field->field_isfile) && strtolower(flexicontent_upload::getExt($v['filename'])) == 'csv' )
				{
					$abspath = JPath::clean( ($v['secure'] ? COM_FLEXICONTENT_FILEPATH : COM_FLEXICONTENT_MEDIAPATH) .DS. $v['filename'] );  //echo $abspath . "<br/>";  					//echo "<pre>"; print_r($v); echo "</pre>";
					if ( isset($indexed_csvs[$abspath]) )
					{
						if (strlen($indexed_csvs[$abspath])) $search_value[] = $indexed_csvs[$abspath];
					}
					else
					{
						try {
							//JFactory::getApplication()->enqueueMessage(($for_advsearch ? 'Parsing (ADV Index)' : 'Parsing (BASIC Index)') . ': ' . $abspath, 'message');
							$csv_count++;
							if ($csv_count % 5 == 0)  gc_collect_cycles();   // Call garbage collector every nnn CSV file parsings
							$csv_data = @ $csv_parser->parseFile($abspath);
							$search_value[] = $indexed_csvs[$abspath] = @ $csv_data->getText();
						}
						catch (Exception $e) {
							$csv_indexing_aborted = true;
							$indexed_csvs[$abspath] = '';
							$err_msg = '';
							if (JFactory::getApplication()->isClient('administrator') && ($last_error = error_get_last()))
							{
								$err_msg .= implode(' ', error_get_last()) . ' <br/> ';
								if (function_exists('error_clear_last')) error_clear_last();
							}
							$err_msg .= $e->getMessage();
						}
					}
				}
				if ($csv_indexing_aborted)
				{
					JFactory::getApplication()->enqueueMessage('<b>' . $v['filename'] . '</b> : ' . JText::_('FLEXI_PATH_CSV_PARSING_NOT_SUPPORTED_BY_SEARCH_INDEXING'), 'notice');
				}
				if ($err_msg && JDEBUG)
				{
					JFactory::getApplication()->enqueueMessage($err_msg, 'warning');
				}

				if (count($search_props) && !count($search_value)) continue;  // all search properties were empty, skip this value
				$searchindex[$vi] = (count($search_props))  ?  implode($props_spacer, $search_value)  :  $v;

				// Do a custom stripping of tags on the text
				if ($filter_func == 'strip_tags')
				{
					$searchindex[$vi] = flexicontent_html::striptagsandcut( $searchindex[$vi] );
				}
				else
				{
					$searchindex[$vi] = $filter_func ? $filter_func($searchindex[$vi]) : $searchindex[$vi];
				}
			}

			// if (!empty($pdf_data)) { echo "<pre>"; print_r($searchindex); exit; }
			// if (!empty($csv_data)) { echo "<pre>"; print_r($searchindex); exit; }


			// * Use word segmenter (if it was created) to add spaces between words
			if ($lang_handler)
			{
				foreach($searchindex as $i => $_searchindex)
				{
					$searchindex[$i] = implode(' ', $lang_handler->get_segment_array($clear_previous = true, $_searchindex));
				}
			}

			/**
			 * Note for basic index, the search prefix will be added by the indexer of the controller
			 */
			if ( !$for_advsearch )
			{
				// Do not add (yet) search prefix to the words, it will be added by controller
				$field->search[$itemid] = implode(' | ', $searchindex);
			}

			/**
			 * Note for advanced index, we will add the search prefix here because we are creating SQL clauses
			 */
			else
			{
				$n = 0;
				foreach ($searchindex as $vi => $search_text)
				{
					// Add search prefix to the words
					if ($search_prefix)
					{
						$search_text = preg_replace('/(\b[^\s,\.]+\b)/u', $search_prefix.'$0', $search_text);
					}

					// Add new search value into the DB
					$query_val = "( "
						.$field->id. "," .$itemid. "," .($n++). "," .$db->Quote($search_text). "," .$db->Quote($vi).
					")";
					$field->ai_query_vals[] = $query_val;
				}
			}
		}

		//if ($field->id==NN) echo $field->name . ": " . print_r($values, true) . "<br/>";
		//if ($field->id==NN) if ( !empty($searchindex) ) echo implode(' | ', $searchindex) ."<br/><br/>";
	}


	// Method to retrieve field values to be used for creating search indexes
	static function searchIndex_getFieldValues(&$field, &$item, $for_advsearch=0)
	{
		$db = JFactory::getDbo();
		$_s = $for_advsearch ? '_s' : '';

		static $nullDate = null;
		static $valCols = array();
		static $txtCols = array();
		static $state_names = null;
		if (!$state_names) $state_names = array(
			1=>JText::_('FLEXI_PUBLISHED'), -5=>JText::_('FLEXI_IN_PROGRESS'), 0=>JText::_('FLEXI_UNPUBLISHED'), -3=>JText::_('FLEXI_PENDING'),
			-4=>JText::_('FLEXI_TO_WRITE'), 2=>JText::_('FLEXI_ARCHIVED'), -2=>JText::_('FLEXI_TRASHED')
		);

		// Create DB query to retrieve field values
		$values = null;
		switch ($field->field_type)
		{
		case 'title':
			$query  = 'SELECT i.title AS value, i.id AS itemid'
				.' FROM #__content AS i'
				.' WHERE i.id IN ('.(@$field->query_itemids ? implode(',', $field->query_itemids) : $field->item_id) .')';
			break;

		case 'maintext':
			$query  = 'SELECT CONCAT_WS(\' \', i.introtext, i.fulltext, i.metakey) AS value, i.id AS itemid'
				.' FROM #__content AS i'
				.' WHERE i.id IN ('.(@$field->query_itemids ? implode(',', $field->query_itemids) : $field->item_id) .')';
			break;

		case 'categories':
			$query  = 'SELECT c.id AS value_id, rel.itemid AS itemid, '
				. (FLEXI_FALANG
					? 'CONCAT_WS(\' \',
						CASE WHEN (fat.value IS NOT NULL) THEN fat.value ELSE c.title END, 
						CASE WHEN (fak.value IS NOT NULL) THEN fak.value ELSE c.metakey END
					)'
					: 'CONCAT_WS(\' \', c.title, c.metakey)'
				) . ' AS value '
				.' FROM #__categories AS c'
				.' JOIN #__flexicontent_cats_item_relations AS rel ON c.id=rel.catid'
				. (!FLEXI_FALANG ? '' :
					' LEFT JOIN #__content AS im ON im.id = rel.itemid ' .
					' LEFT JOIN #__languages AS la ON la.lang_code = im.language ' .
					' LEFT JOIN #__falang_content AS fat ON fat.reference_table = "categories" ' .
						' AND fat.reference_field = "title" AND fat.reference_id = c.id AND fat.language_id = la.lang_id' .
					' LEFT JOIN #__falang_content AS fak ON fak.reference_table = "categories" ' .
						' AND fak.reference_field = "metakey" AND fak.reference_id = c.id AND fak.language_id = la.lang_id'
				)
				.' WHERE c.id<>0 AND rel.itemid IN ('.(@$field->query_itemids ? implode(',', $field->query_itemids) : $field->item_id) .')';
			break;

		case 'tags':
			$query  = 'SELECT t.id AS value_id, rel.itemid AS itemid, '
				. (FLEXI_FALANG
					? 'CONCAT_WS(\' \',
						CASE WHEN (fat.value IS NOT NULL) THEN fat.value ELSE t.name END, 
						CASE WHEN (fak.value IS NOT NULL) THEN fak.value ELSE jt.metakey END
					)'
					: 'CONCAT_WS(\' \', t.name, jt.metakey)'
				) . ' AS value '
				.' FROM #__flexicontent_tags AS t'
				.' JOIN #__flexicontent_tags_item_relations AS rel ON t.id=rel.tid'
				.' LEFT JOIN #__tags AS jt ON t.jtag_id = jt.id'
				. (!FLEXI_FALANG ? '' :
					' LEFT JOIN #__content AS im ON im.id = rel.itemid ' .
					' LEFT JOIN #__languages AS la ON la.lang_code = im.language ' .
					' LEFT JOIN #__falang_content AS fat ON fat.reference_table = "tags" ' .
						' AND fat.reference_field = "title" AND fat.reference_id = t.jtag_id AND fat.language_id = la.lang_id' .
					' LEFT JOIN #__falang_content AS fak ON fak.reference_table = "tags" ' .
						' AND fak.reference_field = "metakey" AND fak.reference_id = t.jtag_id AND fak.language_id = la.lang_id'
				)
				.' WHERE t.id<>0 AND rel.itemid IN ('.(@$field->query_itemids ? implode(',', $field->query_itemids) : $field->item_id) .')';
			break;

		case 'type':
			$textcol = ', t.name AS value';
			$query 	= ' SELECT t.id AS value_id '.$textcol.', ext.item_id AS itemid'
				. ' FROM #__flexicontent_types AS t'
				.' JOIN #__flexicontent_items_ext AS ext ON t.id=ext.type_id '
				.' WHERE t.id<>0 AND ext.item_id IN ('.(@$field->query_itemids ? implode(',', $field->query_itemids) : $field->item_id) .')';
			break;

		case 'state':
			$textcol = ', CASE i.state';
			foreach($state_names as $_id => $_name)  $textcol .= ' WHEN '.$_id.' THEN '.$db->Quote($_name);
			$textcol .= ' ELSE '.$db->Quote("unknown").' END AS value';
			$query 	= ' SELECT i.state AS value_id '.$textcol.', i.id AS itemid'
				.' FROM #__content AS i'
				.' WHERE i.id IN ('.(@$field->query_itemids ? implode(',', $field->query_itemids) : $field->item_id) .')';
			break;

		case 'created': case 'modified':
			if ($nullDate===null) $nullDate	= $db->getNullDate();

			if (!isset($valCols[$field->field_type]))
			{
				$display_filter_as = (int) $field->parameters->get('display_filter_as'.$_s, 0);
				$date_filter_group = $field->parameters->get('date_filter_group'.$_s, 'month');
				$filter_as_age = $field->parameters->get('filter_as_age'.$_s, 0);

				if ($date_filter_group === 'year')
				{
					$date_valformat  = '%Y';
					$filter_age_type = 'YEAR';
				}
				elseif ($date_filter_group === 'month')
				{
					$date_valformat  = '%Y-%m';
					$filter_age_type = 'MONTH';
				}
				else
				{
					$date_valformat  = '%Y-%m-%d';
					$filter_age_type = 'DAY';
				}

				if (in_array($display_filter_as, array(1, 3)))
				{
					$date_valformat  = '%Y-%m-%d';
					$filter_age_type = 'DAY';
				}

				// Display date 'label' can be different than the (aggregated) date value
				$date_filter_label_format = $field->parameters->get('date_filter_label_format'.$_s, '');
				$date_txtformat = $date_filter_label_format ? $date_filter_label_format : $date_valformat;  // If empty then same as value

				if (!$filter_as_age)
				{
					$valCols[$field->field_type] = sprintf(' DATE_FORMAT(i.%s, "%s") ', $field->field_type, $date_valformat);
					$txtCols[$field->field_type] = sprintf(' DATE_FORMAT(i.%s, "%s") ', $field->field_type, $date_txtformat);
				}
				else
				{
					$valCols[$field->field_type] = sprintf(' TIMESTAMPDIFF(' . $filter_age_type . ', i.%s, CURDATE()) ', $field->field_type);
					$txtCols[$field->field_type] = sprintf(' TIMESTAMPDIFF(' . $filter_age_type . ', i.%s, CURDATE()) ', $field->field_type);
				}
			}

			$valuecol = $valCols[$field->field_type];
			$textcol  = $txtCols[$field->field_type];

			$query 	= 'SELECT '.$valuecol.' AS value_id, '.$textcol.' AS value, i.id AS itemid'
				.' FROM #__content AS i'
				.' WHERE i.'.$field->name.'<>'.$db->Quote($nullDate).' AND i.id IN ('.(@$field->query_itemids ? implode(',', $field->query_itemids) : $field->item_id) .')';
			break;

		case 'createdby': case 'modifiedby':
			$textcol = ', u.name AS value';
			$query 	= ' SELECT u.id AS value_id '.$textcol.', i.id AS itemid'
				. ' FROM #__users AS u'
				.' JOIN #__content AS i ON i.'.$field->name.' = u.id'
				.' WHERE u.id<>0 AND i.id IN ('.(@$field->query_itemids ? implode(',', $field->query_itemids) : $field->item_id) .')';
			break;

		default:
			if ($field->iscore) $values=array(); //die('Field Type: '.$field->field_type.' does not support FLEXIcontent Advanced Search Indexing');
			$valuesselect = @$field->field_valuesselect ? $field->field_valuesselect : ' fi.value AS value ';
			$valuesjoin   = @$field->field_valuesjoin   ? $field->field_valuesjoin : '';
			$valueswhere  = @$field->field_valueswhere  ? $field->field_valueswhere  : ' AND fi.field_id ='.$field->id;

			$item_id_col = @$field->field_item_id_col ? $field->field_item_id_col : ' fi.item_id ';
			$groupby     = @$field->field_groupby ? $field->field_groupby .', '.$item_id_col : ' GROUP BY fi.value, '.$item_id_col;

			$valuesfrom = !empty($field->field_valuesfrom)
				? $field->field_valuesfrom
				:	' FROM #__flexicontent_fields_item_relations as fi ' .
					' JOIN #__content as i ON i.id=fi.item_id ';

			$query = 'SELECT '.$valuesselect.', '.$item_id_col.' AS itemid'
				. $valuesfrom
				. $valuesjoin
				.' WHERE 1 '
				. $valueswhere
				.' AND '.$item_id_col.' IN ('.(@$field->query_itemids ? implode(',', $field->query_itemids) : $field->item_id) .')'
				. $groupby;
			break;
		}

		//if ($field->id==NN) echo $query;
		//if ($field->id==NN) exit;

		// Execute query if not already done to load a single value column with no value id
		$_raw = !empty($field->field_rawvalues);
		if ($values === null)
		{
			$db->setQuery($query);
			$_values = $db->loadObjectList();
			$values = array();
			if ($_values) foreach($_values as $v)
			{
				if (isset($v->value_id))
					$values[$v->itemid][$v->value_id] = $_raw ? (array) $v : (isset($v->value) ? $v->value : $v->value_id);
				else
					$values[$v->itemid][] = $_raw ? (array) $v : $v->value;
			}
		}

		return $values;
	}





	// ********************************************************************************************
	// Methods for - MATCHING - Field Filters of FC views, (thus limiting the current ITEM LISTING)
	// ********************************************************************************************

	// Private Method to create a generic matching of filter
	static function createFilterValueMatchSQL(&$filter, &$value, $is_full_text=0, $is_search=0, $colname='')
	{
		static $search_prefix = null;
		if ($search_prefix === null) $search_prefix = JComponentHelper::getParams( 'com_flexicontent' )->get('add_search_prefix') ? 'vvv' : '';   // SEARCH WORD Prefix
		$_search_prefix = $colname=='fs.search_index' ? $search_prefix : '';

		//echo 'filter_id: ' . $filter->id . '<br>';
		//echo '<pre>'; print_r($value); echo '</pre>';

		// ***
		// *** Filter out zero-length values
		// ***

		// *** Force array
		if (!is_array($value))
		{
			$value = array($value);
		}

		$_value = array();

		foreach ($value as $i => $v)
		{
			if (is_array($v))
			{
				// Indirect fitering ... for filtering on values of fields of related items
				if (!is_integer($i))
				{
					$_value[$i] = $v;
				}
				elseif ($i < 0)
				{
					$_value = $v;
				}
				break;
			}
			else
			{
				$v = trim($v);

				if (strlen($v))
				{
					$_value[$i] = $v;
				}
			}
		}
		$value = $_value;

		// No values were given
		if (!count($value))
		{
			return '';
		}

		$db = JFactory::getDbo();
		$display_filter_as = (int) $filter->parameters->get( $is_search ? 'display_filter_as_s' : 'display_filter_as', 0 );
		$filter_compare_type = (int) $filter->parameters->get( 'filter_compare_type', 0 );

		$isDate = in_array($filter->field_type, array('date','created','modified')) || $filter->parameters->get('isdate',0);
		$isRange = in_array( $display_filter_as, array(2,3,8) );
		$isTextInput = $display_filter_as==1 || $display_filter_as==3;
		$quoted = false;

		if (!isset($value[0]) && (isset($value[1]) || isset($value[1])))
		{
			$display_filter_as = $isRange ? $display_filter_as : 2;
			$isRange = true;
		}

		$require_all_values = $filter->parameters->get( 'filter_values_require_all', 0 );
		$require_all_in_SQL = is_array($value) && count($value) > 1 && !$isRange   // prevent require_all for known ranges
			? $require_all_values
			: 0;
		//echo "createFilterValueMatchSQL : filter name: ".$filter->name." Filter Type: ".$display_filter_as." Values: "; print_r($value); echo "<br>";


		// ***
		// *** Extra preparation date filter range, to support date offsets
		// ***

		if ($isDate && !isset($value[0]))
		{
			if (
				(isset($value[1]) && strpos(trim($value[1]), 'now') === 0) ||
				(isset($value[2]) && strpos(trim($value[2]), 'now') === 0)
			) {
				require_once (JPATH_SITE.DS.'modules'.DS.'mod_flexicontent'.DS.'classes'.DS.'datetime.php');
				$v = array();

				if (!empty($value[1]))
				{
					$value[1] = preg_replace('/now\s*/', '', $value[1]);
					$shift = array(
						0 => preg_replace("/[^-+0-9\s]/", "", $value[1]),
						1 => preg_replace("/[0-9-+\s]/", "", $value[1])
					);
					$value[1] = date_time::shift_dates('', $shift[0], $shift[1]);
				}

				if (!empty($value[2]))
				{
					$value[2] = preg_replace('/now\s*/', '', $value[2]);
					$shift = array(
						0 => preg_replace("/[^-+0-9\s]/", "", $value[2]),
						1 => preg_replace("/[0-9-+\s]/", "", $value[2])
					);
					$value[2] = date_time::shift_dates('', $shift[0], $shift[1]);
				}

				// Update filter value to calculated value
				JFactory::getApplication()->input->set('filter_'.$filter->id, $value);
			}
		}


		// ***
		// *** Extra preparation date / text search inputs with custom value formats
		// ***

		if (isset($filter->filter_valueformat))
		{
			$value_suffix = '';

			if ($isDate)
			{
				$date_filter_group = $filter->parameters->get('date_filter_group', 'month');
				$filter_as_age     = $filter->parameters->get('filter_as_age', 0);

				if (!$filter_as_age && !in_array($display_filter_as, array(1, 3)))
				{
					switch($date_filter_group)
					{
						case 'year':
							$value_suffix = '-1-1';
							break;
						case 'month':
							$value_suffix = '-1';
							break;
					}
				}
			}

			foreach ($value as $i => $val)
			{
				$typecasted_val = !$filter_compare_type
					? $db->Quote($value[$i] . $value_suffix)
					: ($filter_compare_type === 1 ? intval($value[$i]) : floatval($value[$i]));

				$value[$i] = str_replace('__filtervalue__', $typecasted_val, $filter->filter_valueformat);
			}

			$quoted = true;
		}


		// ***
		// *** Create the VALUEs WHERE clause
		// ***

		$valueswhere = '';
		if ($isRange)
		{
			// RANGE cases: 2, 3, 8
			if ( empty($quoted) )
			{
				foreach ($value as $i => $v)
				{
					$value[$i] = !$filter_compare_type
						? $db->Quote( preg_replace('(\w+)', $_search_prefix.'$0', $v) )
						: ($filter_compare_type==1 ? intval($v) : floatval($v));
				}
			}
			$reverse_values = $filter->parameters->get( 'reverse_filter_order', 0) && $display_filter_as == 8;
			$value1 = $reverse_values ? @$value[2] : @$value[1];
			$value2 = $reverse_values ? @$value[1] : @$value[2];
			$value_empty = !strlen(@$value[1]) && strlen(@$value[2]) ? ' OR _v_="" OR _v_ IS NULL' : '';
			if ( strlen($value1) ) $valueswhere .= ' AND (_v_ >=' . $value1 . ')';
			if ( strlen($value2) ) $valueswhere .= ' AND (_v_ <=' . $value2 . $value_empty . ')';
		}

		// non-text, aka EXACT value cases: 0, 4, 5, 6, 7, * -OR- isDate
		elseif ($display_filter_as !== 1 || $isDate)
		{
			$value_clauses = array();

			if (!$require_all_in_SQL)
			{
				foreach ($value as $val)
				{
					if (!is_array($val))
					{
						$value_clauses[] = $quoted
							? '_v_=' . $val
							: '_v_=' . $db->Quote( preg_replace('(\w+)', $_search_prefix.'$0', $val) );
					}
					else
					{
						foreach ($val as $vv)
						{
							$value_clauses[] = $quoted
								? '_v_=' . $vv
								: '_v_=' . $db->Quote( preg_replace('(\w+)', $_search_prefix.'$0', $vv) );
						}
					}
				}

				if ($value_clauses)
				{
					$valueswhere .= ' AND ('.implode(' OR ', $value_clauses).') ';
				}
			}
			else
			{
				foreach ($value as $val)
				{
					if (!is_array($val))
					{
						$value_clauses[] = $quoted
							? $val
							: $db->Quote( preg_replace('(\w+)', $_search_prefix.'$0', $val) );
					}
					else
					{
						foreach ($val as $vv)
						{
							$value_clauses[] = $quoted
								? $vv
								: $db->Quote( preg_replace('(\w+)', $_search_prefix.'$0', $vv) );
						}
					}
				}

				if ($value_clauses)
				{
					$valueswhere = ' AND _v_ IN ('. implode(',', $value_clauses) .')';
				}
			}
		}

		// SINGLE TEXT select value cases
		else
		{
			if (!empty($filter->filter_valueexact))
			{
				$valueswhere .= ' AND _v_=' . $db->Quote( preg_replace('(\w+)', $_search_prefix.'$0', $value[0]) );
			}
			else
			{
				// DO NOT put % in front of the value since this will force a full table scan instead of indexed column scan
				$_value_like = preg_replace('(\w+)', $_search_prefix.'$0'.($is_full_text ? '*' : '%'), $value[0]);
				if (empty($quoted))
				{
					$_value_like = $db->Quote($_value_like);
				}
				$valueswhere .= $is_full_text
					? ' AND  MATCH (_v_) AGAINST ('.$_value_like.' IN BOOLEAN MODE)'
					: ' AND _v_ LIKE ' . $_value_like;
			}
		}

		//echo $valueswhere . "<br>";
		return $valueswhere;
	}


	// Method to get the active filter result for Content Lists Views (an SQL where clause part OR an array of item ids, matching field filter)
	static function getFiltered( &$filter, $value, $return_sql=true )
	{
		$db = JFactory::getDbo();

		// Check if field type supports advanced search
		$support = FlexicontentFields::getPropertySupport($filter->field_type, $filter->iscore);
		if ( ! $support->supportfilter )  return null;

		$display_filter_as = $filter->parameters->get( 'display_filter_as', 0 );
		$is_full_text = !empty($filter->is_full_text) ? $filter->is_full_text : $display_filter_as == 1;

		$valueswhere = !empty($filter->filter_valuewhere)
			? $filter->filter_valuewhere
			: FlexicontentFields::createFilterValueMatchSQL($filter, $value, $is_full_text, $is_search=0);

		if (!$valueswhere)
		{
			return;
		}

		$idname = !empty($filter->filter_valuesjoin)
			? 'c.id'
			: 'rel.item_id';
		$colname = !empty($filter->filter_colname)
			? $filter->filter_colname
			: 'value';
		$valueswhere = str_replace('_v_', $colname, $valueswhere);

		$valuesfrom = !empty($filter->filter_valuesfrom)
			? $filter->filter_valuesfrom
			: null;
		$valuesjoin  = !empty($filter->filter_valuesjoin)
			? $filter->filter_valuesjoin
			: null;

		// Decide to require all values

		$isRange = in_array( $display_filter_as, array(2,3,8) );
		$require_all_values = $filter->parameters->get( 'filter_values_require_all', 0 );
		$require_all_in_SQL = is_array($value) && count($value) > 1 && !$isRange   // prevent require_all for known ranges
			? $require_all_values
			: 0;

		$query = 'SELECT '.($require_all_in_SQL ? $idname : 'DISTINCT ' . $idname);

		// We have a special values join, by default, select from the content table
		if ($valuesjoin)
		{
			$query .= ($valuesfrom ?: ' FROM #__content c')
				. $valuesjoin
				. ' WHERE 1'
				. $valueswhere ;
		}

		// No special values join, by default, select from the values table
		else
		{
			$query .= ($valuesfrom ?: ' FROM #__flexicontent_fields_item_relations as rel')
				. ' WHERE rel.field_id=' . $filter->id
				. $valueswhere ;
		}

		// TEST FILTER VALUE MATCH SQL
		//echo $filter->label . '<pre>' . $query . '</pre>';

		if ($require_all_in_SQL)
		{
			/*
			 * Faceted filter calculation when all values required
			 * Use HAVING COUNT(DISTINCT value), only if require all is enabled
			 * this because the query is slower
			 */
			$query .= ''
				. ' GROUP BY ' . $idname . ' HAVING COUNT(DISTINCT value) >= ' . count($value)
				. ' ORDER BY NULL';  // THIS should remove filesort in MySQL, and improve performance issue of REQUIRE ALL
		}

		//$query .= ' GROUP BY id';   // BAD PERFORMANCE ?

		if ( !$return_sql )
		{
			//echo "<br>GET FILTERED Items (helper func) -- [".$filter->name."] using in-query ids :<br>". $query."<br>\n";
			$db->setQuery($query);
			$filtered = $db->loadColumn();
			return $filtered;
		}

		else if ($return_sql===2)
		{
			static $iids_tblname  = array();

			$tbl_id = $filter->id . (isset($filter->filt_prop_name) ? '_' . $filter->filt_prop_name : '');

			if (!isset($iids_tblname[$tbl_id]))
			{
				$iids_tblname[$tbl_id] = 'fc_filter_iids_' . $tbl_id;
			}

			$tmp_tbl = $iids_tblname[$tbl_id];

			try {
				// Use sub-query on temporary table
				$db->setQuery('CREATE TEMPORARY TABLE IF NOT EXISTS '.$tmp_tbl.' (id INT, KEY(`id`))');
				$db->execute();
				$db->setQuery('TRUNCATE TABLE '.$tmp_tbl);
				$db->execute();
				$db->setQuery('INSERT INTO '.$tmp_tbl.' '.$query);
				//echo $query; exit;
				$db->execute();
				$_query = $query;
				$query = 'SELECT id FROM '.$tmp_tbl;   //echo "<br/><br/>GET FILTERED Items (helper func) -- [".$filter->name."] using temporary table: ".$query." for :".$_query ." <br/><br/>";
				/*$db->setQuery($query);
				$data = $db->loadObjectList();
				echo "<pre>";
				print_r($data);
				exit;*/
			}
			catch (Exception $e) {
				// Ignore table creation error
				//echo 'SQL QUERY ERROR:<br/>'.nl2br($e->getMessage());
				//echo "<br/><br/>GET FILTERED Items (helper func) -- [".$filter->name."] using subquery: ".$query." <br/><br/>";
			}
		}
		else
		{
			//echo "<br/><br/>GET FILTERED Items (helper func) -- [".$filter->name."] using subquery: ".$query." <br/><br/>";
		}
		return ' AND i.id IN ('. $query .')';
	}


	// Method to get the active filter result Search View (an SQL where clause part OR an array of item ids, matching field filter)
	static function getFilteredSearch( &$filter, $value, $return_sql=true )
	{
		$app = JFactory::getApplication();
		$db  = JFactory::getDbo();

		// Check if field type supports advanced search
		$support = FlexicontentFields::getPropertySupport($filter->field_type, $filter->iscore);
		if ( ! $support->supportadvsearch && ! $support->supportadvfilter )  return null;

		// Decide to require all values
		$display_filter_as = $filter->parameters->get( 'display_filter_as_s', 0 );

		$isDate = in_array($filter->field_type, array('date','created','modified')) || $filter->parameters->get('isdate',0);
		$isRange = in_array( $display_filter_as, array(2,3,8) );
		$isTextInput = $display_filter_as==1 || $display_filter_as==3;

		$require_all_values = $filter->parameters->get( 'filter_values_require_all', 0 );
		$require_all_in_SQL = is_array($value) && count($value) > 1 && !$isRange   // prevent require_all for known ranges
			? $require_all_values
			: 0;

		$colname = (!empty($filter->isindexed) && !$isTextInput) || $isDate
			? 'fs.value_id'
			: 'fs.search_index';

		// Create where clause for matching the filter's values
		$valueswhere = FlexicontentFields::createFilterValueMatchSQL($filter, $value, $is_full_text=1, $is_search=1, $colname);
		if ( !$valueswhere )  return;
		$valueswhere = str_replace('_v_', $colname, $valueswhere);

		$field_tbl = 'flexicontent_advsearch_index_field_'.$filter->id;
		$query = 'SHOW TABLES LIKE "' . $app->getCfg('dbprefix') . $field_tbl . '"';
		$db->setQuery($query);
		$tbl_exists = (boolean) count($db->loadObjectList());
		$field_tbl = $tbl_exists ? $field_tbl : 'flexicontent_advsearch_index';

		// Get ALL items that have such values for the given field
		$query = 'SELECT '.($require_all_in_SQL ? 'fs.item_id' : 'DISTINCT fs.item_id')
			.' FROM #__'.$field_tbl.' AS fs'
			.' WHERE fs.field_id=' . $filter->id
			. $valueswhere ;
		if ($require_all_in_SQL)
		{
			/*
			 * Faceted filter calculation when all values required
			 * Use HAVING COUNT(DISTINCT value), only if require all is enabled
			 * this because the query is slower
			 */
			$query .= ''
				. ' GROUP BY fs.item_id ' . ' HAVING COUNT(DISTINCT value) >= ' . count($value)
				. ' ORDER BY NULL';  // THIS should remove filesort in MySQL, and improve performance issue of REQUIRE ALL
		}
		//echo 'Filter ['. $filter->label .']: '. $query."<br/><br/>\n";

		if ( !$return_sql ) {
			//echo "<br>GET FILTERED Items (helper func) -- [".$filter->name."] using in-query ids : ". $query."<br>\n";
			$db->setQuery($query);
			$filtered = $db->loadColumn();
			return $filtered;
		}
		else if ($return_sql===2)
		{
			static $iids_tblname  = array();

			$tbl_id = $filter->id . (isset($filter->filt_prop_name) ? '_' . $filter->filt_prop_name : '');

			if (!isset($iids_tblname[$tbl_id]))
			{
				$iids_tblname[$tbl_id] = 'fc_filter_iids_' . $tbl_id;
			}

			$tmp_tbl = $iids_tblname[$tbl_id];

			try {
				// Use sub-query on temporary table
				$db->setQuery('CREATE TEMPORARY TABLE IF NOT EXISTS '.$tmp_tbl.' (id INT, KEY(`id`))');
				$db->execute();
				$db->setQuery('TRUNCATE TABLE '.$tmp_tbl);
				$db->execute();
				$db->setQuery('INSERT INTO '.$tmp_tbl.' '.$query);
				//echo $query; exit;
				$db->execute();
				$_query = $query;
				$query = 'SELECT id FROM '.$tmp_tbl;   //echo "<br/><br/>GET FILTERED Items (helper func) -- [".$filter->name."] using temporary table: ".$query." for :".$_query ." <br/><br/>";
				/*$db->setQuery($query);
				$data = $db->loadObjectList();
				echo "<pre>";
				print_r($data);
				exit;*/
			}
			catch (Exception $e) {
				// Ignore table creation error
				//echo 'SQL QUERY ERROR:<br/>'.nl2br($e->getMessage());
				//echo "<br/><br/>GET FILTERED Items (helper func) -- [".$filter->name."] using subquery: ".$query." <br/><br/>";
			}
		} else {
			//echo "<br/><br/>GET FILTERED Items (helper func) -- [".$filter->name."] using subquery: ".$query." <br/><br/>";
		}
		return ' AND i.id IN ('. $query .')';
	}



	// **********************************************
	// Methods for creating Field Filters of FC views
	// **********************************************

	// Method to create a category (content list) or search filter
	static function createFilter(&$filter, $value='', $formName='adminForm', $indexed_elements=false, $search_prop='')
	{
		static $faceted_overlimit_msg = null;

		$app = JFactory::getApplication();
		$user = JFactory::getUser();
		$cparams = JComponentHelper::getParams('com_flexicontent');  // createFilter maybe called in backend too ...
		$print_logging_info = $cparams->get('print_logging_info');

		$option = $app->input->get('option', '', 'cmd');
		$view   = $app->input->get('view', '', 'cmd');

		$isFC = $option === 'com_flexicontent';
		$isCategoryView = $isFC && $view === 'category';
		$isSearchView   = $isFC && $view === 'search';

		if ( $print_logging_info )
		{
			global $fc_run_times;
			$start_microtime = microtime(true);
		}

		// Apply caching to filters regardless of cache setting ...
		if (FLEXI_CACHE)
		{
			$itemcache = JFactory::getCache('com_flexicontent_filters');  // Get Joomla Cache of '...items' Caching Group
			$itemcache->setCaching(1); 		              // Force cache ON
			$itemcache->setLifeTime(FLEXI_CACHE_TIME); 	// Set expire time (default is 1 hour)
		}

		$isDate = in_array($filter->field_type, array('date','created','modified')) || $filter->parameters->get('isdate',0);
		$_s = $isSearchView ? '_s' : '';

		// Some parameter shortcuts
		$label_filter = $filter->parameters->get( 'display_label_filter'.$_s, 0 ) ;   // How to show filter label
		$faceted_filter = $filter->parameters->get( 'faceted_filter'.$_s, 2);
		$display_filter_as = $filter->parameters->get( 'display_filter_as'.$_s, 0 );  // Filter Type of Display

		$isSlider = $display_filter_as == 7 || $display_filter_as == 8;
		$slider_display_config = $filter->parameters->get( 'slider_display_config'.$_s, 1 );  // Slider found values: 1 or custom values/labels: 2

		// Make sure the current filtering values match the field filter configuration to single or multi-value
		if (in_array($display_filter_as, array(2,3,5,6,8)))
		{
			if (!is_array($value)) $value = strlen($value) ? array($value) : array();
		}
		else
		{
			if (is_array($value)) $value = isset($value[0]) ? $value[0] : null;
		}

		$isRange = in_array( $display_filter_as, array(2,3,8) );
		$require_all_values = $filter->parameters->get( 'filter_values_require_all', 0 );
		$require_all_in_SQL = is_array($value) && count($value) > 1 && !$isRange   // prevent require_all for known ranges
			? $require_all_values
			: 0;

		$show_matching_items = $filter->parameters->get( 'show_matching_items'.$_s, 1 );
		$show_matches = $isRange || !$faceted_filter ?  0  :  $show_matching_items;
		$hide_disabled_values = $filter->parameters->get( 'hide_disabled_values'.$_s, 0 );
		$get_filter_vals = in_array($display_filter_as, array(0,2,4,5,6)) || ($isSlider && $slider_display_config==1);

		$pretext_filter  = $filter->parameters->get( 'pretext_filter'.$_s, '' );
		$posttext_filter = $filter->parameters->get( 'posttext_filter'.$_s, '' );
		$opentag_filter  = $filter->parameters->get( 'opentag_filter'.$_s, '' );
		$closetag_filter = $filter->parameters->get( 'closetag_filter'.$_s, '' );

		$filter_ffname = 'filter_'.$filter->id . (isset($filter->filt_prop_name) ? '[' . $filter->filt_prop_name . ']': '');
		$filter_ffid   = $formName . '_' . $filter->id . (isset($filter->filt_prop_name) ? '_' . $filter->filt_prop_name: '') . '_val';

		// Escape values for output, moved to HTML code, because it causes problem with value matching (==)
		//if (!is_array($value)) $value = htmlspecialchars($value, ENT_COMPAT, 'UTF-8');
		//else foreach ($value as $i => $v) $value[$i] = htmlspecialchars($value[$i], ENT_COMPAT, 'UTF-8');

		// Alter search property name (indexed fields only), remove underscore _ at start & end of it
		if ($indexed_elements && $search_prop)
		{
			preg_match("/^_([a-zA-Z_0-9]+)_$/", $search_prop, $prop_matches);
			$search_prop = @ $prop_matches[1];
		}

		// Get filtering values, this can be cached if not filtering according to current category filters
		if ($get_filter_vals)
		{
			$view_join = '';
			$view_join_n_text = '';
			$view_where = '';
			$filters_where = array();
			$text_search = '';
			$view_total = 0;

			// ***
			// *** Limiting of displayed filter values according to current category filtering, but show all field values if filter is active
			// ***

			// Category view, use parameter to decide if limitting filter values
			if ( $isCategoryView )
			{
				global $fc_catview;

				if ($faceted_filter)
				{
					$view_join = @ $fc_catview['join_clauses'];
					$view_join_n_text = @ $fc_catview['join_clauses_with_text'];
					$view_where = @ $fc_catview['where_conf_only'];
					$filters_where = @ $fc_catview['filters_where'];
					$text_search = $fc_catview['search'];
					$view_total = isset($fc_catview['view_total']) ? $fc_catview['view_total'] : 0;
				}
			}

			// Search view, use parameter to decide if limitting filter values
			else if ( $isSearchView )
			{
				global $fc_searchview;

				if (empty($fc_searchview))
				{
					return array();  // search view plugin disabled ?
				}

				if ($faceted_filter)
				{
					$view_join = $fc_searchview['join_clauses'];
					$view_join_n_text = $fc_searchview['join_clauses_with_text'];
					$view_where = $fc_searchview['where_conf_only'];
					$filters_where = $fc_searchview['filters_where'];
					$text_search = $fc_searchview['search'];
					$view_total = isset($fc_searchview['view_total']) ? $fc_searchview['view_total'] : 0;
				}
			}

			$createFilterValues = !$isSearchView ? 'createFilterValues' : 'createFilterValuesSearch';

			// Decide if filter display depends on language too
			$lang_code = $isDate && !empty($filter->date_txtformat)? JFactory::getLanguage()->getTag() : null;

			// This is hack for filter core properties to be filterable in search view without being added to the adv search index
			if ($filter->field_type == 'coreprops' &&  $view=='search')
			{
				$createFilterValues = 'createFilterValues';
			}

			// Get filter values considering PAGE configuration (regardless of ACTIVE filters)
			if (isset($filter->filter_options))
			{
				$results_page = $filter->filter_options;
			}
			elseif (FLEXI_CACHE)
			{
				$results_page = $itemcache->get(
					array('FlexicontentFields', $createFilterValues),
					array($filter, $view_join, $view_where, array(), $indexed_elements, $search_prop, $lang_code)
				);
			}
			elseif (!$isSearchView)
			{
				$results_page = FlexicontentFields::createFilterValues($filter, $view_join, $view_where, array(), $indexed_elements, $search_prop, $lang_code);
			}
			else
			{
				$results_page = FlexicontentFields::createFilterValuesSearch($filter, $view_join, $view_where, array(), $indexed_elements, $search_prop, $lang_code);
			}

			// Get filter values considering ACTIVE filters, but only if there is at least ONE filter active
			$faceted_max_item_limit = 10000;
			if ( $faceted_filter==2 )
			{
				if (isset($filter->filter_options))
				{
					$faceted_filter = 0;
					$results_active = $filter->filter_options;
				}

				elseif ($view_total <= $faceted_max_item_limit)
				{
					/**
					 * DO NOT cache at this point the filter combinations are endless,
					 * so they will produce big amounts of cached data, that will be rarely used ...
					 * but if only a single filter is active we can get the cached result of it ...
					 * because its own filter_where is not used for the filter itself
					 */
					if (!$text_search && (count($filters_where)==0 || (count($filters_where)==1 && isset($filters_where[$filter->id] )&& !$require_all_values)))
					{
						$results_active = $results_page;
					}
					elseif (!$isSearchView)
					{
						$results_active = FlexicontentFields::createFilterValues($filter, $view_join_n_text, $view_where, $filters_where, $indexed_elements, $search_prop, $lang_code);
					}
					else
					{
						$results_active = FlexicontentFields::createFilterValuesSearch($filter, $view_join_n_text, $view_where, $filters_where, $indexed_elements, $search_prop, $lang_code);
					}
				}

				elseif ($faceted_overlimit_msg === null)
				{
					// Set a notice message about not counting item per filter values and instead showing item TOTAL of current category / view
					$faceted_overlimit_msg = 1;
					$filter_messages = array();
					$filter_messages[] = JText::sprintf('FLEXI_FACETED_ITEM_LIST_OVER_LIMIT', $faceted_max_item_limit, $view_total);
					$app->setUserState('filter_messages', $filter_messages);
				}
			}

			// Decide which results to show those based: (a) on active filters or (b) on page configuration
			// This depends if hiding disabled values (for FACETED: 2) AND if active filters exist
			$use_active_vals = $hide_disabled_values && isset($results_active);
			$results_shown = $use_active_vals ? $results_active : $results_page;
			$update_found = !$use_active_vals && isset($results_active);

			// Set usage counters
			$add_usage_counters = $faceted_filter==2 && $show_matches;
			$results = array();
			foreach ($results_shown as $i => $result)
			{
				$results[$i] = $result;

				// FACETED: 0,1 or NOT showing usage
				// Set usage to non-zero value e.g. -1 ... which maybe used (e.g. disabling values) but not be displayed
				if (!$show_matches || $faceted_filter<2)
					$results[$i]->found = -1;

				// FACETED: 2 and SHOWING PAGE VALUES (not hiding values or no active filters),
				// Set usage of filter values that was calculated according to active filters
				// 1. this overrides value usage calculated for page's configuration (faceted: 1)
				// 2. we set zero if value was not found
				else if ($update_found)
					$results[$i]->found = isset($results_active[$i]->found) ? (int) $results_active[$i]->found : null;

				// FACETED: 1 or hiding unavailable values ... leave value unchanged (if it has been calculated)
				else ;

				// Prepend prefix to value's label
				if ($pretext_filter)
				{
					$results[$i]->text = $pretext_filter . ' ' . $results[$i]->text;
				}

				// Append suffix to value's label
				if ($posttext_filter)
				{
					$results[$i]->text = $results[$i]->text . ' ' . $posttext_filter;
				}

				// Append value usage to value's label
				if ($add_usage_counters && $results[$i]->found)
				{
					$results[$i]->text .= ' ('.$results[$i]->found.')';  // THESE for indexed fields should have been cloned, so it is ok to modify
				}
			}
		}

		else
		{
			$add_usage_counters = false;
			$faceted_filter = 0; // clear faceted filter flag
		}

		$displayData = array(
			'filter' => & $filter,
			'filter_ffid' => $filter_ffid,
			'filter_ffname' => $filter_ffname,
			'results' => & $results,
			'value' => $value,
			'require_all_values' => $require_all_values,
			'isSearchView' => $isSearchView,
		);

		static $template;

		if ($template === null)
		{
			// Get frontend ('site') Joomla template name
			$template = flexicontent_html::getSiteTemplate();
		}

		$layouts_path_1 = JPATH_ROOT . '/templates/' . $template . '/html/layouts/com_flexicontent/items_list_filters';
		$layouts_path_2 = JPATH_SITE.DS.'components'.DS.'com_flexicontent'.DS.'layouts';

		// *** Do not create any HTML just return empty string to indicate a filter that should be skipped
		if ($hide_disabled_values && empty($results))
		{
			$filter->html = '';
		}

		/**
		 * Create the form field(s) used for filtering, note output is captured into filter->html, but we do 'echo JLayoutHelper::render()' for debugging
		 * Place override at: /templates/TEMPLATENAME/html/layouts/com_flexicontent/items_list_filters/
		 */
		else
		{
			$filter->html = '';

			switch ($display_filter_as)
			{
				// 0: Select (single value selectable), 2: Dual select (value range), 6: Multi Select (multiple values selectable)
				case 0: case 2: case 6:
					$layout_name = 'select_selectmul';
					break;

				// (TODO: autocomplete) ... 1: Text input, 3: Dual text input (value range), both of these can be JS date calendars, 7: Slider, 8: Slider range
				case 1: case 3: case 7: case 8:
					$layout_name = 'txtsearch_date_slider';
					break;

				case 4: case 5:  // 4: radio (single value selectable), 5: checkbox (multiple values selectable)
					$layout_name = 'radio_checkbox';
					break;

				default:
					$layout_name = false;
					$filter->html = 'Case ' . $display_filter_as . ' not implemented';
					break;
			}

			echo JLayoutHelper::render(
				'items_list_filters.'.$layout_name,
				$displayData,
				file_exists($layouts_path_1 . '/' . $layout_name . '.php') ? $layouts_path_1 : $layouts_path_2
			);
		}
		//$last_error = error_get_last();
		//echo '<pre>'; print_r($last_error); exit;

		// Prepend opening Text to filter's HTML
		if ($opentag_filter)
		{
			$filter->html = $opentag_filter . ' ' . $filter->html;
		}

		// Append closing Text to filter's HTML
		if ($closetag_filter)
		{
			$filter->html = $filter->html . ' ' . $closetag_filter;
		}

		if ($print_logging_info)
		{
			$current_filter_creation = round(1000000 * 10 * (microtime(true) - $start_microtime)) / 10;
			$flt_active_count = isset($filters_where) ? count($filters_where) : 0;
			$faceted_str = array(0=>'non-FACETED ', 1=>'FACETED: current view &nbsp; (cacheable) ', 2=>'FACETED: current filters:'." (".$flt_active_count.' active) ');

			$fc_run_times['create_filter'][$filter->name] = $current_filter_creation + (!empty($fc_run_times['create_filter'][$filter->name]) ? $fc_run_times['create_filter'][$filter->name] : 0);

			if (isset($fc_run_times['_create_filter_init']))
			{
				$fc_run_times['create_filter'][$filter->name] -= $fc_run_times['_create_filter_init'];
				$fc_run_times['create_filter_init'] = $fc_run_times['_create_filter_init'] + (!empty($fc_run_times['create_filter_init']) ? $fc_run_times['create_filter_init'] : 0);
				unset($fc_run_times['_create_filter_init']);
			}

			$fc_run_times['create_filter_type'][$filter->name] = $faceted_str[$faceted_filter];
		}

		//$filter_display_typestr = array(0=>'Single Select', 1=>'Single Text', 2=>'Range Dual Select', 3=>'Range Dual Text', 4=>'Radio Buttons', 5=>'Checkbox Buttons');
		//echo "FIELD name: <b>". $filter->name ."</b> Field Type: <b>". $filter->field_type."</b> Filter Type: <b>". $filter_display_typestr[$display_filter_as] ."</b> (".$display_filter_as.") ".sprintf(" %.2f s",$current_filter_creation/1000000)." <br/>";
	}


	// Method to create a calendar form field according to a given configuation, e.g. called during Filter Creation of FC views
	static function createCalendarField($value, $date_allowtime, $fieldname, $elementid, $attribs=array(), $skip_on_invalid=false, $timezone=false, $date_format='%Y-%m-%d')
	{
		@list($date, $time) = preg_split('#\s+#', $value, $limit=2);
		$time = ($date_allowtime==2 && !$time) ? '00:00' : $time;

		try {
			// we check if date has no SYNTAX error (=being invalid) so use $gregorian = true,
			// to avoid it being change according to CALENDAR of current user
			// because user already entered the date in his/her calendar
			if ( !$value ) {
				$date = '';
			} else if (!$date_allowtime || !$time) {
				$date = JHtml::_('date',  $date, 'Y-m-d', $timezone, $gregorian = true);
			} else {
				$date = JHtml::_('date',  $value, 'Y-m-d H:i', $timezone, $gregorian = true);
			}
		} catch ( Exception $e ) {
			if ($skip_on_invalid) return '';
			else $date = '';
			JFactory::getApplication()->enqueueMessage(JText::sprintf('FLEXI_CLEARING_INVALID_CALENDAR_DATE', $value), 'warning');
		}

		// Create JS calendar
		$time_formats_map = array('0'=>'', '1'=>' %H:%M', '2'=>' 00:00');
		$date_time_format = $date_format . $time_formats_map[$date_allowtime];
		$attribs['showTime'] = $date_allowtime ? 1 : 0;
		return JHtml::_('calendar', $date, $fieldname, $elementid, $date_time_format, $attribs);
	}


	// Method to create filter values for a field filter to be used in content lists views (category, etc)
	static function createFilterValues($filter, $view_join, $view_where, $filters_where, $indexed_elements, $search_prop, $lang_code)
	{
		$faceted_filter = $filter->parameters->get( 'faceted_filter', 2);
		$display_filter_as = $filter->parameters->get( 'display_filter_as', 0 );  // Filter Type of Display
		$isRange = in_array( $display_filter_as, array(2,3,8) );
		$lang_filter_values = $filter->parameters->get( 'lang_filter_values', 1);

		$format_output = (int) $filter->parameters->get('format_output', 0);
		if ($format_output > 0)  // 1: decimal, 2: integer
		{
			$decimal_digits_displayed = $format_output === 2 ? 0 : (int)$filter->parameters->get('decimal_digits_displayed', 2);
			$decimal_digits_sep    = $filter->parameters->get('decimal_digits_sep', '.');
			$decimal_thousands_sep = $filter->parameters->get('decimal_thousands_sep', ',');
			$output_prefix = JText::_($filter->parameters->get('output_prefix', ''));
			$output_suffix = JText::_($filter->parameters->get('output_suffix', ''));
		}
		else if ($format_output === -1)
		{
			$output_custom_func = $filter->parameters->get('output_custom_func', '');
			$format_output = !$output_custom_func ? 0 : $format_output;
		}

		$show_matching_items = $filter->parameters->get( 'show_matching_items', 1 );
		$show_matches = $isRange || !$faceted_filter ?  0  :  $show_matching_items;

		//echo "<b>FILTER NAME</b>: ". $filter->label ."<br/>\n";
		//echo "<b> &nbsp; view_join</b>: <br/>". $view_join ."<br/>\n";
		//echo "<b> &nbsp;view_where</b>: <br/>". $view_where ."<br/>\n";
		//echo "<b> &nbsp;filters_where</b>: <br/>". print_r($filters_where, true) ."<br/><br/>\n";
		//exit;

		if ($faceted_filter || !$indexed_elements)
		{
			$_results = FlexicontentFields::getFilterValues($filter, $view_join, $view_where, $filters_where, $lang_code);
			//if ($filter->id==NN) echo "<pre>". $filter->label.": ". print_r($_results, true) ."\n\n</pre>";
			//echo "<pre>". $filter->label.": ". print_r($_results, true) ."\n\n</pre>";
		}


		// Support of value-indexed fields
		if ($indexed_elements)
		{
			// non-FACETED filter
			if (!$faceted_filter)
			{
				// Clone 'indexed_elements' because they maybe modified
				$results = array();
				foreach ($indexed_elements as $i => $result)
				{
					if (isset($result->state) && $result->state < 1)
					{
						continue;
					}
					$results[$i] = clone($result);
				}
			}

			// FACETED filter
			else
			{
				// Limit indexed element according to DB results found
				$results = array_intersect_key($indexed_elements, $_results);
				//echo "<pre>". $filter->label.": ". print_r($results, true) ."\n\n</pre>";
				if ($faceted_filter==2 && $show_matches) foreach ($results as $i => $result)
				{
					if (isset($result->state) && $result->state < 1)
					{
						unset($results[$i]);
						continue;
					}
					$result->found = $_results[$i]->found;
					// Clone 'indexed_elements' because they maybe modified
					$results[$i] = clone($result);
				}
			}
		}

		// Support for multi-property fields
		else if ($search_prop)
		{
			// Check and unserialize values
			foreach ($_results as $i => $result)
			{
				$array = flexicontent_db::unserialize_array($result->value, $force_array=false, $force_value=false);
				if ( $array!==false )
				{
					$_results[$i] = $array;
				}
			}

			// Index values via the search property
			$results = array();
			foreach ($_results as $i => $result)
			{
				$_results[$i] = is_array($_results[$i])
					? (object) $_results[$i]
					: (object) array($search_prop=>$_results[$i]);
				if ( isset($_results[$i]->$search_prop) )
				{
					$results[ $_results[$i]->$search_prop ] = $_results[$i];
				}
			}
		}

		// non-indexable or single property field
		else
		{
			$results = & $_results;
		}

		if (empty($results)) $results = array();

		// Language filter values/labels (for indexed fields this is already done)
		if ( ($lang_filter_values || $format_output) && !$indexed_elements )
		{
			foreach ($results as $i => $result)
			{
				if ($format_output > 0)  // 1: decimal, 2: integer
				{
					$results[$i]->text = is_numeric($results[$i]->text)
						? number_format($results[$i]->text, $decimal_digits_displayed, $decimal_digits_sep, $decimal_thousands_sep)
						: NULL;
					$results[$i]->text = $results[$i]->text === NULL ? 0 : $results[$i]->text;
					$results[$i]->text = $output_prefix .$results[$i]->text. $output_suffix;
				}
				else if ($format_output === -1)
				{
					$value = & $results[$i]->text;
					$results[$i]->text = eval( "\$value= \"{$value}\";" . $output_custom_func);
				}

				if ($lang_filter_values)
				{
					$results[$i]->text = JText::_($result->text);
				}
			}
			unset($value);
		}

		// Skip sorting for indexed elements, DB query or element entry is responsible
		// for ordering indexable fields, also skip if ordering is done by the filter
		if ( !$indexed_elements && empty($filter->filter_orderby) )
		{
			uksort($results, 'strnatcasecmp');
			if ($filter->parameters->get( 'reverse_filter_order', 0)) $results = array_reverse($results, true);
		}

		return $results;
	}


	// Method to create filter values for a field filter to be used in search view
	static function createFilterValuesSearch($filter, $view_join, $view_where, $filters_where, $indexed_elements, $search_prop, $lang_code)
	{
		$faceted_filter = $filter->parameters->get( 'faceted_filter_s', 2);
		$display_filter_as = $filter->parameters->get( 'display_filter_as_s', 0 );  // Filter Type of Display
		$isRange = in_array( $display_filter_as, array(2,3,8) );
		$lang_filter_values = $filter->parameters->get( 'lang_filter_values', 1);

		$format_output = (int) $filter->parameters->get('format_output', 0);
		if ($format_output > 0)  // 1: decimal, 2: integer
		{
			$decimal_digits_displayed = $format_output==2 ? 0 : (int)$filter->parameters->get('decimal_digits_displayed', 2);
			$decimal_digits_sep    = $filter->parameters->get('decimal_digits_sep', '.');
			$decimal_thousands_sep = $filter->parameters->get('decimal_thousands_sep', ',');
			$output_prefix = JText::_($filter->parameters->get('output_prefix', ''));
			$output_suffix = JText::_($filter->parameters->get('output_suffix', ''));
		}
		else if ($format_output === -1)
		{
			$output_custom_func = $filter->parameters->get('output_custom_func', '');
			$format_output = !$output_custom_func ? 0 : $format_output;
		}

		$show_matching_items = $filter->parameters->get( 'show_matching_items_s', 1 );
		$show_matches = $isRange || !$faceted_filter ?  0  :  $show_matching_items;

		$filter->filter_isindexed = (boolean) $indexed_elements;
		$indexed_elements_given = is_array($indexed_elements) && $indexed_elements;

		if ($faceted_filter || !$indexed_elements_given)
		{
			$_results = FlexicontentFields::getFilterValuesSearch($filter, $view_join, $view_where, $filters_where, $lang_code);
			//echo "<pre>". $filter->label.": ". print_r($_results, true) ."\n\n</pre>";
		}

		// Support of value-indexed fields
		if ($indexed_elements_given)
		{
			if (!$faceted_filter)
			{
				// Clone 'indexed_elements' because they maybe modified
				$results = array();
				foreach ($indexed_elements as $i => $result)
				{
					$results[$i] = clone($result);
				}
			}

			// Limit indexed element according to DB results found
			else
			{
				$results = array_intersect_key($indexed_elements, $_results);
				//echo "<pre>". $filter->label.": ". print_r($indexed_elements, true) ."\n\n</pre>";
				if ($faceted_filter==2 && $show_matches) foreach ($results as $i => $result)
				{
					$result->found = $_results[$i]->found;
					// Clone 'indexed_elements' because they maybe modified
					$results[$i] = clone($result);
				}
			}
		}

		// Indexed elements not given
		else
		{
			$results = & $_results;
		}

		// Language filter values/labels (for indexed fields this is already done)
		if ( ($lang_filter_values || $format_output) && !$indexed_elements )
		{
			foreach ($results as $i => $result)
			{
				if ($format_output > 0)  // 1: decimal, 2: integer
				{
					$results[$i]->text = number_format((float)$results[$i]->text, $decimal_digits_displayed, $decimal_digits_sep, $decimal_thousands_sep);
					$results[$i]->text = $results[$i]->text === NULL ? 0 : $results[$i]->text;
					$results[$i]->text = $output_prefix .$results[$i]->text. $output_suffix;
				}
				else if ($format_output === -1)
				{
					$value = & $results[$i]->text;
					$results[$i]->text = eval( "\$value= \"{$value}\";" . $output_custom_func);
				}

				if ($lang_filter_values)
				{
					$results[$i]->text = JText::_($result->text);
				}
			}
			unset($value);
		}

		// Skip sorting for indexed elements, DB query or element entry is responsible
		// for ordering indexable fields, also skip if ordering is done by the filter
		if ( !$indexed_elements && empty($filter->filter_orderby_adv) )
		{
			uksort($results, 'strnatcasecmp');
			if ($filter->parameters->get( 'reverse_filter_order', 0)) $results = array_reverse($results, true);
		}

		return $results;
	}


	// Retrieves all available filter values of the given field according to the given VIEW'S FILTERING (Content Lists)
	static function getFilterValues(&$filter, &$view_join, &$view_where, &$filters_where, &$lang_code)
	{
		//echo "<pre>"; debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); echo "</pre>";
		$db = JFactory::getDbo();

		$require_all_values = (int) $filter->parameters->get('filter_values_require_all', 0);
		$filter_where_curr = '';
		foreach ($filters_where as $filter_id => $filter_where)
		{
			if ($require_all_values || $filter_id != $filter->id)
			{
				$filter_where_curr .= ' ' . $filter_where;
			}
		}
		//echo $filter->name . ' - require_all_values'  . (int)$require_all_values . " - filter_where_curr : ". $filter_where_curr ."<br/>";

		// partial SQL clauses
		$valuesselect = !empty($filter->filter_valuesselect)
			? $filter->filter_valuesselect
			: ' fi.value AS value, fi.value AS text';

		$valuesfrom = !empty($filter->filter_valuesfrom)
			? $filter->filter_valuesfrom
			: ($filter->iscore || $filter->field_type=='coreprops' ? ' FROM #__content AS i' : ' FROM #__flexicontent_fields_item_relations AS fi ');

		$valuesjoin = !empty($filter->filter_valuesjoin)
			? $filter->filter_valuesjoin
			: ' ';

		$valueswhere = !empty($filter->filter_valueswhere)
			? $filter->filter_valueswhere
			: ' AND fi.field_id ='.$filter->id;

		// full SQL clauses
		$groupby = !empty($filter->filter_groupby) ? $filter->filter_groupby : ' GROUP BY value ';
		$having  = !empty($filter->filter_having)  ? $filter->filter_having  : '';
		$orderby = !empty($filter->filter_orderby) ? $filter->filter_orderby : '';

		if ($filter->parameters->get( 'reverse_filter_order', 0) && $orderby)
		{
			$replace_count = null;
			$orderby = str_ireplace( ' ASC', ' DESC', $orderby, $replace_count);
			if (!$replace_count) $orderby .= ' DESC';
		}

		$faceted_filter = $filter->parameters->get( 'faceted_filter', 2);
		$display_filter_as = $filter->parameters->get( 'display_filter_as', 0 );  // Filter Type of Display
		$isRange = in_array( $display_filter_as, array(2,3,8) );

		$show_matching_items = $filter->parameters->get( 'show_matching_items', 1 );
		$show_matches = $isRange || !$faceted_filter ?  0  :  $show_matching_items;

		$use_tmp = true;
		static $iids_subquery = null;
		static $iids_tblname  = array();
		$view_n_text = 'SELECT DISTINCT i.id '."\n"
			. ' FROM #__'.($use_tmp ? 'flexicontent_items_tmp' : 'content').' AS i'."\n"
			. $view_join."\n"
			. $view_where."\n"
			;
		if ( !isset($iids_tblname[$view_n_text]) )
		{
			$iids_tblname[$view_n_text] = 'fc_view_iids_'.count($iids_tblname);
		}
		$tmp_tbl = $iids_tblname[$view_n_text];

		// Format string according to language
		static $lc_time_names = null;
		if ($lang_code && !empty($filter->date_txtformat))
		{
			if ($lc_time_names === null)
			{
				$db->setQuery('SELECT @@lc_time_names');
				$lc_time_names = $db->loadResult();
			}
			$language = $lc_time_names !== $lang_code ? $lang_code : null;
		}


		// ***
		// *** Find items belonging to current view
		// ***

		// FACETED FILTER
		if ( $faceted_filter > 1 )
		{
			if ( !isset($iids_subquery[$view_n_text]) && empty($view_where) )
			{
				$iids_subquery[$view_n_text] = '';  // current view has not limits in where clause
			}

			if ( !isset($iids_subquery[$view_n_text]) )
			{
				global $fc_run_times, $fc_jprof, $fc_catview;
				$start_microtime = microtime(true);

				try {
					// Use sub-query on temporary table
					$db->setQuery('CREATE TEMPORARY TABLE IF NOT EXISTS '.$tmp_tbl.' (id INT, KEY(`id`))');
					$db->execute();
					$db->setQuery('TRUNCATE TABLE '.$tmp_tbl);
					$db->execute();
					$db->setQuery('INSERT INTO '.$tmp_tbl.' '.$view_n_text);
					$db->execute();
					$iids_subquery[$view_n_text] = 'SELECT id FROM '.$tmp_tbl;   //echo "<br/><br/> FILTER INITIALIZATION - using temporary table: ".$iids_subquery[$view_n_text]." for :".$view_n_text ." <br/><br/>";
				}
				catch (Exception $e) {
					// Repeat sub-query if creating temporary table failed
					//echo 'SQL QUERY ERROR:<br/>'.nl2br($e->getMessage());
					$iids_subquery[$view_n_text] = $view_n_text;   //echo "<br/><br/> FILTER INITIALIZATION - using subquery: ".$iids_subquery[$view_n_text]." <br/><br/>";
					/*if ($fc_catview['search']) {
						$db->setQuery($view_n_text);
						$item_ids = $db->loadColumn();
						$iids_subquery[$view_n_text] = implode(',', $item_ids);   //echo "<br/><br/> FILTER INITIALIZATION - using item ID list: ".$iids_subquery[$view_n_text]." <br/><br/>";
					}*/
				}
				$fc_run_times['_create_filter_init'] = round(1000000 * 10 * (microtime(true) - $start_microtime)) / 10;
			}
			//if ($filter->id==NN) echo "<br/><br/> FILTER INITIALIZATION - using temporary table: ".$iids_subquery[$view_n_text]." for :".$view_n_text ." <br/><br/>";

			$item_id_col = !empty($filter->filter_item_id_col)
				? $filter->filter_item_id_col
				: ($filter->iscore || $filter->field_type=='coreprops' ? 'i.id' : 'fi.item_id');

			$filter_where_curr = preg_replace('/\bi.id\b/', $item_id_col, $filter_where_curr);
			$query = 'SELECT '. $valuesselect .($faceted_filter && $show_matches ? ', COUNT(DISTINCT '.$item_id_col.') as found ' : '')."\n"
				//.', GROUP_CONCAT('.$item_id_col.' SEPARATOR ",") AS idlist '   // enable FOR DEBUG purposes only
				. $valuesfrom."\n"
				. $valuesjoin."\n"
				. ' WHERE 1 '."\n"
				. (empty($iids_subquery[$view_n_text]) ? '' : ' AND '.$item_id_col.' IN('.$iids_subquery[$view_n_text].')'."\n")
				. $filter_where_curr."\n"
				. $valueswhere."\n"
				. $groupby."\n"
				. $having."\n"
				. $orderby
				;
			//if ($filter->id==NN) echo $query."<br/><br/>";
		}

		// Non FACETED filter (according to view but without acounting for filtering and without counting items)
		else
		{
			$query = 'SELECT DISTINCT '. $valuesselect ."\n"
				. $valuesfrom."\n"
				. $valuesjoin."\n"
				. ' WHERE 1 '."\n"
				. $valueswhere."\n"
				//. $groupby."\n"  // replaced by distinct, when not counting items
				. $having."\n"
				. $orderby
				;
		}
		//if ( in_array($filter->field_type, array('tags','created','modified')) ) echo nl2br($query);

		$db->setQuery($query);
		try {
			$results = $db->loadObjectList('value');
			//if ($filter->id==NN) { echo "<pre>"; print_r($results); echo "</pre>"; }
		}
		catch (Exception $e) {
			JFactory::getApplication()->enqueueMessage(__FUNCTION__."() Filter for : ".$filter->label." cannot be displayed, SQL QUERY ERROR:<br />" .nl2br(JDEBUG ? $e->getMessage() . '<br/>' . $query : 'Joomla Debug is OFF'), 'warning');
			$results = array();
		}

		// Format string according to language
		if (!empty($language))
		{
			$filter_as_age = $filter->parameters->get('filter_as_age', 0);

			if (!$filter_as_age)
			{
				$date_txtformat = str_replace('%', '', $filter->date_txtformat);
				$nullDate = $db->getNullDate();
				$is_year_group = $filter->parameters->get('date_filter_group', 'month') === 'year';

				foreach($results as &$r)
				{
					if ($r->value && $r->value !== $nullDate)
					{
						$date = new JDate($is_year_group ? $r->value . '-1-1' : $r->value);   // JDate can not handle just year (YYYY) so we use YYYY-1-1
						$r->text = $date->format($date_txtformat);
					}
				}
				unset($r);
			}
		}

		return $results;
	}


	// Retrieves all available filter values of the given field according to the given VIEW'S FILTERING (Search view)
	static function getFilterValuesSearch(&$filter, &$view_join, &$view_where, &$filters_where, &$lang_code)
	{
		//echo "<pre>"; debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); echo "</pre>";
		$app = JFactory::getApplication();
		$db  = JFactory::getDbo();

		$require_all_values = (int) $filter->parameters->get('filter_values_require_all', 0);
		$filter_where_curr = '';
		foreach ($filters_where as $filter_id => $filter_where)
		{
			if ($require_all_values || $filter_id != $filter->id)
			{
				$filter_where_curr .= ' ' . $filter_where;
			}
		}

		$isDate = in_array($filter->field_type, array('date','created','modified')) || $filter->parameters->get('isdate',0);
		$using_value_id = $isDate || @$filter->filter_isindexed;
		$valuesselect = $using_value_id ? ' ai.value_id as value, ai.search_index as text ' : ' ai.search_index as value, ai.search_index as text';
		$orderby = @$filter->filter_orderby_adv ? $filter->filter_orderby_adv : '';
		if ($filter->parameters->get( 'reverse_filter_order', 0) && $orderby)
		{
			$replace_count = null;
			$orderby = str_ireplace( ' ASC', ' DESC', $orderby, $replace_count);
			if (!$replace_count) $orderby .= ' DESC';
		}

		$faceted_filter = $filter->parameters->get( 'faceted_filter_s', 2);
		$display_filter_as = $filter->parameters->get( 'display_filter_as_s', 0 );  // Filter Type of Display
		$isRange = in_array( $display_filter_as, array(2,3,8) );

		$show_matching_items = $filter->parameters->get( 'show_matching_items_s', 1 );
		$show_matches = $isRange || !$faceted_filter ?  0  :  $show_matching_items;

		$field_tbl = 'flexicontent_advsearch_index_field_'.$filter->id;
		$query = 'SHOW TABLES LIKE "' . $app->getCfg('dbprefix') . $field_tbl . '"';
		$db->setQuery($query);
		$tbl_exists = (boolean) count($db->loadObjectList());
		$field_tbl = $tbl_exists ? $field_tbl : 'flexicontent_advsearch_index';


		static $iids_subquery = null;
		static $iids_tblname  = array();

		$view_n_text = 'SELECT DISTINCT i.id '."\n"
			.' FROM #__content i '."\n"
			. $view_join."\n"
			. $view_where."\n"
			;
		if ( !isset($iids_tblname[$view_n_text]) )
		{
			$iids_tblname[$view_n_text] = 'fc_view_iids_'.count($iids_tblname);
		}
		$tmp_tbl = $iids_tblname[$view_n_text];


		// Format string according to language
		static $lc_time_names = null;
		if ($lang_code && !empty($filter->date_txtformat))
		{
			if ($lc_time_names === null)
			{
				$db->setQuery('SELECT @@lc_time_names');
				$lc_time_names = $db->loadResult();
			}
			$language = $lang_code;  // Search view always needs recreation of display, because the field configuration may have changed, since the Search Index was created
		}


		// ***
		// *** Find items belonging to current view
		// ***

		// FACETED FILTER
		if ( $faceted_filter > 1 )
		{
			if ( !isset($iids_subquery[$view_n_text]) && empty($view_where) )  $iids_subquery[$view_n_text] = '';  // current view has not limits in where clause

			if ( !isset($iids_subquery[$view_n_text]) )
			{
				global $fc_run_times, $fc_jprof, $fc_searchview;

				$start_microtime = microtime(true);
				try {
					// Use sub-query on temporary table
					$db->setQuery('CREATE TEMPORARY TABLE IF NOT EXISTS '.$tmp_tbl.' (id INT, KEY(`id`))');
					$db->execute();
					$db->setQuery('TRUNCATE TABLE '.$tmp_tbl);
					$db->execute();
					$db->setQuery('INSERT INTO '.$tmp_tbl.' '.$view_n_text);
					$db->execute();
					$iids_subquery[$view_n_text] = 'SELECT id FROM '.$tmp_tbl;   //echo "<br/><br/> FILTER INITIALIZATION - using temporary table: ".$iids_subquery[$view_n_text]." for :".$view_n_text ." <br/><br/>";
				}
				catch (Exception $e) {
					// Repeat sub-query if creating temporary table failed
					//echo 'SQL QUERY ERROR:<br/>'.nl2br($e->getMessage());
					$iids_subquery[$view_n_text] = $view_n_text;   //echo "<br/><br/> FILTER INITIALIZATION - using subquery: ".$iids_subquery[$view_n_text]." <br/><br/>";
					/*if ($fc_searchview['search']) {
						$db->setQuery($view_n_text);
						$item_ids = $db->loadColumn();
						$iids_subquery[$view_n_text] = implode(',', $item_ids);   //echo "<br/><br/> FILTER INITIALIZATION - using item ID list: ".$iids_subquery[$view_n_text]." <br/><br/>";
					}*/
				}
				$fc_run_times['_create_filter_init'] = round(1000000 * 10 * (microtime(true) - $start_microtime)) / 10;
			}

			// Get ALL records that have such values for the given field
			$query = 'SELECT '. $valuesselect .($faceted_filter && $show_matches ? ', COUNT(DISTINCT ai.item_id) as found ' : '')."\n"
				. ' FROM #__'.$field_tbl.' AS ai'."\n"
				. ' WHERE ai.field_id='.(int)$filter->id."\n"
				. (empty($iids_subquery[$view_n_text]) ? '' : ' AND ai.item_id IN('.$iids_subquery[$view_n_text].')'."\n")
				.  str_replace('i.id', 'ai.item_id', $filter_where_curr)."\n"
				. ' GROUP BY ai.search_index, ai.value_id'."\n"
				. $orderby
				;
			//if ($filter->id==NN) echo $query."<br/><br/>";
		}

		// Non FACETED filter (according to view but without acounting for filtering and without counting items)
		else {
			$query = 'SELECT DISTINCT '. $valuesselect."\n"
				. ' FROM #__'.$field_tbl.' AS ai'."\n"
				. ' WHERE ai.field_id='.(int)$filter->id."\n"
				. (empty($iids_subquery[$view_n_text]) ? '' : ' AND ai.item_id IN('.$iids_subquery[$view_n_text].')'."\n")
				.  str_replace('i.id', 'ai.item_id', $filter_where_curr)."\n"
				//. ' GROUP BY ai.search_index, ai.value_id'."\n"  // replaced by distinct, when not counting items
				. $orderby
				;
		}
		//echo $query."<br/><br/>";

		$db->setQuery($query);
		try {
			$results = $db->loadObjectList('value');
			//if ($filter->id==NN) { echo "<pre>"; print_r($results); echo "</pre>"; }
		}
		catch (Exception $e) {
			JFactory::getApplication()->enqueueMessage(__FUNCTION__."() Filter for : ".$filter->label." cannot be displayed, SQL QUERY ERROR:<br />" .nl2br(JDEBUG ? $e->getMessage() . '<br/>' . $query : 'Joomla Debug is OFF'), 'warning');
			$results = array();
		}

		// Format string according to language
		if (!empty($language))
		{
			$filter_as_age = $filter->parameters->get('filter_as_age_s', 0);

			if (!$filter_as_age)
			{
				$date_txtformat = str_replace('%', '', $filter->date_txtformat);
				$nullDate = $db->getNullDate();
				$is_year_group = $filter->parameters->get('date_filter_group_s', 'month') === 'year';

				foreach($results as &$r)
				{
					if ($r->value && $r->value !== $nullDate)
					{
						$date = new JDate($is_year_group ? $r->value . '-1-1' : $r->value);   // JDate can not handle just year (YYYY) so we use YYYY-1-1
						$r->text = $date->format($date_txtformat);
					}
				}
				unset($r);
			}
		}

		static $search_prefix = null;
		if ($search_prefix === null) $search_prefix = JComponentHelper::getParams( 'com_flexicontent' )->get('add_search_prefix') ? 'vvv' : '';   // SEARCH WORD Prefix
		if ($search_prefix) foreach ($results as $i => $result)
		{
			$result->text = preg_replace('/\b'.$search_prefix.'/u', '', $result->text);
			if (!$using_value_id) $result->value = $result->text;
		}

		return $results;
	}


	/**
	 * Method to set custom filters values VIA configuration parameters
	 * -- CASE 1: CONTENT LISTS (component / category / menu items / filtering module)
	 *    these are set as HTTP Request variables to be used by the filtering mechanism of the category model (content lists)
	 * -- CASE 2: Custom Fields SCOPE of Universal Content MODULE
	 *    these are returned as an array to be used directly into the SQL query
	 *
	 * @access public
	 * @return object
	 * @since 1.5
	 */
	static function setFilterValues( &$cparams, $mfilter_name='persistent_filters', $is_persistent=1, $set_method="httpReq" )
	{
		$jinput = JFactory::getApplication()->input;

		$field_filters = array();   // Used when set_method is 'array' instead of 'httpReq'
		$is_persistent =            // Non-httpReq method does not have initial filters
			$set_method!="httpReq" ? 1 : $is_persistent;

		// Get configuration parameter holding the custom field filtering and abort if empty
		$mfilter_data = $cparams->get($mfilter_name, '');
		if (!$mfilter_data)
		{
			$cparams->set($mfilter_name, '');  // Set to empty string for J1.5 compatibility, otherwise this could be empty array too
			return array();
		}

		// Parse configuration parameter into individual fields
		$mfilter_arr = preg_split("/[\s]*%%[\s]*/", $mfilter_data);
		if ( empty($mfilter_arr[count($mfilter_arr)-1]) )
		{
			unset($mfilter_arr[count($mfilter_arr)-1]);
		}

		// This array contains the field (filter) ID that were parsed without errors
		$filter_ids = array();

		foreach ($mfilter_arr as $mfilter)
		{
			// a. Split elements into their properties: filter_id, filter_value
			$_data  = preg_split("/[\s]*##[\s]*/", $mfilter, 2);  //print_r($_data);
			$filter_id = (int) $_data[0];
			$filter_value = @$_data[1];
			//echo "filter_".$filter_id.": "; print_r( $filter_value ); echo "<br/>";

			// b. Basic parsing error check: a non numeric field id
			if ( !$filter_id ) continue;

			// c. Add field (filter) ID into those that are valid
			$filter_ids[] = $filter_id;

			// d. Skip field filter, if it is not persistent and user user has overriden it
			if ( !$is_persistent && $jinput->get('filter_'.$filter_id, false, 'raw') !== false ) continue;

			// ***
			// *** FILTER FOR FIELD OF RELATED ITEM: rel_field_id##rel_item_field_id~~value
			// ***
			$relitem_field_id = 0;
			if (strpos($filter_value, '~~') !== false)
			{
				list($relitem_field_id, $rel_field_value) = explode('~~', $filter_value, 2);
				$relitem_field_id = (int) $relitem_field_id;
				if ($relitem_field_id)
				{
					$filter_value = $rel_field_value;
				}
				//print_r($filter_value);
			}

			// CASE: range values:  value01---value02
			if (strpos($filter_value, '---') !== false)
			{
				$filter_value = explode('---', $filter_value);
				$filter_value[2] = $filter_value[1];
				$filter_value[1] = $filter_value[0];
				unset($filter_value[0]);
			}

			// CASE: multiple values:  value01+++value02+++value03+++value04
			else if (strpos($filter_value, '+++') !== false)
			{
				$filter_value = explode('+++', $filter_value);
			}

			// CASE: specific value:  value01
			else {}

			// *** Add filter for field of related item
			if ($relitem_field_id)
			{
				$filter_value = array(- $relitem_field_id => $filter_value);
			}

			// INDIRECT method of using field filter (via HTTP request)
			if ($set_method=='httpReq')
			{
				$jinput->set('filter_'.$filter_id, $filter_value);
			}

			// DIRECT method of using field filter (via a returned array)
			else
			{
				$field_filters[$filter_id] = $filter_value;
			}
		}

		// INDIRECT method of using field filter (via HTTP request),
		// NOTE: we overwrite the above configuration parameter of custom field filters with an ARRAY OF VALID FILTER IDS, to
		// indicate to category/search model security not to skip these if they are not IN category/search configured filters list
		if ($set_method=='httpReq')
		{
			count($filter_ids) ?
				$cparams->set($mfilter_name, $filter_ids) :
				$cparams->set($mfilter_name, false );  // FALSE means do not retrieve ALL
		}

		// DIRECT method filter values, return an array of filter values (for direct usage into an SQL query)
		else {
			return $field_filters;
		}
	}


	/**
	 * Method to get data of filters
	 *
	 * @access public
	 * @return object
	 * @since 1.5
	 */
	static function & getFilters($filt_param='filters', $usage_param='use_filters', & $params = null, $check_access=true)
	{
		// Parameter that controls using these filters
		$filters = array();
		if ( $usage_param!='__ALL_FILTERS__' && $params && !$params->get($usage_param,0) ) return $filters;

		// Get Filter IDs, false means do retrieve any filter
		$filter_ids = $params
			? $params->get($filt_param, array())
			: array();

		if ($filter_ids === false)
		{
			return $filters;
		}

		// Check if array or comma separated list
		if (!is_array($filter_ids))
		{
			$filter_ids = preg_split("/\s*,\s*/u", $filter_ids);

			if (empty($filter_ids[0]))
			{
				unset($filter_ids[0]);
			}
		}

		// Sanitize the given filter_ids ... just in case
		$filter_ids = array_filter($filter_ids, 'is_numeric');

		// array_flip to get unique filter ids as KEYS (due to flipping) ... and then array_keys to get filter_ids in 0,1,2, ... array
		$filter_ids = array_keys(array_flip($filter_ids));

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

		// None selected filters means ALL
		$and_scope = $usage_param!='__ALL_FILTERS__' && count($filter_ids) ? ' AND fi.id IN (' . implode(',', $filter_ids) . ')' : '';

		// Use ACCESS Level, usually this is only for shown filters
		$and_access = '';

		if ($check_access)
		{
			$aid_arr = JAccess::getAuthorisedViewLevels($user->id);
			$aid_list = implode(",", $aid_arr);
			$and_access = ' AND fi.access IN (0,'.$aid_list.') ';
		}

		// Create and execute SQL query for retrieving filters
		$query  = 'SELECT fi.*'
			. ' FROM #__flexicontent_fields AS fi'
			. ' WHERE fi.published = 1'
			. ' AND fi.isfilter = 1'
			. $and_access
			. $and_scope
			. ' ORDER BY fi.ordering, fi.name';

		$filters = $db->setQuery($query)->loadObjectList('id');

		if (!$filters)
		{
			// Create variable to return a reference (also can not return false here as it will mean an error)
			$filters = array();
			return $filters;
		}

		// Order filters according to given order
		$filters_tmp = array();

		if ($params->get('filters_order', 0) && !empty($filter_ids) && $usage_param!='__ALL_FILTERS__')
		{
			foreach( $filter_ids as $filter_id) {
				if ( empty($filters[$filter_id]) ) continue;
				$filter = $filters[$filter_id];
				$filters_tmp[$filter->name] = $filter;
			}
		}

		// Not re-ordering, but index them via fieldname in this case too (for consistency)
		else
		{
			foreach ($filters as $filter)
			{
				$filters_tmp[$filter->name] = $filter;
			}
		}

		$filters = $filters_tmp;

		// Create filter parameters, language filter label, etc
		foreach ($filters as $filter)
		{
			$filter->parameters = new JRegistry($filter->attribs);
			$filter->label = JText::_($filter->label);
		}

		// Return found filters
		return $filters;
	}


	/**
	 * Method to creat the HTML of filters
	 *
	 * @access public
	 * @return object
	 * @since 1.5
	 */
	static function renderFilters( &$params, &$filters, $form_name )
	{
		// Make the filter compatible with Joomla standard cache
		$jinput = JFactory::getApplication()->input;

		$filter_prefix = ($form_name == 'item_form' ? 'iform_' : '') .'filter_';

		$display_label_filter_override = (int) $params->get('show_filter_labels', 0);
		foreach ($filters as $filter_name => $filter)
		{
			$filtervalue = $jinput->get($filter_prefix.$filter->id, '', 'raw');
			//print_r($filtervalue);

			// make sure filter HTML is cleared, and create it
			$display_label_filter_saved = $filter->parameters->get('display_label_filter');
			if ( $display_label_filter_override ) $filter->parameters->set('display_label_filter', $display_label_filter_override); // suppress labels inside filter's HTML (hide or show all labels externally)

			// else ... filter default label behavior
			$filter->html = '';  // make sure filter HTML display is cleared
			$field_type = $filter->iscore ? 'core' : $filter->field_type;
			//$results 	= $dispatcher->trigger('onDisplayFilter', array( &$filter, $filtervalue ));
			FLEXIUtilities::call_FC_Field_Func($field_type, 'onDisplayFilter', array( &$filter, $filtervalue, $form_name ) );
			$filter->parameters->set('display_label_filter', $display_label_filter_saved);
		}
	}



	// ***
	// *** Helper methods to create GENERIC ITEM LISTs which also includes RENDERED display of fields and custom HTML
	// ***

	/*
	 * Helper method to perform HTML replacements on given list of item ids (with optional catids too), the items of the item
	 * is either given as parameter or the list is created via a field / item pair (field is relation or relation reverse)
	 *
	 * @param 	object 		$params     some parameters, typically the parameters of a relation field
	 * @param 	string    $itemIDs    the item IDs as index of an array of some item data
	 * @param 	object 		$field      a field that is a relation or relation reverse field
	 * @param 	object 		$item       an item having a relation or relation reverse field
	 * @param 	string    $options    some options like 'return_items_array' meaning to return an items array and not the final HTML
	 *
	 * @return  a string with the rendered display HTML of the calculated items list, or an array of item objects, with their display HTML as a property
	 * @since 2.1
	 *
	 */
	static function getItemsList($params, $itemIDs, $field, $item, $options = null)
	{
		if (!is_object($field) || !is_object($item))  return 'getItemsList : Legacy call not possible, please review your custom code';
		if (!$options) $options = new stdClass();

		// 0: return imploded display HTML
		// 1: return items array with display HTML per item
		// 2: return items array without rendering display HTML
		$return_items_array = isset($options->return_items_array) ? (int) $options->return_items_array : 0;

		// Create the SQL query to retrieve item list data array,
		// ... and check for empty SQL query, e.g. no valid related items for relation field
		$query = FlexicontentFields::createItemsListSQL($params, $itemIDs, $field, $item, $options);
		if (!$query)
		{
			return $return_items_array ? array() : '';
		}

		// Execute SQL query to get item list data array,
		// ... and check for none found items (e.g. none published)
		$db = JFactory::getDbo();
		try {
			$rows = flexicontent_db::directQuery($query);
			$item_list = array();

			if ($return_items_array != 3)
			{
				foreach ($rows as $row)
				{
					$item_list[$row->id] = $row;
				}
				$db->setQuery("SELECT FOUND_ROWS()");
				$total = $db->loadResult();
			}
			else
			{
				// instead of items array contains the total
				$total = $rows ? $rows[0]->total : 0;
			}
			$options->total = $total;
		}
		catch (Exception $e) {
			$item_list = $db->setQuery($query)->loadObjectList('id');
		}

		// Check for empty array
		if ( !$item_list )
		{
			return $return_items_array ? array() : '';
		}

		// Get Original content ids for creating some untranslatable fields that have share data (like shared folders)
		flexicontent_db::getOriginalContentItemids($item_list);

		// Only return the items array without creating their HTML
		if ( $return_items_array == 2)
		{
			return $item_list;
		}

		// Finally create the display HTML of the items,
		// - either returning an imploded string of the items display HTML
		// - or returning the items data array, with the display HTML as property of every item object
		return FlexicontentFields::createItemsListHTML($params, $item_list, $field, $item, $itemIDs, $options);
	}


	// Helper method to create SQL query for retrieving items list data
	static function createItemsListSQL($params, $itemIDs, $field, $item, $options)
	{
		$db = JFactory::getDbo();
		$user = JFactory::getUser();

		// Options
		$reverse_field_id = (int) $params->get('reverse_field', 0);
		$isform = isset($options->isform) ? (int) $options->isform : 0;
		$states = isset($options->items_list_state)   // if isform this is ignored
			? $options->items_list_state
			: array(1,-5,2);
		$sfx = $isform ? '_form' : '';

		$scopes_where = array();

		if (!$isform)
		{
			$samelangonly = !$reverse_field_id || $params->get('samelangonly_view', 1) != -1
				? $params->get('samelangonly_view', 1)
				: $params->get('samelangonly', 1);

			if ($samelangonly)
			{
				$language = !$item->language || $item->language === '*'
					? JFactory::getLanguage()->getTag()
					: $item->language;
				$scopes_where[] = ' (ext.language = ' . $db->Quote($language) . ' OR ext.language = ' . $db->Quote('*') . ') ';
			}

			$use_publish_dates = !$reverse_field_id || $params->get('use_publish_dates_view', 1) != -1
				? $params->get('use_publish_dates_view', 1)
				: $params->get('use_publish_dates', 1);

			if ($use_publish_dates)
			{
				// Date-Times are stored as UTC, we should use current UTC time to compare and not user time (requestTime),
				// thus the items are published globally at the time the author specified in his/her local clock

				//$now  = JFactory::getApplication()->requestTime;   // NOT correct behavior it should be UTC (below)
				//$now  = JFactory::getDate()->toSql();              // NOT good if string passed to function that will be cached, because string continuesly different

				$nowDate = 'UTC_TIMESTAMP()';  //$db->Quote($now);
				$nullDate = $db->getNullDate();

				$scopes_where[] = ' ( i.publish_up is NULL OR i.publish_up = '.$db->Quote($nullDate).' OR i.publish_up <= '.$nowDate.' )';
				$scopes_where[] = ' ( i.publish_down is NULL OR i.publish_down = '.$db->Quote($nullDate).' OR i.publish_down >= '.$nowDate.' )';
			}

			$onlypublished = !$reverse_field_id || $params->get('onlypublished_view', 1) != -1
				? (int) $params->get('onlypublished_view', 1)
				: (int) $params->get('onlypublished', 1);

			if ($onlypublished && count($states))
			{
				$scopes_where[] = ' i.state IN ('.implode(',',$states).')';
			}
		}


		// Item IDs via reversing a relation field
		if ($reverse_field_id)
		{
			$item_join = ' JOIN #__flexicontent_fields_item_relations AS fi_rel'
				.'  ON i.id=fi_rel.item_id AND fi_rel.field_id=' . $reverse_field_id . ' AND CAST(fi_rel.value AS SIGNED)=' . (int) $item->id;
		}

		// Indicate nothing to do, since no related items given
		elseif (empty($itemIDs))
		{
			return false;
		}

		// Item IDs via a given related items list (relation field and ... maybe other cases too)
		else
		{
			$item_where = ' AND i.id IN ('. implode(',', array_keys($itemIDs)) .')';
		}


		// category scope (reverse relation field parameter)
		if($params->get('reverse_scope_category', 0))
		{
			$cat_where = ' AND i.catid IN ('. implode(",", array_values($params->get('reverse_scope_category', 0))) .')';
		}

		// type scope (reverse relation field parameter)
		if($params->get('reverse_scope_types', 0))
		{
			$type_where = ' AND ext.type_id IN ('. implode(",", array_values($params->get('reverse_scope_types', 0))) .')';
		}

		// owner scope
		$ownedbyuser = !$reverse_field_id || $params->get('ownedbyuser_view', -1) != -1
			? (int) $params->get('ownedbyuser_view')
			: (int) $params->get('ownedbyuser', 0);

		switch ($ownedbyuser)
		{
			// Limit the related items list to items created by current user (editor or viewer)
			case 1:
				$itemowned_where = ' AND i.created_by=' . $user->id;
				break;

			// Limit the related items list to items created by the creator of current item
			case 2:
				$itemowned_where = ' AND i.created_by=' . $item->created_by;
				break;

			// case zero, do not apply any limitation to
		}


		// item count limit
		$itemcount = $isform ? (int) $params->get('itemcount_form ', 0) : (int) $params->get('itemcount', 0);
		$limit = $itemcount
			? ' LIMIT ' . $itemcount
			: '';


		// Get orderby SQL CLAUSE ('ordering' is passed by reference but no frontend user override is used (we give empty 'request_var')
		if ($params && $params->get('orderby') == 'manual')
		{
			$order = array(1 => 'manual', 2=>'');
			$orderby = ' ORDER BY FIELD(i.id, '. implode(',', array_keys($itemIDs)) .')';
		}
		else
		{
			$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
			);
		}
		$orderby_join = '';

		// Create JOIN for ordering items by a custom field (use SFX)
		if ( 'field' == $order[1] )
		{
			$orderbycustomfieldid = (int)$params->get('orderbycustomfieldid'.$sfx, 0);
			$orderbycustomfieldint = (int)$params->get('orderbycustomfieldint'.$sfx, 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;
		}

		// Create JOIN for ordering items by a custom field (Level 2)
		if ( $sfx=='' && '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;
		}

		// 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;
		}


		// Because query includes specific items it should be fast
		$return_items_array = isset($options->return_items_array) ? (int) $options->return_items_array : 0;

		// Only count found items
		if ($return_items_array == 3)
		{
			$query = 'SELECT COUNT(*) AS total FROM ('
				.' SELECT 1'
				.' FROM #__flexicontent_items_tmp AS i '
				.' LEFT JOIN #__flexicontent_items_ext AS ext ON i.id=ext.item_id '
				. @ $item_join
				. @ $orderby_join
				.' LEFT JOIN #__flexicontent_cats_item_relations AS rel ON i.id=rel.itemid '  // to get info for item's categories
				.' LEFT JOIN #__categories AS c ON c.id=rel.catid '
				.' WHERE 1 '
				. @ $item_where
				. @ $type_where
				. @ $cat_where
				. @ $itemowned_where
				. ($scopes_where ? ' AND ' . implode(' AND ', $scopes_where) : '')
				.' GROUP BY i.id '
				//. $orderby
				//.@ $limit
				.') AS sq';
		}
		else
		{
			$query = 'SELECT ' . ($itemcount ? 'SQL_CALC_FOUND_ROWS' : '') . ' i.*, ext.*,'
				.' GROUP_CONCAT(c.id SEPARATOR  ",") AS catidlist, '
				.' GROUP_CONCAT(c.alias SEPARATOR  ",") AS  cataliaslist '
				. @ $orderby_col
				.' FROM #__content AS i '
				.' LEFT JOIN #__flexicontent_items_ext AS ext ON i.id=ext.item_id '
				. @ $item_join
				. @ $orderby_join
				.' LEFT JOIN #__flexicontent_cats_item_relations AS rel ON i.id=rel.itemid '  // to get info for item's categories
				.' LEFT JOIN #__categories AS c ON c.id=rel.catid '
				.' WHERE 1 '
				. @ $item_where
				. @ $type_where
				. @ $cat_where
				. @ $itemowned_where
				. ($scopes_where ? ' AND ' . implode(' AND ', $scopes_where) : '')
				.' GROUP BY i.id '
				. $orderby
				.@ $limit
				;
		}

		//echo "<pre>".$query."</pre>";
		return $query;
	}


	// Helper method to create HTML display of an item list according to replacements
	static function createItemsListHTML($params, & $item_list, $field, $item, & $itemIDs, $options)
	{
		$isform = isset($options->isform) ? (int) $options->isform : 0;
		require_once (JPATH_SITE.DS.'components'.DS.'com_flexicontent'.DS.'helpers'.DS.'route.php');

		$db = JFactory::getDbo();
		global $globalcats, $globalnoroute, $fc_run_times;
		if (!is_array($globalnoroute)) $globalnoroute = array();

		// Get fields of type relation
		static $disallowed_fieldnames = null;
		$disallowed_fields = array('relation', 'relation_reverse');
		if ($disallowed_fieldnames===null)
		{
			$query = "SELECT name FROM #__flexicontent_fields WHERE field_type IN ('". implode("','", $disallowed_fields) ."')";
			$db->setQuery($query);
			$field_name_col = $db->loadColumn();
			$disallowed_fieldnames = !$field_name_col ? array() : array_flip($field_name_col);
		}

		// Prefix - Suffix - Separator parameters, replacing other field values if found
		$remove_space	= $params->get( 'remove_space', 0 ) ;
		$pretext			= $params->get( $isform ? 'pretext_form' : 'pretext', '' ) ;
		$posttext			= $params->get( $isform ? 'posttext_form' : 'posttext', '' ) ;
		$separatorf		= $params->get( $isform ? 'separator' : 'separatorf' ) ;
		$opentag			= $params->get( $isform ? 'opentag_form' : 'opentag', '' ) ;
		$closetag			= $params->get( $isform ? 'closetag_form' : 'closetag', '' ) ;

		if($pretext)  { $pretext  = $remove_space ? $pretext : $pretext . ' '; }
		if($posttext) { $posttext = $remove_space ? $posttext : ' ' . $posttext; }

		switch($separatorf)
		{
			case 0:
			$separatorf = '&nbsp;';
			break;

			case 1:
			$separatorf = '<br class="fcclear" />';
			break;

			case 2:
			$separatorf = '&nbsp;|&nbsp;';
			break;

			case 3:
			$separatorf = ',&nbsp;';
			break;

			case 4:
			$separatorf = $closetag . $opentag;
			break;

			case 5:
			$separatorf = '';
			break;

			default:
			$separatorf = '&nbsp;';
			break;
		}

		// some parameter shortcuts
		$relitem_html_param = $params->get('relitem_html_override', $isform ? 'relitem_html_form' : 'relitem_html');
		$relitem_html = $params->get( $relitem_html_param, '__display_text__' ) ;
		$displayway		= $params->get( $isform ? 'displayway_form' : 'displayway', 1 ) ;
		$addlink 			= $params->get( $isform ? 'addlink_form' : 'addlink', 1 ) ;
		$addtooltip		= $params->get( $isform ? 'addtooltip_form' : 'addtooltip', 1 ) ;

		// Parse and identify custom fields
		$result = preg_match_all("/\{\{([a-zA-Z_0-9-]+)(##)?([a-zA-Z_0-9-]+)?\}\}/", $relitem_html, $field_matches);
		$custom_field_reps    = $result ? $field_matches[0] : array();
		$custom_field_names   = $result ? $field_matches[1] : array();
		$custom_field_methods = $result ? $field_matches[3] : array();

		/*
		foreach ($custom_field_names as $i => $custom_field_name)
		{
			$parsed_fields[] = $custom_field_names[$i] . ($custom_field_methods[$i] ? "->". $custom_field_methods[$i] : "");
		}
		echo "$relitem_html :: Fields for Related Items List: ". implode(", ", $parsed_fields ? $parsed_fields : array() ) ."<br/>\n";
		*/

		// ***
		// *** Parse and identify language strings and then make language replacements
		// ***

		$result = preg_match_all("/\%\%([^%]+)\%\%/", $relitem_html, $translate_matches);
		$translate_strings = $result ? $translate_matches[1] : array('FLEXI_READ_MORE_ABOUT');
		foreach ($translate_strings as $translate_string)
		{
			$relitem_html = str_replace('%%'.$translate_string.'%%', JText::_($translate_string), $relitem_html);
		}

		foreach($item_list as $result)
		{
			$itemslug = $result->id.":".$result->alias;
			$catslug = "";

			// Check if removed from category or inside a noRoute category or inside a non-published category and use main category slug or other routable & published category slug
			$catid_arr = explode(',', $result->catidlist);
			$catalias_arr = explode(',', $result->cataliaslist);

			for($i=0; $i<count($catid_arr); $i++)
			{
				$itemcataliases[$catid_arr[$i]] = $catalias_arr[$i];
			}

			$rel_itemid = $result->id;
			$rel_catid = isset($itemIDs[$result->id]->catid)
				? $itemIDs[$result->id]->catid
				: $result->catid;

			if ( isset($itemcataliases[$rel_catid]) && !in_array($rel_catid, $globalnoroute) && $globalcats[$rel_catid]->published)
			{
				$catslug = $rel_catid.":".$itemcataliases[$rel_catid];
			}

			else if (!in_array($result->catid, $globalnoroute) && $globalcats[$result->catid]->published )
			{
				$catslug = $globalcats[$result->catid]->slug;
			}

			else
			{
				foreach ($catid_arr as $catid)
				{
					if ( !in_array($catid, $globalnoroute) && $globalcats[$catid]->published)
					{
						$catslug = $globalcats[$catid]->slug;
						break;
					}
				}
			}

			$result->slug = $itemslug;
			$result->categoryslug = $catslug;
		}


		// ***
		// *** Perform field's display replacements
		// ***

		$i_slave = $field ? $item->id . '_' . $field->id : '';
		if ( $i_slave )
		{
			$fc_run_times['render_subfields'][$i_slave] = 0;
		}

		// Treat list creation as the 'sublist' VIEW case
		$_view = 'sublist';

		foreach($custom_field_names as $i => $custom_field_name)
		{
			if ( isset($disallowed_fieldnames[$custom_field_name]) ) continue;
			if ( $custom_field_methods[$i] == 'label' ) continue;

			if ($i_slave) $start_microtime = microtime(true);

			$display_var = $custom_field_methods[$i] ? $custom_field_methods[$i] : 'display';
			FlexicontentFields::getFieldDisplay($item_list, $custom_field_name, $custom_field_values=null, $display_var, $_view);

			if ($i_slave) $fc_run_times['render_subfields'][$i_slave] += round(1000000 * 10 * (microtime(true) - $start_microtime)) / 10;
		}

		$tooltip_class = ' hasTooltip';
		$display_html = array();
		$read_more_about = JText::_('FLEXI_READ_MORE_ABOUT', true);
		foreach($item_list as $result)
		{
			$url_read_more = isset($itemIDs[$result->id]->url_read_more)
				? JText::_($itemIDs[$result->id]->url_read_more, true)
				: $read_more_about;
			$url_class = isset($itemIDs[$result->id]->url_class)
				? $itemIDs[$result->id]->url_class
				: 'relateditem';

			// a. Replace some custom made strings
			$item_url = JRoute::_(FlexicontentHelperRoute::getItemRoute($result->slug, $result->categoryslug, 0, $result));
			$item_title_escaped = htmlspecialchars($result->title, ENT_COMPAT, 'UTF-8');

			$tooltip_title = flexicontent_html::getToolTip($url_read_more, $item_title_escaped, $translate=0, $escape=0);
			$item_tooltip = ' class="'.$url_class.$tooltip_class.'" title="'.$tooltip_title.'" ';

			$display_text = $displayway ? $result->title : $result->id;
			$display_text = !$addlink ? $display_text : '<a href="'.$item_url.'"'.($addtooltip ? $item_tooltip : '').' >' .$display_text. '</a>';

			$curr_relitem_html = $relitem_html;
			$curr_relitem_html = str_replace('__item_url__', $item_url, $curr_relitem_html);
			$curr_relitem_html = str_replace('__item_title_escaped__', $item_title_escaped, $curr_relitem_html);
			$curr_relitem_html = str_replace('__item_tooltip__', $item_tooltip, $curr_relitem_html);
			$curr_relitem_html = str_replace('__display_text__', $display_text, $curr_relitem_html);

			// b. Replace item properties, e.g. {item->id}, (item->title}, etc
			$null_field = null;
			FlexicontentFields::doQueryReplacements($curr_relitem_html, $null_field, $result);

			// c. Replace HTML display of various item fields
			$err_mssg = 'Cannot replace field: "%s" because it is of not allowed field type: "%s", which can cause loop or other problem';
			foreach($custom_field_names as $i => $custom_field_name)
			{
				$_field = @ $result->fields[$custom_field_name];
				$custom_field_display = '';
				if ($is_disallowed_field = isset($disallowed_fieldnames[$custom_field_name]))
				{
					$custom_field_display .= sprintf($err_mssg, $custom_field_name, @ $_field->field_type);
				}
				else
				{
					$display_var = $custom_field_methods[$i] ? $custom_field_methods[$i] : 'display';
					$custom_field_display .= @ $_field->{$display_var};
				}
				$curr_relitem_html = str_replace($custom_field_reps[$i], $custom_field_display, $curr_relitem_html);
			}

			$result->ri_url  = $item_url;
			$result->ri_html = $pretext . $curr_relitem_html . $posttext;
			$display_html[] = $result->ri_html;
		}

		// Return item list data array
		$return_items_array = isset($options->return_items_array) ? (int) $options->return_items_array : 0;
		if ($return_items_array)
		{
			return $item_list;
		}

		// Return item list HTML
		else
		{
			$display_html = implode($separatorf, $display_html);
			return $display_html
				? $opentag . $display_html . $closetag
				: '';
		}
	}





	// **********************************************
	// Helper methods for handling runtime statistics
	// **********************************************

	static function getFieldRenderTimes( &$fields_render_total=0 )
	{
		global $fc_run_times;
		$fields_render = array();

		$inline_css_val = 'float:left !important; display:inline-block !important;';
		$inline_css_lbl = 'float:left !important; display:inline-block !important; margin-left:8px !important; min-width:100px; text-align:left !important;';
		foreach ($fc_run_times['render_field'] as $field_type => $field_msecs)
		{
			// Total rendering time of fields
			$fields_render_total += $field_msecs;

			// Create Log a message about current field rendering time
			$fld_msg =
				'<span class="flexi value" style="'.$inline_css_val.'">'. sprintf("%.3f s",$field_msecs/1000000) .'</span>'.
				'<span class="flexi label" style="'.$inline_css_lbl.'">'.$field_type.'</span>'
				;
			// Check if field rendered other fields as part of it's display
			if ( isset($fc_run_times['render_subfields'][$field_type]) ) {
				$fld_msg .= " <small> - Field rendered other fields. Time was (retrieval+render)= ";
				$fld_msg .= sprintf("%.3f s", $fc_run_times['render_subfields'][$field_type]/1000000).'</small>';
			}
			$fields_render[] = $fld_msg;
		}
		return $fields_render;
	}


	static function getFilterCreationTimes( &$filters_creation_total=0 )
	{
		global $fc_run_times;
		$filters_creation = array();

		if ( isset($fc_run_times['create_filter_init']) ) {
			$filters_creation_total += $fc_run_times['create_filter_init'];
		}
		$inline_css_val = 'float:left !important; display:inline-block !important;';
		$inline_css_lbl = 'float:left !important; display:inline-block !important; margin-left:8px !important; min-width:100px !important; text-align:left !important;';
		foreach ($fc_run_times['create_filter'] as $field_type => $filter_msecs)
		{
			// Total creation time of filters
			$filters_creation_total += $filter_msecs;

			// Create Log a message about current filter creation time
			$fld_msg =
				'<span class="" style="'.$inline_css_val.'">'. sprintf("%.3f s",$filter_msecs/1000000) .'</span>'.
				'<span class="flexi label" style="'.$inline_css_lbl.'">'.$field_type.'</span>'.
				'<span class="" style="'.$inline_css_val.' min-width:200px;">'.$fc_run_times['create_filter_type'][$field_type].'</span>'
				;

			$filters_creation[] = $fld_msg;
		}
		return $filters_creation;
	}



	static function & getFieldsPerGroup()
	{
		static $ginfo = null;
		if ( $ginfo!==null ) return $ginfo;

		$db = JFactory::getDbo();
		$query = 'SELECT f.* '
			. ' FROM #__flexicontent_fields AS f '
			. ' WHERE f.published = 1'
			. ' AND f.field_type = "fieldgroup" '
			;
		$db->setQuery($query);
		$field_groups = $db->loadObjectList('id');

		$grp_to_field = array();
		$field_to_grp = array();

		foreach($field_groups as $field_id => $field_group)
		{
			// Create field parameters, if not already created, NOTEL: for 'custom' fields loadFieldConfig() is optional
			$field_group->parameters = new JRegistry($field_group->attribs);

			$fieldids = $field_group->parameters->get('fields', array());

			if (empty($fieldids))
			{
				$fieldids = array();
			}

			if (!is_array($fieldids))
			{
				$fieldids = preg_split("/[\|,]/", $fieldids);
			}

			$field_group->label = JText::_($field_group->label);

			foreach ($fieldids as $grouped_fieldid)
			{
				$grp_to_field[$field_id][] = $grouped_fieldid;
				$field_to_grp[$grouped_fieldid] = $field_id;
			}
		}

		$ginfo = new stdClass;
		$ginfo->grps = $field_groups;
		$ginfo->grp_to_field = $grp_to_field;
		$ginfo->field_to_grp = $field_to_grp;

		return $ginfo;
	}



	/**
	 * Method to get a basic display for fields value data of an item view
	 *
	 * $field->raw_values    (Array of raw unserialized values)
	 * $field->basic_texts   (Array of basic textual display of values)
	 *
	 * @param 	object    $item      the item object of the item component view
	 * @param 	array     $fields    the fields of the item
	 */
	public static function getBasicFieldData($item, $fields)
	{
		foreach ($fields as $fn => $field)
		{
			$field->raw_values  = array();
			$field->basic_texts = array();

			if ($field->iscore)
			{
				$v = $item->$fn;

				$field->raw_values = $v;

				if ($field->field_type === 'type')
				{
					$v = $item->type_id;

					$field->raw_values  = $v;
					$field->basic_texts = JText::_($item->$fn);
				}
				elseif ($field->field_type === 'createdby' || $field->field_type === 'modifiedby')
				{
					$field->raw_values  = $v;
					$field->basic_texts = $field->field_type === 'createdby' ? $item->author : $item->modifier;
				}
				elseif ($field->field_type === 'state')
				{
					$v = $item->$fn;

					$state_names = array(
						1=>JText::_('FLEXI_PUBLISHED'), -5=>JText::_('FLEXI_IN_PROGRESS'), 0=>JText::_('FLEXI_UNPUBLISHED'),
						-3=>JText::_('FLEXI_PENDING'), -4=>JText::_('FLEXI_TO_WRITE'),
						2=>JText::_('FLEXI_ARCHIVED'), -2=>JText::_('FLEXI_TRASHED'),
					);

					$field->raw_values  = $v;
					$field->basic_texts = isset($state_names[$v]) ? $state_names[$v] : JText::_('FLEXI_UNKNOWN');
				}
				else
				{
					$v = $item->$fn;

					$field->raw_values = $v;
				}
			}

			else
			{
				$field->raw_values = isset($item->fieldvalues[$field->id]) ? $item->fieldvalues[$field->id] : array();

				// Safely unserialize
				foreach ($field->raw_values as $i => $v)
				{
					$_v = flexicontent_db::unserialize_array($v, $force_array = true, $force_value = false);
					if ($_v !== false)
					{
						$field->raw_values[$i] = $_v;
					}
				}

				if (in_array($field->field_type, array('select', 'selectmultiple', 'radio', 'radioimage', 'checkbox', 'checkboximage')))
				{
					if (empty($field->parameters))
					{
						FlexicontentFields::loadFieldConfig($field, $item);
					}

					$field->elements = isset($field->elements)
						? $field->elements
						: FlexicontentFields::indexedField_getElements($field, $item);
					$field->basic_texts = array();

					foreach ($field->raw_values as $i => $v)
					{
						if (is_array($v))
						{
							foreach($v as $ii => $vv)
							{
								$field->basic_texts[$fn][$vv] = $field->elements[$vv]->text;
							}
						}
						else
						{
							$field->basic_texts[$fn][$v] = $field->elements[$v]->text;
						}
					}
				}
			}

			/*echo '<pre> <h2>' . $fn . '</h2> <h4>' . $field->field_type . '</h4>';
			print_r(!empty($field->basic_texts) ? $field->basic_texts : $field->raw_values);
			echo '</pre>';*/
		}
	}


	/**
	 * Method to get a basic display for filters value data of a category view
	 *
	 * $filter->raw_values    (Array of filter values filtered via STRING)
	 * $filter->basic_texts   (Array of basic textual display of values)
	 *
	 * @param 	object    $category   the category object of the category component view
	 * @param 	array     $filters    the filters of the category-based view
	 */
	public static function getBasicFilterData($category, $filters)
	{
		$jinput = JFactory::getApplication()->input;

		foreach ($filters as $fn => $filter)
		{
			$fn = $filter->name;

			$filter->raw_values  = array();
			$filter->basic_texts = array();

			$vals = $jinput->get('filter_' . $filter->id, null, 'raw');

			if (!is_array($vals))
			{
				if (strlen($vals ?? ''))
				{
					$vals = array($vals);
				}
				else
				{
					continue;
				}
			}

			// Sanitize user submitted input ... and ... remove empty zero length values and empty arrays
			$safe_vals = self::cleanArray($vals, 'STRING', 3);

			if (!is_array($safe_vals) && !strlen($safe_vals))
			{
				continue;
			}

			$filter->raw_values = $safe_vals;

			if ($filter->field_type === 'state')
			{
				$state_names = array(
					1=>JText::_('FLEXI_PUBLISHED'), -5=>JText::_('FLEXI_IN_PROGRESS'), 0=>JText::_('FLEXI_UNPUBLISHED'),
					-3=>JText::_('FLEXI_PENDING'), -4=>JText::_('FLEXI_TO_WRITE'),
					2=>JText::_('FLEXI_ARCHIVED'), -2=>JText::_('FLEXI_TRASHED'),
				);


				foreach($safe_vals as $v)
				{
					$filter->basic_texts = array($v => (isset($state_names[$v]) ? $state_names[$v] : JText::_('FLEXI_UNKNOWN')));
				}
			}

			elseif (in_array($filter->field_type, array('select', 'selectmultiple', 'radio', 'radioimage', 'checkbox', 'checkboximage')))
			{
				$_item = null;

				$filter->elements = isset($filter->elements)
					? $filter->elements
					: FlexicontentFields::indexedField_getElements($filter, $_item);
				$filter->basic_texts = array();

				foreach ($safe_vals as $i => $v)
				{
					$filter->basic_texts[$v] = $filter->elements[$v]->text;
				}
			}

			/*echo '<pre> <h2>' . $fn . '</h2> <h4>' . $filter->field_type . '</h4>';
			print_r(!empty($filter->basic_texts) ? $filter->basic_texts : $filter->raw_values);
			echo '</pre>';*/
		}
	}

	static function cleanArray($value, $validation = 'STRING', $max_depth = 10)
	{
		static $depth = 0;
		static $noHtmlFilter = null;

		/*
		 * Do not allow = , just for beging safer, in case someone output this as HTML tag property without encoding it,
		 * a combination of = and quotes is unsafe ...
		 */

		if ($noHtmlFilter === null)
		{
			$noHtmlFilter = JFilterInput::getInstance();
		}

		if (!is_array($value))
		{
			$value = $noHtmlFilter->clean($value, $validation);
			$value = str_replace('=', '', $value);

			return strlen($value) ? $value : '';
		}
		elseif ($depth <= $max_depth)
		{
			++$depth;

			foreach ($value as $i => $v)
			{
			  $v = self::cleanArray($v, $validation, $max_depth);

				if (is_array($v) || strlen($v))
				{
					$value[$i] = $v;
				}
				else
				{
					unset($value[$i]);
				}

			}

			--$depth;

			return count($value) ? $value : '';
		}
		else
		{
			return '';
		}
	}
}