<?php
/**
* @version	$Id: search_helper.php 16513 2017-01-20 14:10:53Z alex $
* @package	In-Portal
* @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license      GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/

	defined('FULL_PATH') or die('restricted access!');

	class kSearchHelper extends kHelper {

		/**
		 * Perform Exact Search flag
		 *
		 * @var bool
		 * @access protected
		 */
		protected $_performExactSearch = true;

		public function __construct()
		{
			parent::__construct();

			$this->_performExactSearch = $this->Application->ConfigValue('PerformExactSearch');
		}

		/**
		 * Splits search phrase into keyword using quotes,plus and minus sings and spaces as split criteria
		 *
		 * @param string $keyword
		 * @return Array
		 * @access public
		 */
		public function splitKeyword($keyword)
		{
			if ( $this->Application->ConfigValue('CheckStopWords') ) {
				$keyword_after_remove = $this->_removeStopWords($keyword);
				if ( $keyword_after_remove ) {
					// allow to search through stop word grid
					$keyword = $keyword_after_remove;
				}
			}

			$final = Array ();
			$quotes_re = '/([+\-]?)"(.*?)"/';
			$no_quotes_re = '/([+\-]?)([^ ]+)/';

			preg_match_all($quotes_re, $keyword, $res);
			foreach ($res[2] as $index => $kw) {
				$final[$kw] = $res[1][$index];
			}

			$keyword = preg_replace($quotes_re, '', $keyword);
			preg_match_all($no_quotes_re, $keyword, $res);

			foreach ($res[2] as $index => $kw) {
				$final[$kw] = $res[1][$index];
			}

			if ( $this->_performExactSearch ) {
				foreach ($final AS $kw => $plus_minus) {
					if ( !$plus_minus ) {
						$final[$kw] = '+';
					}
				}
			}

			return $final;
		}

		function getPositiveKeywords($keyword)
		{
			$keywords = $this->splitKeyword($keyword);

			$ret = Array();
			foreach ($keywords as $keyword => $sign) {
				if ($sign == '+' || $sign == '') {
					$ret[] = $keyword;
				}
			}
			return $ret;
		}

		/**
		 * Replace wildcards to match MySQL
		 *
		 * @param string $keyword
		 * @return string
		 */
		function transformWildcards($keyword)
		{
			return str_replace(Array ('%', '_', '*', '?') , Array ('\%', '\_', '%', '_'), $keyword);
		}

		function buildWhereClause($keyword, $fields)
		{
			$keywords = $this->splitKeyword( $this->transformWildcards($keyword) );

			$normal_conditions = $plus_conditions = $minus_conditions = Array();

			foreach ($keywords as $keyword => $sign) {
				$keyword = $this->Conn->escape($keyword);

				switch ($sign) {
					case '+':
						$plus_conditions[] = implode(" LIKE '%" . $keyword . "%' OR ", $fields) . " LIKE '%" . $keyword . "%'";
						break;

					case '-':
						$condition = Array ();

						foreach ($fields as $field) {
							$condition[] = $field . " NOT LIKE '%" . $keyword . "%' OR " . $field . ' IS NULL';
						}
						$minus_conditions[] = '(' . implode(') AND (', $condition) . ')';
						break;

					case '':
						$normal_conditions[] = implode(" LIKE '%" . $keyword . "%' OR ", $fields) . " LIKE '%" . $keyword . "%'";
						break;
				}
			}

			// building where clause
			if ($normal_conditions) {
				$where_clause = '(' . implode(') OR (', $normal_conditions) . ')';
			}
			else {
				$where_clause = '1';
			}

			if ($plus_conditions) {
				$where_clause = '(' . $where_clause . ') AND (' . implode(') AND (', $plus_conditions) . ')';
			}

			if ($minus_conditions) {
				$where_clause = '(' . $where_clause . ') AND (' . implode(') AND (', $minus_conditions) . ')';
			}

			return $where_clause;
		}

		/**
		 * Returns additional information about search field
		 *
		 * @param kDBList $object
		 * @param string $field_name
		 * @return Array
		 */
		function _getFieldInformation(&$object, $field_name)
		{
			$sql_filter_type = $object->isVirtualField($field_name) ? 'having' : 'where';
			$field_options = $object->GetFieldOptions($field_name);

			$table_name = '';
			$field_type = isset($field_options['type']) ? $field_options['type'] : 'string';

			if (preg_match('/(.*)\.(.*)/', $field_name, $regs)) {
				$table_name = '`'.$regs[1].'`.'; // field from external table
				$field_name = $regs[2];
			}
			elseif ($sql_filter_type == 'where') {
				$table_name = '`'.$object->TableName.'`.'; // field from local table
			}

			$table_name = ($sql_filter_type == 'where') ? $table_name : '';

			// replace wid inside table name to WID_MARK constant value
			$is_temp_table = preg_match('/(.*)'.TABLE_PREFIX.'ses_'.$this->Application->GetSID().'(_[\d]+){0,1}_edit_(.*)/', $table_name, $regs);
			if ($is_temp_table) {
				$table_name = $regs[1].TABLE_PREFIX.'ses_'.EDIT_MARK.'_edit_'.$regs[3]; // edit_mark will be replaced with sid[_main_wid] in AddFilters
			}

			return Array ($field_name, $field_type, $table_name, $sql_filter_type);
		}

		/**
		 * Removes stop words from keyword
		 *
		 * @param string $keyword
		 * @return string
		 */
		function _removeStopWords($keyword)
		{
			static $stop_words = Array ();

			if (!$stop_words) {
				$sql = 'SELECT StopWord
						FROM ' . $this->Application->getUnitOption('stop-word', 'TableName') . '
						ORDER BY LENGTH(StopWord) DESC, StopWord ASC';
				$stop_words = $this->Conn->GetCol($sql);

				foreach ($stop_words as $index => $stop_word) {
					$stop_words[$index] = '/(^| )' . preg_quote($stop_word, '/') . '( |$)/';
				}
			}

			$keyword = preg_replace($stop_words, ' ', $keyword);

			return trim( preg_replace('/[ ]+/', ' ', $keyword) );
		}

		/**
		 * Performs new search on a given grid
		 *
		 * @param kEvent $event
		 * @return void
		 * @access public
		 */
		public function performSearch($event)
		{
			/** @var kDBItem $object */
			$object = $event->getObject();

			// process search keyword
			$search_keyword = $this->Application->GetVar($event->getPrefixSpecial(true) . '_search_keyword');
			$this->Application->StoreVar($event->getPrefixSpecial() . '_search_keyword', $search_keyword);

			$custom_filter = $this->processCustomFilters($event);

			if ( !$search_keyword && $custom_filter === false ) {
				$this->resetSearch($event);
				return ;
			}

			if ( $search_keyword ) {
				$this->processAutomaticFilters($event, $search_keyword, $custom_filter);
			}
		}

		/**
		 * Creates filtering sql clauses based on given search restrictions
		 *
		 * @param kEvent $event
		 * @param string $search_keyword
		 * @param Array $custom_filter
		 * @return void
		 */
		function processAutomaticFilters($event, $search_keyword, $custom_filter)
		{
			$grid_name = $this->Application->GetVar('grid_name');
			$grids = $this->Application->getUnitOption($event->Prefix, 'Grids');
			$search_fields = array_keys($grids[$grid_name]['Fields']);

			$search_filter = Array();

			/** @var kDBList $object */
			$object = $event->getObject();

			foreach ($search_fields as $search_field) {
				$custom_search = isset($custom_filter[$search_field]);

				$filter_data = $this->getSearchClause($object, $search_field, $search_keyword, $custom_search);

				if ($filter_data) {
					$search_filter[$search_field] = $filter_data;
				}
				else {
					unset($search_filter[$search_field]);
				}
			}
			$this->Application->StoreVar($event->getPrefixSpecial().'_search_filter', serialize($search_filter) );
		}

		/**
		 * Returns search clause for any particular field
		 *
		 * @param kDBList $object
		 * @param string $field_name
		 * @param string $search_keyword what we are searching (false, when building custom filter clause)
		 * @param string $custom_search already found using custom filter
		 * @return Array
		 */
		function getSearchClause(&$object, $field_name, $search_keyword, $custom_search)
		{
			if ($object->isVirtualField($field_name) && !$object->isCalculatedField($field_name)) {
				// Virtual field, that is shown in grid, but it doesn't have corresponding calculated field.
				// Happens, when field value is calculated on the fly (during grid display) and it is not searchable.
				return '';
			}

			$search_keywords = $this->splitKeyword($search_keyword);

			list ($field_name, $field_type, $table_name, $sql_filter_type) = $this->_getFieldInformation($object, $field_name);

			$filter_value = '';

			// get field clause by formatter name and/or parameters
			$field_options = $object->GetFieldOptions($field_name);
			$formatter = getArrayValue($field_options, 'formatter');

			switch ($formatter) {
				case 'kOptionsFormatter':
					$search_keys = Array();

					if ($custom_search === false) {
						// if keywords passed through simple search filter (on each grid)
						$use_phrases = getArrayValue($field_options, 'use_phrases');
						$multiple = array_key_exists('multiple', $field_options) && $field_options['multiple'];

						foreach ($field_options['options'] as $key => $val) {
							$match_to = mb_strtolower($use_phrases ? $this->Application->Phrase($val) : $val);

							foreach ($search_keywords as $keyword => $sign) {
								// doesn't support wildcards
								if (strpos($match_to, mb_strtolower($keyword)) === false) {
									if ($sign == '+') {
										$filter_value = $table_name.'`'.$field_name.'` = NULL';
										break;
									}
									else {
										continue;
									}
								}

								if ($sign == '+' || $sign == '') {
									// don't add single quotes to found option ids when multiselect (but escape string anyway)
									$search_keys[$key] = $multiple ? $this->Conn->escape($key) : $this->Conn->qstr($key);
								}
								elseif($sign == '-') {
									// if same value if found as exclusive too, then remove from search result
									unset($search_keys[$key]);
								}
							}
						}
					}

					if ($search_keys) {
						if ($multiple) {
							$filter_value = $table_name.'`'.$field_name.'` LIKE "%|' . implode('|%" OR ' . $table_name.'`'.$field_name.'` LIKE "%|', $search_keys) . '|%"';
						}
						else {
							$filter_value = $table_name.'`'.$field_name.'` IN ('.implode(',', $search_keys).')';
						}
					}

					$field_processed = true;
					break;

				case 'kDateFormatter':
					// if date is searched using direct filter, then do nothing here, otherwise search using LIKE clause
					$field_processed = ($custom_search !== false) ? true : false;
					break;

				default:
					$field_processed = false;
					break;
			}

			// if not already processed by formatter, then get clause by field type
			if (!$field_processed && $search_keywords) {
				switch($field_type)
				{
					case 'int':
					case 'integer':
					case 'numeric':
						$search_keys = Array();
						foreach ($search_keywords as $keyword => $sign) {
							if (!is_numeric($keyword) || ($sign == '-')) {
								continue;
							}
							$search_keys[] = $this->Conn->qstr($keyword);
						}

						if ($search_keys) {
							$filter_value = $table_name.'`'.$field_name.'` IN ('.implode(',', $search_keys).')';
						}
						break;

					case 'double':
					case 'float':
					case 'real':
						$search_keys = Array();
						foreach ($search_keywords as $keyword => $sign) {
							$keyword = str_replace(',', '.', $keyword);
							if (!is_numeric($keyword) || ($sign == '-')) continue;
							$search_keys[] = 'ABS('.$table_name.'`'.$field_name.'` - '.$this->Conn->qstr($keyword).') <= 0.0001';
						}

						if ($search_keys) {
							$filter_value = '('.implode(') OR (', $search_keys).')';
						}
						break;

					case 'string':
						$filter_value = $this->buildWhereClause($search_keyword, Array($table_name.'`'.$field_name.'`'));
						break;
				}
			}

			if ($filter_value) {
				return Array('type' => $sql_filter_type, 'value' => $filter_value);
			}

			return false;
		}

		/**
		 * Processes custom filters from submit
		 *
		 * @param kEvent $event
		 * @return Array|bool
		 */
		function processCustomFilters($event)
		{
			$grid_name = $this->Application->GetVar('grid_name');

			// update "custom filter" with values from submit: begin
			$view_name = $this->Application->RecallVar($event->getPrefixSpecial().'_current_view');
			$custom_filters = $this->Application->RecallPersistentVar($event->getPrefixSpecial().'_custom_filter.'.$view_name/*, ALLOW_DEFAULT_SETTINGS*/);
			if ($custom_filters) {
				$custom_filters = unserialize($custom_filters);
				$custom_filter = isset($custom_filters[$grid_name]) ? $custom_filters[$grid_name] : Array ();
			}
			else {
				$custom_filter = Array ();
			}

			// submit format custom_filters[prefix_special][field]
			$submit_filters = $this->Application->GetVar('custom_filters');
			if ($submit_filters) {
				$submit_filters = getArrayValue($submit_filters, $event->getPrefixSpecial(), $grid_name);
				if ($submit_filters) {
					foreach ($submit_filters as $field_name => $field_options) {
						list ($filter_type, $field_value) = each($field_options);
						$is_empty = strlen(is_array($field_value) ? implode('', $field_value) : $field_value) == 0;
						if ($is_empty) {
							if (isset($custom_filter[$field_name])) {
								// use isset, because non-existing key will cause "php notice"!
								unset($custom_filter[$field_name][$filter_type]); // remove filter

								if (!$custom_filter[$field_name]) {
									// if no filters left for field, then delete record at all
									unset($custom_filter[$field_name]);
								}
							}
						}
						else {
							$custom_filter[$field_name][$filter_type]['submit_value'] = $field_value;
						}
					}
				}
			}

			if ($custom_filter) {
				$custom_filters[$grid_name] = $custom_filter;
			}
			else {
				unset($custom_filters[$grid_name]);
			}
			// update "custom filter" with values from submit: end

			if (!$custom_filter) {
				// in case when no filters specified, there are nothing to process
				$this->Application->StorePersistentVar($event->getPrefixSpecial().'_custom_filter.'.$view_name, serialize($custom_filters) );
				return false;
			}

			$object = $event->getObject(); // don't recall it each time in getCustomFilterSearchClause
			$grid_info = $this->Application->getUnitOption($event->Prefix.'.'.$grid_name, 'Grids');
			foreach ($custom_filter as $field_name => $field_options) {
				list ($filter_type, $field_options) = each($field_options);
				$field_options['grid_options'] = $grid_info['Fields'][$field_name];
				$field_options = $this->getCustomFilterSearchClause($object, $field_name, $filter_type, $field_options);
				if ($field_options['value']) {
					unset($field_options['grid_options']);
					$custom_filter[$field_name][$filter_type] = $field_options;
				}
			}

			$custom_filters[$grid_name] = $custom_filter;
			$this->Application->StorePersistentVar($event->getPrefixSpecial().'_custom_filter.'.$view_name, serialize($custom_filters) );
			return $custom_filter;
		}

		/**
		 * Checks, that range filters "To" part is defined for given grid
		 *
		 * @param string $prefix_special
		 * @param string $grid_name
		 * @return bool
		 */
		function rangeFiltersUsed($prefix_special, $grid_name)
		{
			static $cache = Array ();
			$cache_key = $prefix_special . $grid_name;

			if (array_key_exists($cache_key, $cache)) {
				return $cache[$cache_key];
			}

			$view_name = $this->Application->RecallVar($prefix_special . '_current_view');
			$custom_filters = $this->Application->RecallPersistentVar($prefix_special . '_custom_filter.' . $view_name/*, ALLOW_DEFAULT_SETTINGS*/);
			if (!$custom_filters) {
				// filters not defined for given prefix
				$cache[$cache_key] = false;
				return false;
			}

			$custom_filters = unserialize($custom_filters);
			if (!is_array($custom_filters) || !array_key_exists($grid_name, $custom_filters)) {
				// filters not defined for given grid
				$cache[$cache_key] = false;
				return false;
			}

			$range_filter_defined = false;

			$custom_filter = $custom_filters[$grid_name];
			foreach ($custom_filter as $field_name => $field_options) {
				list ($filter_type, $field_options) = each($field_options);

				if (strpos($filter_type, 'range') === false) {
					continue;
				}

				$to_value = (string)$field_options['submit_value']['to'];
				if ($to_value !== '') {
					$range_filter_defined = true;
					break;
				}
			}

			$cache[$cache_key] = $range_filter_defined;
			return $range_filter_defined;
		}

		/**
		 * Return numeric range filter value + checking that it's number
		 *
		 * @param Array $value array containing range filter value
		 * @return unknown
		 */
		function getRangeValue($value)
		{
			// fix user typing error, since MySQL only sees "." as decimal separator
			$value = str_replace(',', '.', $value);

			return strlen($value) && is_numeric($value) ? $this->Conn->qstr($value) : false;
		}

		/**
		 * Returns filter clause
		 *
		 * @param kDBItem $object
		 * @param string $field_name
		 * @param string $filter_type
		 * @param Array $field_options
		 * @return Array
		 */
		function getCustomFilterSearchClause(&$object, $field_name, $filter_type, $field_options)
		{
			// this is usually used for mutlilingual fields and date fields
			if (isset($field_options['grid_options']['sort_field'])) {
				$field_name = $field_options['grid_options']['sort_field'];
			}
			list ($field_name, $field_type, $table_name, $sql_filter_type) = $this->_getFieldInformation($object, $field_name);

			$filter_value = '';

			switch ($filter_type) {
				case 'range':
					$from = $this->getRangeValue($field_options['submit_value']['from']);
					$to = $this->getRangeValue($field_options['submit_value']['to']);

					if ( $from !== false && $to !== false ) {
						// add range filter
						$filter_value = $table_name . '`' . $field_name . '` >= ' . $from . ' AND ' . $table_name . '`' . $field_name . '` <= ' . $to;
					}
					elseif ( $field_type == 'int' || $field_type == 'integer' ) {
						if ( $from !== false ) {
							// add equals filter on $from
							$filter_value = $table_name . '`' . $field_name . '` = ' . $from;
						}
						elseif ( $to !== false ) {
							// add equals filter on $to
							$filter_value = $table_name . '`' . $field_name . '` = ' . $to;
						}
					}
					else {
						// MySQL can't compare values in "float" type columns using "=" operator
						if ( $from !== false ) {
							// add equals filter on $from
							$filter_value = 'ABS(' . $table_name . '`' . $field_name . '` - ' . $from . ') <= 0.0001';
						}
						elseif ( $to !== false ) {
							// add equals filter on $to
							$filter_value = 'ABS(' . $table_name . '`' . $field_name . '` - ' . $to . ') <= 0.0001';
						}
					}
					break;

				case 'date_range':
					$from = $this->processRangeField($object, $field_name, $field_options['submit_value'], 'from');
					$to = $this->processRangeField($object, $field_name, $field_options['submit_value'], 'to');

					$day_seconds = 23 * 60 * 60 + 59 * 60 + 59;
					if ($from !== false && $to === false) {
						$from = strtotime(date('Y-m-d', $from) . ' 00:00:00', $from); // reset to morning
						$to = $from + $day_seconds;
					}
					elseif ($from === false && $to !== false) {
						$to = strtotime(date('Y-m-d', $to) . ' 23:59:59', $to); // reset to evening
						$from = $to - $day_seconds;
					}

					if ($from !== false && $to !== false) {
						$filter_value = $table_name.'`'.$field_name.'` >= '.$from.' AND '.$table_name.'`'.$field_name.'` <= '.$to;
					}
					break;

				case 'equals':
				case 'options':
					$field_value = strlen($field_options['submit_value']) ? $this->Conn->qstr($field_options['submit_value']) : false;
					if ($field_value) {
						$filter_value = $table_name.'`'.$field_name.'` = '.$field_value;
					}
					break;

				case 'picker':
					$field_value = strlen($field_options['submit_value']) ? $this->Conn->escape($field_options['submit_value']) : false;
					if ($field_value) {
						$filter_value = $table_name.'`'.$field_name.'` LIKE "%|'.$field_value.'|%"';
					}
					break;

				case 'multioptions':
					$field_value = $field_options['submit_value'];

					if ( $field_value ) {
						$field_value = explode('|', substr($field_value, 1, -1));
						$multiple = $object->GetFieldOption($field_name, 'multiple');
						$field_value = $this->Conn->qstrArray($field_value, $multiple ? 'escape' : 'qstr');

						if ( $multiple ) {
							$filter_value = $table_name . '`' . $field_name . '` LIKE "%|' . implode('|%" OR ' . $table_name . '`' . $field_name . '` LIKE "%|', $field_value) . '|%"';
						}
						else {
							$filter_value = $table_name . '`' . $field_name . '` IN (' . implode(',', $field_value) . ')';
						}
					}
					break;

				case 'like':
					$filter_value = $this->buildWhereClause($field_options['submit_value'], Array($table_name.'`'.$field_name.'`'));
					break;

				default:
					break;
			}

			$field_options['sql_filter_type'] = $sql_filter_type;
			$field_options['value'] = $filter_value;

			return $field_options;
		}

		/**
		 * Enter description here...
		 *
		 * @param kdbItem $object
		 * @param string $search_field
		 * @param string $value
		 * @param string $type
		 */
		function processRangeField(&$object, $search_field, $value, $type)
		{
			if ( !strlen($value[$type]) ) {
				return false;
			}

			$options = $object->GetFieldOptions($search_field);
			$dt_separator = array_key_exists('date_time_separator', $options) ? $options['date_time_separator'] : ' ';
			$value[$type] = trim($value[$type], $dt_separator); // trim any

			$tmp_value = explode($dt_separator, $value[$type], 2);
			if ( count($tmp_value) == 1 ) {
				$time_format = $this->_getInputTimeFormat($options);
				if ( $time_format ) {
					// time is missing, but time format available -> guess time and add to date
					$time = ($type == 'from') ? adodb_mktime(0, 0, 0) : adodb_mktime(23, 59, 59);
					$time = adodb_date($time_format, $time);
					$value[$type] .= $dt_separator . $time;
				}
			}

			/** @var kFormatter $formatter */
			$formatter = $this->Application->recallObject($options['formatter']);

			$value_ts = $formatter->Parse($value[$type], $search_field, $object);

			if ( $object->GetErrorPseudo($search_field) ) {
				// invalid format -> ignore this date in search
				$object->RemoveError($search_field);

				return false;
			}

			return $value_ts;
		}

		/**
		 * Returns InputTimeFormat using given field options
		 *
		 * @param Array $field_options
		 * @return string
		 */
		function _getInputTimeFormat($field_options)
		{
			if ( array_key_exists('input_time_format', $field_options) ) {
				return $field_options['input_time_format'];
			}

			/** @var LanguagesItem $lang_current */
			$lang_current = $this->Application->recallObject('lang.current');

			return $lang_current->GetDBField('InputTimeFormat');
		}

		/**
		 * Resets current search
		 *
		 * @param kEvent $event
		 */
		function resetSearch($event)
		{
			$this->Application->RemoveVar($event->getPrefixSpecial().'_search_filter');
			$this->Application->RemoveVar($event->getPrefixSpecial().'_search_keyword');

			$view_name = $this->Application->RecallVar($event->getPrefixSpecial().'_current_view');
			$this->Application->RemovePersistentVar($event->getPrefixSpecial().'_custom_filter.'.$view_name);
		}

		/**
		 * Creates filters based on "types" & "except" parameters from PrintList
		 *
		 * @param kEvent $event
		 * @param Array $type_clauses
		 * @param string $types
		 * @param string $except_types
		 */
		function SetComplexFilter($event, &$type_clauses, $types, $except_types)
		{
			/** @var kMultipleFilter $includes_or_filter */
			$includes_or_filter = $this->Application->makeClass('kMultipleFilter', Array (kDBList::FLT_TYPE_OR));

			/** @var kMultipleFilter $excepts_and_filter */
			$excepts_and_filter = $this->Application->makeClass('kMultipleFilter', Array (kDBList::FLT_TYPE_AND));

			/** @var kMultipleFilter $includes_or_filter_h */
			$includes_or_filter_h = $this->Application->makeClass('kMultipleFilter', Array (kDBList::FLT_TYPE_OR));

			/** @var kMultipleFilter $excepts_and_filter_h */
			$excepts_and_filter_h = $this->Application->makeClass('kMultipleFilter', Array (kDBList::FLT_TYPE_AND));

			if ( $types ) {
				$types = explode(',', $types);

				foreach ($types as $type) {
					$type = trim($type);

					if ( isset($type_clauses[$type]) ) {
						if ( $type_clauses[$type]['having_filter'] ) {
							$includes_or_filter_h->addFilter('filter_' . $type, $type_clauses[$type]['include']);
						}
						else {
							$includes_or_filter->addFilter('filter_' . $type, $type_clauses[$type]['include']);
						}
					}
				}
			}

			if ( $except_types ) {
				$except_types = explode(',', $except_types);
				foreach ($except_types as $type) {
					$type = trim($type);

					if ( isset($type_clauses[$type]) ) {
						if ( $type_clauses[$type]['having_filter'] ) {
							$excepts_and_filter_h->addFilter('filter_' . $type, $type_clauses[$type]['except']);
						}
						else {
							$excepts_and_filter->addFilter('filter_' . $type, $type_clauses[$type]['except']);
						}
					}
				}
			}

			/** @var kDBList $object */
			$object = $event->getObject();

			$object->addFilter('includes_filter', $includes_or_filter);
			$object->addFilter('excepts_filter', $excepts_and_filter);

			$object->addFilter('includes_filter_h', $includes_or_filter_h, kDBList::HAVING_FILTER);
			$object->addFilter('excepts_filter_h', $excepts_and_filter_h, kDBList::HAVING_FILTER);
		}

		/**
		 * Ensures empty search table
		 *
		 * @return void
		 */
		public function ensureEmptySearchTable()
		{
			$search_table = $this->getSearchTable();
			$this->Conn->Query('DROP TABLE IF EXISTS ' . $search_table);
			$sql = 'CREATE TABLE ' . $search_table . ' (
					  `Relevance` decimal(8,5) DEFAULT NULL,
					  `ItemId` int(11) NOT NULL DEFAULT 0,
					  `ResourceId` int(11) DEFAULT NULL,
					  `ItemType` int(1) NOT NULL DEFAULT 0,
					  `EdPick` tinyint(4) NOT NULL DEFAULT 0,
					  KEY `ResourceId` (`ResourceId`),
					  KEY `Relevance` (`Relevance`)
					)';
			$this->Conn->Query($sql);
		}

		/**
		 * Search table name
		 *
		 * @return string
		 */
		public function getSearchTable()
		{
			return TABLE_PREFIX . 'ses_' . $this->Application->GetSID() . '_' . TABLE_PREFIX . 'Search';
		}

	}
