Your IP : 3.148.210.23


Current Path : /var/www/axolotl/data/www/yar.axolotls.ru/bitrix/modules/tasks/classes/general/
Upload File :
Current File : /var/www/axolotl/data/www/yar.axolotls.ru/bitrix/modules/tasks/classes/general/taskfilterctrl.php

<?php
/**
 * Bitrix Framework
 * @package bitrix
 * @subpackage tasks
 * @copyright 2001-2013 Bitrix
 */

use Bitrix\Main;
use Bitrix\Tasks\Util\User;

IncludeModuleLangFile(__FILE__);


interface CTaskFilterCtrlInterface
{
	// Standart filter presets
	const ROOT_PRESET                        =  0;	// Root element for all presets
	const STD_PRESET_ACTIVE_MY_TASKS         = -1;
	const STD_PRESET_ACTIVE_I_AM_DOER        = -2;	// This is selected by default
	const STD_PRESET_ACTIVE_I_AM_ORIGINATOR  = -3;
	const STD_PRESET_ACTIVE_I_AM_AUDITOR     = -4;
	const STD_PRESET_DEFERRED_MY_TASKS       = -5;
	const STD_PRESET_COMPLETED_MY_TASKS      = -6;
	const STD_PRESET_ACTIVE_I_AM_RESPONSIBLE = -7;
	const STD_PRESET_ACTIVE_I_AM_ACCOMPLICE  = -8;
	const STD_PRESET_ALL_MY_TASKS            = -9;
	const STD_PRESET_EXPIRED                 = -11;
	const STD_PRESET_EXPIRED_SOON            = -12;
	const STD_PRESET_ALIAS_TO_DEFAULT        =  self::STD_PRESET_ALL_MY_TASKS;

	const SPEC_PRESET_FAVORITE_TASKS          = -10;

	// Identifications for CUserOptions
	// Warning: this is private constants, don't use it!!!
	const filterCategoryName  = 'tasks:mobile:filter';
	const filterParamPresetId = 'selected_preset_id';

	// For manifest of operations types
	const OP_EQUAL            = 0x001;		// no prefix for this case in filed name
	const OP_NOT_EQUAL        = 0x002;		// !
	const OP_SUBSTRING        = 0x003;		// %
	const OP_NOT_SUBSTRING    = 0x004;		// !%
	const OP_STRICTLY_LESS    = 0x010;		// <
	const OP_STRICTLY_GREATER = 0x011;		// >
	const OP_LESS_OR_EQUAL    = 0x012;		// <=
	const OP_GREATER_OR_EQUAL = 0x013;		// >=

	// Pseudo operations on date fields
	const OP_DATE_TODAY      = 0x005;		// #RC#
	const OP_DATE_YESTERDAY  = 0x006;		// #RC(-1)#
	const OP_DATE_TOMORROW   = 0x007;		// #RC1#
	const OP_DATE_CUR_WEEK   = 0x008;		// #R1C#
	const OP_DATE_PREV_WEEK  = 0x009;		// #R1C(-1)#
	const OP_DATE_NEXT_WEEK  = 0x00A;		// #R1C1#
	const OP_DATE_CUR_MONTH  = 0x00B;		// #R2C#
	const OP_DATE_PREV_MONTH = 0x00C;		// #R2C(-1)#
	const OP_DATE_NEXT_MONTH = 0x00D;		// #R2C1#
	const OP_DATE_LAST_DAYS  = 0x00E;		// #RC(-x)#
	const OP_DATE_NEXT_DAYS  = 0x00F;		// #RCx#

	// For manifest of field types
	const TYPE_TEXT     = 0x101;
	const TYPE_GROUP_ID = 0x102;
	const TYPE_USER_ID  = 0x103;
	const TYPE_STATUS   = 0x104;
	const TYPE_DATE     = 0x105;
	const TYPE_PRIORITY = 0x106;

	// Import modes
	const IMPORT_MODE_CREATE  = 0x01;	// Create new preset when import
	const IMPORT_MODE_REPLACE = 0x02;	// Replace existing preset during import


	/**
	 * List all available filter presets for given user.
	 * 
	 * @param boolean $bTreeMode - false by default. If true, than
	 * children filter presets will be placed at parent presets in '#Children' field.
	 * 
	 * @return array where keys are filter ids, and values are arrays,
	 * that contain key 'FilterName' with filter name and key 'ChildrenFilters'
	 * with array of children filters.
	 * 
	 * @example of return value
	 * array (
	 *  -1 => array(
	 *      'Name' => 'My tasks',
	 *      'Condition' => '...',
	 *      'Parent' => NULL	// This preset doesn't have parent
	 * ),
	 * -2 => array(
	 *      'Name' => "I'm responsible",
	 *      'Condition' => '...',
	 *      'Parent' => -1		// 'My tasks' is parent for this filter
	 * ), 
	 * ...
	 * )
	 */
	public function listFilterPresets($bTreeMode = false);


	/**
	 * Selects some filter preset. It will be automatically saved for given 
	 * user (through CUserOptions).
	 * 
	 * @param integer $presetId. Preset must be exists, otherwise exception will be throwed.
	 */
	public function switchFilterPreset($presetId);


	/**
	 * Get id of preset, currently selected for user (choose are saved in CUserOptions)
	 * 
	 * @return integer presetd id
	 */
	public function getSelectedFilterPresetId();


	/**
	 * Get selected filter as array for CTasks::GetList()
	 * 
	 * @return array filter
	 */
	public function getSelectedFilterPresetCondition();


	/**
	 * Get selected filter name
	 * 
	 * @return array filter
	 */
	public function getSelectedFilterPresetName();


	/**
	 * Create new preset for user
	 */
	public function createPreset($arPresetData);


	/**
	 * Remove user preset
	 */
	public function removePreset($presetId);
}


/**
 * Tasks filters controller
 */
class CTaskFilterCtrl implements CTaskFilterCtrlInterface
{
	protected static $instanceOfSelf = array();

	protected $userId = false;
	protected $loggedInUserId = false;
	protected $selectedFilterPreset = self::STD_PRESET_ALIAS_TO_DEFAULT;
	protected $arPresets = array();
	protected $paramName = '';
	private   $bGroupMode = null;

	private static $arOperationsMap = array(
		self::OP_EQUAL            => '',
		self::OP_NOT_EQUAL        => '!',
		self::OP_SUBSTRING        => '%',
		self::OP_NOT_SUBSTRING    => '!%',
		self::OP_STRICTLY_LESS    => '<',
		self::OP_STRICTLY_GREATER => '>',
		self::OP_LESS_OR_EQUAL    => '<=',
		self::OP_GREATER_OR_EQUAL => '>=',
		self::OP_DATE_TODAY       => '#RC#',
		self::OP_DATE_YESTERDAY   => '#RC(-1)#',
		self::OP_DATE_TOMORROW    => '#RC1#',
		self::OP_DATE_CUR_WEEK    => '#R1C#',
		self::OP_DATE_PREV_WEEK   => '#R1C(-1)#',
		self::OP_DATE_NEXT_WEEK   => '#R1C1#',
		self::OP_DATE_CUR_MONTH   => '#R2C#',
		self::OP_DATE_PREV_MONTH  => '#R2C(-1)#',
		self::OP_DATE_NEXT_MONTH  => '#R2C1#',
		self::OP_DATE_LAST_DAYS   => '#RC(-x)#',
		self::OP_DATE_NEXT_DAYS   => '#RCx#'
	);

	/**
	 * prevent creating through "new"
	 *
	 * @param $userId
	 * @param bool $bGroupMode
	 */
	private function __construct($userId, $bGroupMode = false)
	{
		CTaskAssert::assertLaxIntegers($userId);
		CTaskAssert::assert($userId > 0);
		CTaskAssert::assert(is_bool($bGroupMode));

		$this->userId     = $userId;
		$this->bGroupMode = $bGroupMode;

		if (User::isAuthorized())
		{
			$this->loggedInUserId = User::getId();
			$this->paramName = self::filterParamPresetId . '_by_user_' . $this->loggedInUserId;
		}
		else
			$this->paramName = self::filterParamPresetId;

		$this->arPresets = $this->FetchFilterPresets();
	}


	// prevent clone of object
	public function __clone()
	{
		throw new Main\SystemException('clone is not allowed');
	}


	// prevent wakeup
	public function __wakeup()
	{
		throw new Main\SystemException('wakeup is not allowed');
	}


	/**
	 * Get instance of multiton filter controller
	 *
	 * @param integer $userId
	 * @param bool $bGroupMode
	 * @return CTaskFilterCtrl
	 */
	public static function getInstance($userId, $bGroupMode = false)
	{
		CTaskAssert::assertLaxIntegers($userId);
		CTaskAssert::assert($userId > 0);
		CTaskAssert::assert(is_bool($bGroupMode));

		$key = $userId . '|' . ($bGroupMode ? 'Y' : 'N');

		if ( ! array_key_exists($key, self::$instanceOfSelf) )
			self::$instanceOfSelf[$key] = new self($userId, $bGroupMode);

		return (self::$instanceOfSelf[$key]);
	}


	/**
	 * Fetch predefined presets and presets from DB.
	 * 
	 * @return array of fetched filter presets. Includes predefined presets.
	 *
	 * @var CDatabase $DB
	 */
	protected function fetchFilterPresets()
	{
		global $DB;

		$arActiveStatuses = array(
			CTasks::METASTATE_VIRGIN_NEW,
			CTasks::METASTATE_EXPIRED,
			CTasks::STATE_NEW,
			CTasks::STATE_PENDING,
			CTasks::STATE_IN_PROGRESS
		);

		if ( ! $this->bGroupMode )
		{
			// Init list with predefined presets
			$arPresets = array(
				self::ROOT_PRESET => array(
					'Name'      => '/',
					'Parent'    => null,	// This preset doesn't have parent
					'Condition' => null		// This preset doesn't have condition
				),
				self::STD_PRESET_ACTIVE_MY_TASKS => array(
					'Name'      => GetMessage('TASKS_FILTER_PRESET_STD_PRESET_ACTIVE_MY_TASKS'),
					'Parent'    => self::ROOT_PRESET,
					'Condition' => serialize(
						array(
							'::LOGIC' => 'AND',
							'MEMBER'  => $this->userId,
							'::SUBFILTER-1' => array(
								'::LOGIC' => 'OR',
								'STATUS'  => $arActiveStatuses,
								'::SUBFILTER-1' => array(
									'::LOGIC'    => 'AND',
									'CREATED_BY' => $this->userId,
									'STATUS'     => array(CTasks::STATE_DECLINED, CTasks::STATE_SUPPOSEDLY_COMPLETED)
								)
							)
						)
					),
					'SQL: select count' =>
						"SELECT COUNT(TT.ID) AS CNT
						FROM (
							SELECT T.ID
							FROM b_tasks T
							INNER JOIN b_tasks_member TM on TM.TASK_ID = T.ID
							WHERE T.CREATED_BY = $this->userId
								AND T.RESPONSIBLE_ID != $this->userId
								AND TM.USER_ID = $this->userId
								AND T.STATUS != 4
								AND T.STATUS != 5
								AND T.ZOMBIE = 'N'

							UNION 

							SELECT T.ID
							FROM b_tasks T
							INNER JOIN b_tasks_member TM on TM.TASK_ID = T.ID
							WHERE T.RESPONSIBLE_ID = $this->userId
								AND TM.USER_ID = $this->userId
								AND T.STATUS != 4
								AND T.STATUS != 5
								AND T.ZOMBIE = 'N'

							UNION 

							SELECT T.ID
							FROM b_tasks T
							WHERE T.CREATED_BY = $this->userId
								AND (T.STATUS = 4 OR T.STATUS = 5)
								AND T.ZOMBIE = 'N'
						) AS TT
					"
				),
				self::STD_PRESET_ACTIVE_I_AM_DOER => array(
					'Name'      => GetMessage('TASKS_FILTER_PRESET_STD_PRESET_ACTIVE_I_AM_DOER'),
					'Parent'    => self::STD_PRESET_ACTIVE_MY_TASKS,	// 'My tasks' is the parent of this filter preset
					'Condition' => serialize(
						array(
							'::LOGIC' => 'AND',
							'DOER'    => $this->userId,
							'::SUBFILTER-1' => array(
								'::LOGIC' => 'OR',
								'STATUS'  => $arActiveStatuses,
								'::SUBFILTER-1' => array(
									'::LOGIC'    => 'AND',
									'CREATED_BY' => $this->userId,
									'STATUS'     => CTasks::STATE_DECLINED
								)
							)
						)
					)
				),
				self::STD_PRESET_ACTIVE_I_AM_RESPONSIBLE => array(
					'Name'      => GetMessage('TASKS_FILTER_PRESET_STD_PRESET_ACTIVE_I_AM_RESPONSIBLE'),
					'Parent'    => self::STD_PRESET_ACTIVE_I_AM_DOER,	// 'I_AM_DOER' is the parent of this filter preset
					'Condition' => serialize(
						array(
							'::LOGIC'        => 'AND',
							'RESPONSIBLE_ID' => $this->userId,
							'::SUBFILTER-1' => array(
								'::LOGIC' => 'OR',
								'STATUS'  => $arActiveStatuses,
								'::SUBFILTER-1' => array(
									'::LOGIC'    => 'AND',
									'CREATED_BY' => $this->userId,
									'STATUS'     => CTasks::STATE_DECLINED
								)
							)
						)
					)
				),
				self::STD_PRESET_ACTIVE_I_AM_ACCOMPLICE => array(
					'Name'      => GetMessage('TASKS_FILTER_PRESET_STD_PRESET_ACTIVE_I_AM_ACCOMPLICE'),
					'Parent'    => self::STD_PRESET_ACTIVE_I_AM_DOER,	// 'I_AM_DOER' is the parent of this filter preset
					'Condition' => serialize(
						array(
							'::LOGIC'    => 'AND',
							'ACCOMPLICE' => $this->userId,
							'::SUBFILTER-1' => array(
								'::LOGIC' => 'OR',
								'STATUS'  => $arActiveStatuses,
								'::SUBFILTER-1' => array(
									'::LOGIC'    => 'AND',
									'CREATED_BY' => $this->userId,
									'STATUS'     => CTasks::STATE_DECLINED
								)
							)
						)
					)
				),
				self::STD_PRESET_ACTIVE_I_AM_ORIGINATOR => array(
					'Name'      => GetMessage('TASKS_FILTER_PRESET_STD_PRESET_ACTIVE_I_AM_ORIGINATOR'),
					'Parent'    => self::STD_PRESET_ACTIVE_MY_TASKS,	// 'My tasks' is the parent of this filter preset
					'Condition' => serialize(
						array(
							'::LOGIC'    => 'AND',
							'CREATED_BY' => $this->userId,
							'::SUBFILTER-1' => array(
								'::LOGIC' => 'OR',
								'STATUS'  => $arActiveStatuses,
								'::SUBFILTER-1' => array(
									'::LOGIC'    => 'AND',
									'CREATED_BY' => $this->userId,
									'STATUS'     => array(CTasks::STATE_DECLINED, CTasks::STATE_SUPPOSEDLY_COMPLETED)
								)
							)
						)
					)
				),
				self::STD_PRESET_ACTIVE_I_AM_AUDITOR => array(
					'Name'      => GetMessage('TASKS_FILTER_PRESET_STD_PRESET_ACTIVE_I_AM_AUDITOR'),
					'Parent'    => self::STD_PRESET_ACTIVE_MY_TASKS,	// 'My tasks' is the parent of this filter preset
					'Condition' => serialize(
						array(
							'::LOGIC' => 'AND',
							'AUDITOR' => $this->userId,
							'::SUBFILTER-1' => array(
								'::LOGIC' => 'OR',
								'STATUS'  => $arActiveStatuses,
								'::SUBFILTER-1' => array(
									'::LOGIC'    => 'AND',
									'CREATED_BY' => $this->userId,
									'STATUS'     => CTasks::STATE_DECLINED
								)
							)
						)
					)
				),
				self::STD_PRESET_EXPIRED => array(
					'Name'      => GetMessage('TASKS_FILTER_PRESET_STD_PRESET_EXPIRED'),
					'Parent'    => self::ROOT_PRESET,
					'Condition' => serialize(
						array(
							'::LOGIC' => 'AND',
							'MEMBER'  => $this->userId,
							'STATUS'  => CTasks::METASTATE_EXPIRED
						)
					)
				),
				self::STD_PRESET_EXPIRED_SOON => array(
					'Name'      => GetMessage('TASKS_FILTER_PRESET_STD_PRESET_EXPIRED_SOON'),
					'Parent'    => self::ROOT_PRESET,
					'Condition' => serialize(
						array(
							'::LOGIC' => 'AND',
							'MEMBER'  => $this->userId,
							'STATUS'  => CTasks::METASTATE_EXPIRED_SOON
						)
					)
				),
				self::STD_PRESET_DEFERRED_MY_TASKS => array(
					'Name'      => GetMessage('TASKS_FILTER_PRESET_STD_PRESET_DEFERRED_MY_TASKS'),
					'Parent'    => self::ROOT_PRESET,
					'Condition' => serialize(
						array(
							'::LOGIC' => 'AND',
							'MEMBER'  => $this->userId,
							'STATUS'  => CTasks::STATE_DEFERRED
						)
					)
				),
				self::STD_PRESET_COMPLETED_MY_TASKS => array(
					'Name'      => GetMessage('TASKS_FILTER_PRESET_STD_PRESET_COMPLETED_MY_TASKS'),
					'Parent'    => self::ROOT_PRESET,
					'Condition' => serialize(
						array(
							'::LOGIC' => 'AND',
							'MEMBER'  => $this->userId,
							'STATUS'  => array(
								CTasks::STATE_SUPPOSEDLY_COMPLETED, 
								CTasks::STATE_COMPLETED
							)
						)
					)
				),
				self::STD_PRESET_ALL_MY_TASKS => array(
					'Name'      => GetMessage('TASKS_FILTER_PRESET_STD_PRESET_ALL_MY_TASKS'),
					'Parent'    => self::ROOT_PRESET,
					'Condition' => serialize(
						array(
							'::LOGIC' => 'AND',
							'MEMBER'  => $this->userId
						)
					)
				)
			);
		}
		else
		{
			// Init list with predefined presets
			$arPresets = array(
				self::ROOT_PRESET => array(
					'Name'      => '/',
					'Parent'    => null,	// This preset doesn't have parent
					'Condition' => null		// This preset doesn't have condition
				),
				self::STD_PRESET_ACTIVE_MY_TASKS => array(
					'Name'      => GetMessage('TASKS_FILTER_PRESET_STD_PRESET_ACTIVE_GROUP_TASKS'),
					'Parent'    => self::ROOT_PRESET,
					'Condition' => serialize(
						array(
							'::LOGIC' => 'AND',
							'::SUBFILTER-1' => array(
								'::LOGIC' => 'OR',
								'STATUS'  => $arActiveStatuses,
								'::SUBFILTER-1' => array(
									'::LOGIC'    => 'AND',
									'CREATED_BY' => $this->userId,
									'STATUS'     => CTasks::STATE_DECLINED
								)
							)
						)
					)
				),

				self::STD_PRESET_EXPIRED => array(
					'Name'      => GetMessage('TASKS_FILTER_PRESET_STD_PRESET_EXPIRED'),
					'Parent'    => self::ROOT_PRESET,
					'Condition' => serialize(
						array(
							'::LOGIC' => 'AND',
							'MEMBER'  => $this->userId,
							'STATUS'  => CTasks::METASTATE_EXPIRED
						)
					)
				),
				self::STD_PRESET_EXPIRED_SOON => array(
					'Name'      => GetMessage('TASKS_FILTER_PRESET_STD_PRESET_EXPIRED_SOON'),
					'Parent'    => self::ROOT_PRESET,
					'Condition' => serialize(
						array(
							'::LOGIC' => 'AND',
							'MEMBER'  => $this->userId,
							'STATUS'  => CTasks::METASTATE_EXPIRED_SOON
						)
					)
				),

				self::STD_PRESET_DEFERRED_MY_TASKS => array(
					'Name'      => GetMessage('TASKS_FILTER_PRESET_STD_PRESET_DEFERRED_MY_TASKS'),
					'Parent'    => self::ROOT_PRESET,
					'Condition' => serialize(
						array(
							'::LOGIC' => 'AND',
							'STATUS'  => CTasks::STATE_DEFERRED
						)
					)
				),
				self::STD_PRESET_COMPLETED_MY_TASKS => array(
					'Name'      => GetMessage('TASKS_FILTER_PRESET_STD_PRESET_COMPLETED_MY_TASKS'),
					'Parent'    => self::ROOT_PRESET,
					'Condition' => serialize(
						array(
							'::LOGIC' => 'AND',
							'STATUS'  => array(
								CTasks::STATE_SUPPOSEDLY_COMPLETED, 
								CTasks::STATE_COMPLETED
							)
						)
					)
				),
				self::STD_PRESET_ALL_MY_TASKS => array(
					'Name'      => GetMessage('TASKS_FILTER_PRESET_STD_PRESET_ALL_MY_TASKS'),
					'Parent'    => self::ROOT_PRESET,
					'Condition' => serialize(
						array(
							'::LOGIC' => 'AND'
						)
					)
				)
			);
		}

		$arPresets[self::SPEC_PRESET_FAVORITE_TASKS] = array(
			'Name'      => GetMessage('TASKS_FILTER_PRESET_SPEC_PRESET_FAVORITE_TASKS'),
			
			// commented out to prevent from figuring in tree list
			//'Parent'    => self::ROOT_PRESET,

			'Code' => 'FAVORITE',
			'Condition' => serialize(
				array_merge(
					array(
						'::LOGIC' => 'AND',
						'=FAVORITE' => 'Y',
					),
					(
						! $this->bGroupMode
						?
						array('MEMBER'  => $this->userId)
						:
						array()
					)
				)
			)
		);

		$arPresetsFromDb = array();
		$bNeedFetchFromDatabase = true;
		$cacheDir = $obCache = null;
		if (defined('BX_COMP_MANAGED_CACHE'))
		{
			$obCache  =  new CPHPCache();
			$lifeTime =  CTasksTools::CACHE_TTL_UNLIM;
			$cacheDir = '/tasks/filter_presets/' . ($this->loggedInUserId % 300);
			$cacheId  = 'tasks_filters_presets_' . $this->loggedInUserId;

			if ($obCache->InitCache($lifeTime, $cacheId, $cacheDir))
			{
				$arPresetsFromDb = $obCache->GetVars();
				$bNeedFetchFromDatabase = false;
			}
		}

		if ($bNeedFetchFromDatabase)
		{
			$dbRes = $DB->query(
				"SELECT ID, NAME, PARENT, SERIALIZED_FILTER
				FROM b_tasks_filters
				WHERE USER_ID = " . (int) $this->loggedInUserId . "
				ORDER BY NAME, ID"
			);

			if ($dbRes)
			{
				while ($arData = $dbRes->fetch())
				{
					$arPresetsFromDb[(int)$arData['ID']] = array(
						'Name'      => $arData['NAME'],
						'Parent'    => (int) $arData['PARENT'],
						'Condition' => $arData['SERIALIZED_FILTER']
					);
				}
			}
			else
				CTaskAssert::log('DB error', CTaskAssert::ELL_ERROR, true);

			if (defined('BX_COMP_MANAGED_CACHE') && $obCache->StartDataCache())
			{
				global $CACHE_MANAGER;
				$CACHE_MANAGER->StartTagCache($cacheDir);
				$CACHE_MANAGER->RegisterTag('tasks_filters_presets_' . $this->loggedInUserId);
				$CACHE_MANAGER->EndTagCache();
				$obCache->EndDataCache($arPresetsFromDb);
			}
		}

		// Merge with predefined presets list
		foreach ($arPresetsFromDb as $presetId => $presetData)
			$arPresets[$presetId] = $presetData;

		return ($arPresets);
	}

	public function listFilterPresets($bTreeMode = false)
	{
		if ( ! $bTreeMode )
			return ($this->arPresets);
		else
			return (self::ConvertPresetsListToTree($this->arPresets));
	}

	public function listFilterSpecialPresets()
	{
		return array(
			self::SPEC_PRESET_FAVORITE_TASKS => $this->arPresets[self::SPEC_PRESET_FAVORITE_TASKS]
		);
	}

	protected function convertPresetsListToTree($arPresets)
	{
		// Remove top root element from presets
		if (array_key_exists(self::ROOT_PRESET, $arPresets))
			unset ($arPresets[self::ROOT_PRESET]);

		$curRoot = self::ROOT_PRESET;
		$arPresetsTree = $this->ConvertPresetsListToTreeHelper($arPresets, $curRoot);

		return ($arPresetsTree);
	}


	private function convertPresetsListToTreeHelper($arPresets, $curRoot)
	{
		$rc = array();

		foreach ($arPresets as $presetId => $arPresetData)
		{
			// current level items
			if ($arPresetData['Parent'] === $curRoot)
			{
				$rc[$presetId] = $arPresetData;
				unset ($arPresets[$presetId]);

				// collect children
				$arSubItems = $this->ConvertPresetsListToTreeHelper($arPresets, $presetId);
				if (count($arSubItems))
				{
					$rc[$presetId]['#Children'] = $arSubItems;

					foreach ($arSubItems as $subItemPresetId => $v)
						unset ($arPresets[$subItemPresetId]);
				}

				continue;
			}
		}

		return ($rc);
	}


	public function getSelectedFilterPresetId()
	{
		$rc = (int) CUserOptions::GetOption(
			self::filterCategoryName, 
			$this->paramName, 
			(string) self::STD_PRESET_ALIAS_TO_DEFAULT,	// by default
			$this->loggedInUserId
		);

		// ensure, that selected filter exists
		if ( ! in_array($rc, array_keys($this->arPresets), true) )
			$rc = self::STD_PRESET_ALIAS_TO_DEFAULT;

		return ($rc);
	}


	public function switchFilterPreset($presetId)
	{
		if ( ! CTaskAssert::isLaxIntegers($presetId) )
			throw new Main\ArgumentException();

		// ensure, that selected filter exists
		if ( ! in_array($presetId, array_keys($this->arPresets), true) )
			throw new Main\ArgumentException();

		$curPresetId = $this->getSelectedFilterPresetId();

		if ($presetId != $curPresetId)
		{
			CUserOptions::SetOption(
				self::filterCategoryName, 
				$this->paramName, 
				(string) $presetId,
				$bCommon = false,
				$this->loggedInUserId
			);
		}
	}


	public function getSelectedFilterPresetCondition()
	{
		$presetId = $this->GetSelectedFilterPresetId();

		return ($this->getFilterPresetConditionById($presetId));
	}

	public function checkExistsPresetById($presetId)
	{
		return array_key_exists($presetId, $this->arPresets);
	}

	public function getFilterPresetConditionById($presetId)
	{
		CTaskAssert::assertLaxIntegers ($presetId);
		$presetId = (int) $presetId;

		if ( !$this->checkExistsPresetById($presetId) )
			return (false);
		
		// Root preset is pseudo preset, it doesn't have a filter condition
		if ($presetId === self::ROOT_PRESET)
			return (false);

		return (array('::SUBFILTER-ROOT' => unserialize($this->arPresets[$presetId]['Condition'])));
	}


	public function getSelectedFilterPresetName()
	{
		$presetId = $this->GetSelectedFilterPresetId();

		return ($this->arPresets[$presetId]['Name']);
	}


	public function createPreset($arPresetData)
	{
		return ($this->addOrReplacePreset($arPresetData));
	}


	public function replacePreset($presetId, $arPresetData)
	{
		CTaskAssert::assert($presetId !== null);

		$rc = $this->addOrReplacePreset($arPresetData, $presetId);

		if ($rc === false)
			throw new TasksException('', TasksException::TE_UNKNOWN_ERROR);

		return ( (int) $presetId);
	}


	/**
	 * @param $arPresetData
	 * @param null $presetId
	 * @return mixed
	 * @throws Exception
	 *
	 * @var CDatabase $DB
	 * @var CCacheManager $CACHE_MANAGER
	 */
	private function addOrReplacePreset($arPresetData, $presetId = null)
	{
		global $DB, $CACHE_MANAGER;

		if ( ! (
			isset($arPresetData['Name'])
			&& (mb_strlen($arPresetData['Name']) <= 100)
			&& isset($arPresetData['Parent'])
			&& isset($arPresetData['Condition'])
			&& is_array($arPresetData['Condition'])
			&& ($arPresetData['Parent'] === self::ROOT_PRESET)
		) )
		{
			throw new Main\ArgumentException('Invalid preset data');
		}

		// Remove CHECK_PERMISSIONS keys from arFilter, because CTasks::GetList() eat it
		$arSafeCondition = self::removeArrayKeyRecursively($arPresetData['Condition'], 'CHECK_PERMISSIONS');

		$arFields = array(
			'NAME'              => $arPresetData['Name'],
			'PARENT'            => (int) $arPresetData['Parent'],
			'SERIALIZED_FILTER' => serialize($arSafeCondition)
		);

		$arBinds = array('SERIALIZED_FILTER');

		// Replace existing preset?
		if ($presetId !== null)
		{
			CTaskAssert::assertLaxIntegers($presetId);
			CTaskAssert::assert($presetId > 0);

			$strUpdate = $DB->PrepareUpdate('b_tasks_filters', $arFields, 'tasks');
			$strSql = "UPDATE b_tasks_filters SET " . $strUpdate 
				. " WHERE ID=" . (int) $presetId . " AND USER_ID=" . (int) $this->userId;

			$DB->QueryBind($strSql, $arBinds, true);
			$rc = $presetId;
		}
		else
		{
			$arFields['USER_ID'] = (int) $this->userId;
			$rc = $DB->Add('b_tasks_filters', $arFields, $arBinds, 'tasks');
		}

		$this->reloadPresetsCache();
		$CACHE_MANAGER->ClearByTag('tasks_filters_presets_' . $this->userId);

		return ($rc);
	}


	/**
	 * @param int $presetId
	 *
	 * @var CDatabase $DB
	 */
	public function removePreset($presetId)
	{
		global $DB, $CACHE_MANAGER;

		CTaskAssert::assertLaxIntegers($presetId);
		CTaskAssert::assert($presetId > 0);

		// Switch to default preset, if preset to be removed is selected now
		if ($this->getSelectedFilterPresetId() == $presetId)
			$this->switchFilterPreset(self::STD_PRESET_ALIAS_TO_DEFAULT);

		$DB->query(
			"DELETE FROM b_tasks_filters
			WHERE ID = " . (int) $presetId 
				. " AND USER_ID = " . (int) $this->userId
		);

		$this->reloadPresetsCache();
		$CACHE_MANAGER->ClearByTag('tasks_filters_presets_' . $this->userId);
	}


	private function reloadPresetsCache()
	{
		$this->arPresets = $this->FetchFilterPresets();
	}


	public static function getManifest()
	{
		$arManifest = array(
			'Manifest version' => '1',
			'Fields' => array(
				'TITLE' => array(
					'Supported operations' => array(
						CTaskFilterCtrl::OP_EQUAL,
						CTaskFilterCtrl::OP_NOT_EQUAL,
						CTaskFilterCtrl::OP_SUBSTRING,
						CTaskFilterCtrl::OP_NOT_SUBSTRING
					),
					'Type' => CTaskFilterCtrl::TYPE_TEXT
				),
				'GROUP_ID' => array(
					'Supported operations' => array(
						CTaskFilterCtrl::OP_EQUAL,
						CTaskFilterCtrl::OP_NOT_EQUAL
					),
					'Type' => CTaskFilterCtrl::TYPE_GROUP_ID
				),
				'CREATED_BY' => array(
					'Supported operations' => array(
						CTaskFilterCtrl::OP_EQUAL,
						CTaskFilterCtrl::OP_NOT_EQUAL
					),
					'Type' => CTaskFilterCtrl::TYPE_USER_ID
				),
				'RESPONSIBLE_ID' => array(
					'Supported operations' => array(
						CTaskFilterCtrl::OP_EQUAL,
						CTaskFilterCtrl::OP_NOT_EQUAL
					),
					'Type' => CTaskFilterCtrl::TYPE_USER_ID
				),
				'ACCOMPLICE' => array(
					'Supported operations' => array(
						CTaskFilterCtrl::OP_EQUAL,
						CTaskFilterCtrl::OP_NOT_EQUAL
					),
					'Type' => CTaskFilterCtrl::TYPE_USER_ID
				),
				'AUDITOR' => array(
					'Supported operations' => array(
						CTaskFilterCtrl::OP_EQUAL,
						CTaskFilterCtrl::OP_NOT_EQUAL
					),
					'Type' => CTaskFilterCtrl::TYPE_USER_ID
				),
				'STATUS' => array(
					'Supported operations' => array(
						CTaskFilterCtrl::OP_EQUAL,
						CTaskFilterCtrl::OP_NOT_EQUAL
					),
					'Type' => CTaskFilterCtrl::TYPE_STATUS
				),
				'PRIORITY' => array(
					'Supported operations' => array(
						CTaskFilterCtrl::OP_EQUAL,
						CTaskFilterCtrl::OP_NOT_EQUAL
					),
					'Type' => CTaskFilterCtrl::TYPE_PRIORITY
				),
				'META:DEADLINE_TS' => array(
					'Supported operations' => array(
						CTaskFilterCtrl::OP_DATE_YESTERDAY,
						CTaskFilterCtrl::OP_DATE_TODAY,
						CTaskFilterCtrl::OP_DATE_TOMORROW,
						CTaskFilterCtrl::OP_DATE_PREV_WEEK,
						CTaskFilterCtrl::OP_DATE_CUR_WEEK,
						CTaskFilterCtrl::OP_DATE_NEXT_WEEK,
						CTaskFilterCtrl::OP_DATE_PREV_MONTH,
						CTaskFilterCtrl::OP_DATE_CUR_MONTH,
						CTaskFilterCtrl::OP_DATE_NEXT_MONTH,
						CTaskFilterCtrl::OP_DATE_LAST_DAYS,
						CTaskFilterCtrl::OP_DATE_NEXT_DAYS,
						CTaskFilterCtrl::OP_EQUAL,
						CTaskFilterCtrl::OP_STRICTLY_LESS,
						CTaskFilterCtrl::OP_STRICTLY_GREATER
					),
					'Type' => CTaskFilterCtrl::TYPE_DATE
				),
				'META:DATE_START_TS' => array(
					'Supported operations' => array(
						CTaskFilterCtrl::OP_DATE_YESTERDAY,
						CTaskFilterCtrl::OP_DATE_TODAY,
						CTaskFilterCtrl::OP_DATE_TOMORROW,
						CTaskFilterCtrl::OP_DATE_PREV_WEEK,
						CTaskFilterCtrl::OP_DATE_CUR_WEEK,
						CTaskFilterCtrl::OP_DATE_NEXT_WEEK,
						CTaskFilterCtrl::OP_DATE_PREV_MONTH,
						CTaskFilterCtrl::OP_DATE_CUR_MONTH,
						CTaskFilterCtrl::OP_DATE_NEXT_MONTH,
						CTaskFilterCtrl::OP_DATE_LAST_DAYS,
						CTaskFilterCtrl::OP_DATE_NEXT_DAYS,
						CTaskFilterCtrl::OP_EQUAL,
						CTaskFilterCtrl::OP_STRICTLY_LESS,
						CTaskFilterCtrl::OP_STRICTLY_GREATER
					),
					'Type' => CTaskFilterCtrl::TYPE_DATE
				),
				'META:START_DATE_PLAN_TS' => array(
					'Supported operations' => array(
						CTaskFilterCtrl::OP_DATE_YESTERDAY,
						CTaskFilterCtrl::OP_DATE_TODAY,
						CTaskFilterCtrl::OP_DATE_TOMORROW,
						CTaskFilterCtrl::OP_DATE_PREV_WEEK,
						CTaskFilterCtrl::OP_DATE_CUR_WEEK,
						CTaskFilterCtrl::OP_DATE_NEXT_WEEK,
						CTaskFilterCtrl::OP_DATE_PREV_MONTH,
						CTaskFilterCtrl::OP_DATE_CUR_MONTH,
						CTaskFilterCtrl::OP_DATE_NEXT_MONTH,
						CTaskFilterCtrl::OP_DATE_LAST_DAYS,
						CTaskFilterCtrl::OP_DATE_NEXT_DAYS,
						CTaskFilterCtrl::OP_EQUAL,
						CTaskFilterCtrl::OP_STRICTLY_LESS,
						CTaskFilterCtrl::OP_STRICTLY_GREATER
					),
					'Type' => CTaskFilterCtrl::TYPE_DATE
				),
				'META:END_DATE_PLAN_TS' => array(
					'Supported operations' => array(
						CTaskFilterCtrl::OP_DATE_YESTERDAY,
						CTaskFilterCtrl::OP_DATE_TODAY,
						CTaskFilterCtrl::OP_DATE_TOMORROW,
						CTaskFilterCtrl::OP_DATE_PREV_WEEK,
						CTaskFilterCtrl::OP_DATE_CUR_WEEK,
						CTaskFilterCtrl::OP_DATE_NEXT_WEEK,
						CTaskFilterCtrl::OP_DATE_PREV_MONTH,
						CTaskFilterCtrl::OP_DATE_CUR_MONTH,
						CTaskFilterCtrl::OP_DATE_NEXT_MONTH,
						CTaskFilterCtrl::OP_DATE_LAST_DAYS,
						CTaskFilterCtrl::OP_DATE_NEXT_DAYS,
						CTaskFilterCtrl::OP_EQUAL,
						CTaskFilterCtrl::OP_STRICTLY_LESS,
						CTaskFilterCtrl::OP_STRICTLY_GREATER
					),
					'Type' => CTaskFilterCtrl::TYPE_DATE
				),
				'META:CREATED_DATE_TS' => array(
					'Supported operations' => array(
						CTaskFilterCtrl::OP_DATE_YESTERDAY,
						CTaskFilterCtrl::OP_DATE_TODAY,
						CTaskFilterCtrl::OP_DATE_TOMORROW,
						CTaskFilterCtrl::OP_DATE_PREV_WEEK,
						CTaskFilterCtrl::OP_DATE_CUR_WEEK,
						CTaskFilterCtrl::OP_DATE_NEXT_WEEK,
						CTaskFilterCtrl::OP_DATE_PREV_MONTH,
						CTaskFilterCtrl::OP_DATE_CUR_MONTH,
						CTaskFilterCtrl::OP_DATE_NEXT_MONTH,
						CTaskFilterCtrl::OP_DATE_LAST_DAYS,
						CTaskFilterCtrl::OP_DATE_NEXT_DAYS,
						CTaskFilterCtrl::OP_EQUAL,
						CTaskFilterCtrl::OP_STRICTLY_LESS,
						CTaskFilterCtrl::OP_STRICTLY_GREATER
					),
					'Type' => CTaskFilterCtrl::TYPE_DATE
				),
				'META:CLOSED_DATE_TS' => array(
					'Supported operations' => array(
						CTaskFilterCtrl::OP_DATE_YESTERDAY,
						CTaskFilterCtrl::OP_DATE_TODAY,
						CTaskFilterCtrl::OP_DATE_TOMORROW,
						CTaskFilterCtrl::OP_DATE_PREV_WEEK,
						CTaskFilterCtrl::OP_DATE_CUR_WEEK,
						CTaskFilterCtrl::OP_DATE_NEXT_WEEK,
						CTaskFilterCtrl::OP_DATE_PREV_MONTH,
						CTaskFilterCtrl::OP_DATE_CUR_MONTH,
						CTaskFilterCtrl::OP_DATE_NEXT_MONTH,
						CTaskFilterCtrl::OP_DATE_LAST_DAYS,
						CTaskFilterCtrl::OP_DATE_NEXT_DAYS,
						CTaskFilterCtrl::OP_EQUAL,
						CTaskFilterCtrl::OP_STRICTLY_LESS,
						CTaskFilterCtrl::OP_STRICTLY_GREATER
					),
					'Type' => CTaskFilterCtrl::TYPE_DATE
				)
			),
			'Operations map' => self::$arOperationsMap
		);

		return ($arManifest);
	}


	public function importFilterDataFromJs($arPresetData,
		$mode = self::IMPORT_MODE_CREATE, $presetId = null
	)
	{
		CTaskAssert::assert(
			in_array($mode, array(self::IMPORT_MODE_CREATE, self::IMPORT_MODE_REPLACE), true)
			&& is_array($arPresetData)
			&& (count($arPresetData) === 3)
			&& isset($arPresetData['Name'], $arPresetData['Parent'], $arPresetData['Condition'])
			&& ($arPresetData['Parent'] === self::ROOT_PRESET)
			&& (mb_strlen($arPresetData['Name']))
			&& is_array($arPresetData['Condition'])
		);

		$arPresetData['Condition'] = self::convertItemForImport($arPresetData['Condition']);

		if ($mode === self::IMPORT_MODE_CREATE)
			$newPresetId = $this->createPreset($arPresetData);
		else
		{
			CTaskAssert::assertLaxIntegers($presetId);
			CTaskAssert::assert($presetId > 0);

			$newPresetId = $this->replacePreset($presetId, $arPresetData);
		}

		return ($newPresetId);
	}


	public function exportFilterDataForJs($presetId)
	{
		if ( ! isset($this->arPresets[$presetId]) )
			return (false);

		$arPresetData = $this->arPresets[$presetId];
		$arPresetData['Condition'] = unserialize($this->arPresets[$presetId]['Condition']);
		$arPresetData['Condition'] = self::convertItemForExport($arPresetData['Condition']);

		return ($arPresetData);
	}


	private static function convertItemForImport($arItem)
	{
		static $arManifest = false;
		static $arAllowedFields = false;
		$arResult = array();

		if ($arManifest === false)
		{
			$arManifest      = self::getManifest();
			$arAllowedFields = array_keys($arManifest['Fields']);
		}

		foreach ($arItem as $itemName => $itemData)
		{
			CTaskAssert::assert(
				(mb_strlen($itemName) > 2)
				|| CTaskAssert::isLaxIntegers($itemName)
			);

			if ($itemName === '::LOGIC')
			{
				$arResult[$itemName] = $itemData;
				continue;
			}

			if (mb_substr($itemName, 0, 12) === '::SUBFILTER-')
			{
				$arResult[$itemName] = self::convertItemForImport($itemData);
				continue;
			}

			CTaskAssert::assert(
				isset($itemData['operation'], $itemData['field'])
				&& array_key_exists('value', $itemData)
				&& CTaskAssert::isLaxIntegers($itemData['operation'])
				&& is_string($itemData['field'])
				&& ($itemData['field'] !== '')
			);

			if ( ! in_array($itemData['field'], $arAllowedFields, true) )
				throw new TasksException('', TasksException::TE_FILTER_MANIFEST_MISMATCH);

			$arSupportedOperations = $arManifest['Fields'][$itemData['field']]['Supported operations'];
			$itemType = $arManifest['Fields'][$itemData['field']]['Type'];
			$operation = (int) $itemData['operation'];

			if ( ! in_array($operation, $arSupportedOperations, true) )
				throw new TasksException('', TasksException::TE_FILTER_MANIFEST_MISMATCH);

			// Resolve operation prefix
			CTaskAssert::assert(isset(self::$arOperationsMap[$operation]));
			$operationPrefix = self::$arOperationsMap[$operation];

			$value = false;

			switch ($itemType)
			{
				case self::TYPE_DATE:
					if ( ! (
						is_string($itemData['value'])
						|| CTaskAssert::isLaxIntegers($itemData['value'])
					))
					{
						throw new TasksException('', TasksException::TE_FILTER_MANIFEST_MISMATCH);
					}

					$value = self::convertDateStringToTimestamp($operation, $itemData['value']);
				break;

				case self::TYPE_TEXT:
					if ( ! is_string($itemData['value']) )
						throw new TasksException('', TasksException::TE_FILTER_MANIFEST_MISMATCH);

					$value = (string) $itemData['value'];
				break;

				case self::TYPE_PRIORITY:
					if ( ! (
						is_string($itemData['value'])
						|| CTaskAssert::isLaxIntegers($itemData['value'])
					))
					{
						throw new TasksException('', TasksException::TE_FILTER_MANIFEST_MISMATCH);
					}

					$value = (int) $itemData['value'];

					if ( ! in_array(
						$value,
						array(CTasks::PRIORITY_LOW, CTasks::PRIORITY_AVERAGE, CTasks::PRIORITY_HIGH),
						true
					))
					{
						throw new TasksException('', TasksException::TE_FILTER_MANIFEST_MISMATCH);
					}
				break;

				case self::TYPE_STATUS:
					if (is_array($itemData['value']))
					{
						foreach ($itemData['value'] as $statusId)
						{
							if ( ! CTaskAssert::isLaxIntegers($statusId) )
								throw new TasksException('', TasksException::TE_FILTER_MANIFEST_MISMATCH);
						}

						$value = array_map('intval', $itemData['value']);
					}
					else
					{
						$statusId = $itemData['value'];
						if ( ! CTaskAssert::isLaxIntegers($statusId) )
							throw new TasksException('', TasksException::TE_FILTER_MANIFEST_MISMATCH);

						$value = (int) $statusId;
					}
				break;

				case self::TYPE_GROUP_ID:
				case self::TYPE_USER_ID:
					$entityId = $itemData['value'];
					if ( ! CTaskAssert::isLaxIntegers($entityId) )
						throw new TasksException('', TasksException::TE_FILTER_MANIFEST_MISMATCH);

					$value = (int) $entityId;
				break;

				default:
					CTaskAssert::assert(false);
				break;
			}

			$arKey = $operationPrefix . $itemData['field'];

			while (isset($arResult[$arKey]))
				$arKey = ' ' . $arKey;

			$arResult[$arKey] = $value;
		}

		return ($arResult);

		/*
			data in:
			array(
				'::LOGIC' => 'AND',
				array(
					'field'     => 'RESPONSIBLE_ID',
					'operation' => CTaskFilterCtrlInterface::OP_EQUAL,
					'value'     => 1
				),
				'::SUBFILTER-1' => array(
					'::LOGIC' => 'OR',
					array(
						'field'     => 'CREATED_BY',
						'operation' => CTaskFilterCtrlInterface::OP_EQUAL,
						'value'     => 1
					),
					array(
						'field'     => 'CREATED_BY',
						'operation' => CTaskFilterCtrlInterface::OP_NOT_EQUAL,
						'value'     => 2
					)
				),
				'::SUBFILTER-2' => array(
					'::LOGIC' => 'AND',
					array(
						'field'     => 'TITLE',
						'operation' => CTaskFilterCtrlInterface::OP_SUBSTRING,
						'value'     => 1
					),
					array(
						'field'     => 'TITLE',
						'operation' => CTaskFilterCtrlInterface::OP_NOT_SUBSTRING,
						'value'     => 2
					)
				)
			)

			data out:
			$arFilter = array(
				'::LOGIC' => 'AND',
				'RESPONSIBLE_ID' => 1,
				'::SUBFILTER-1' => array(
					'::LOGIC' => 'OR',
					'CREATED_BY' => 1,
					'!=CREATED_BY' => 2
				),
				'::SUBFILTER-2' => array(
					'::LOGIC' => 'AND',
					'%TITLE' => 'some interesting substring',
					'!%TITLE' => 'some not interesting substring'
				)
			);
		*/
	}


	private static function convertItemForExport($arItem)
	{
		static $arFields = null;
		$arResult = array();

		if ($arFields === null)
		{
			$arManifest = self::getManifest();
			$arFields   = $arManifest['Fields'];
		}

		foreach ($arItem as $itemName => $itemData)
		{
			CTaskAssert::assert(mb_strlen($itemName) > 2);

			if ($itemName === '::LOGIC')
			{
				$arResult[$itemName] = $itemData;
				continue;
			}

			if (mb_substr($itemName, 0, 12) === '::SUBFILTER-')
			{
				$arResult[$itemName] = self::convertItemForExport($itemData);
				continue;
			}

			$value = $itemData;

			$itemName = ltrim($itemName);

			// Resolve operation code and cutoff operation prefix from item name
			$operation = null;
			foreach (self::$arOperationsMap as $operationCode => $operationPrefix)
			{
				$pattern = '/^' . preg_quote($operationPrefix) . '[A-Za-z]/';
				if (preg_match($pattern, $itemName))
				{
					$operation = $operationCode;
					$itemName = mb_substr($itemName, mb_strlen($operationPrefix));
					break;
				}
			}

			CTaskAssert::assert($operation !== null);

			// Process values for date fields
			if (isset($arFields[$itemName]['Type']) && ($arFields[$itemName]['Type'] === self::TYPE_DATE))
				$value = self::convertTimestampToDateString($operation, $value);

			$arResult[] = array(
				'field'     => $itemName,
				'operation' => $operation,
				'value'     => $value
			);
		}

		return ($arResult);

		/*
			data in:
			$arFilter = array(
				'::LOGIC' => 'AND',
				'RESPONSIBLE_ID' => 1,
				'::SUBFILTER-1' => array(
					'::LOGIC' => 'OR',
					'CREATED_BY' => 1,
					'!=CREATED_BY' => 2
				),
				'::SUBFILTER-2' => array(
					'::LOGIC' => 'AND',
					'%TITLE' => 'some interesting substring',
					'!%TITLE' => 'some not interesting substring'
				)
			);

			data out:
			array(
				'::LOGIC' => 'AND',
				array(
					'field'     => 'RESPONSIBLE_ID',
					'operation' => CTaskFilterCtrlInterface::OP_EQUAL,
					'value'     => 1
				),
				'::SUBFILTER-1' => array(
					'::LOGIC' => 'OR',
					array(
						'field'     => 'CREATED_BY',
						'operation' => CTaskFilterCtrlInterface::OP_EQUAL,
						'value'     => 1
					),
					array(
						'field'     => 'CREATED_BY',
						'operation' => CTaskFilterCtrlInterface::OP_NOT_EQUAL,
						'value'     => 2
					)
				),
				'::SUBFILTER-2' => array(
					'::LOGIC' => 'AND',
					array(
						'field'     => 'TITLE',
						'operation' => CTaskFilterCtrlInterface::OP_SUBSTRING,
						'value'     => 1
					),
					array(
						'field'     => 'TITLE',
						'operation' => CTaskFilterCtrlInterface::OP_NOT_SUBSTRING,
						'value'     => 2
					)
				)
			)
		*/
	}


	private static function convertTimestampToDateString($operation, $value)
	{
		switch ($operation)
		{
			case self::OP_DATE_TODAY:
			case self::OP_DATE_YESTERDAY:
			case self::OP_DATE_TOMORROW:
			case self::OP_DATE_CUR_WEEK:
			case self::OP_DATE_PREV_WEEK:
			case self::OP_DATE_NEXT_WEEK:
			case self::OP_DATE_CUR_MONTH:
			case self::OP_DATE_PREV_MONTH:
			case self::OP_DATE_NEXT_MONTH:
				$value = '';
			break;

			case self::OP_DATE_LAST_DAYS:
			case self::OP_DATE_NEXT_DAYS:
				$value = (string) (int) $value;
			break;

			default:
			case self::OP_EQUAL:
			case self::OP_NOT_EQUAL:
			case self::OP_STRICTLY_LESS:
			case self::OP_STRICTLY_GREATER:
			case self::OP_LESS_OR_EQUAL:
			case self::OP_GREATER_OR_EQUAL:
				$bTzWasDisabled = ! CTimeZone::enabled();

				if ($bTzWasDisabled)
					CTimeZone::enable();

				$value = (string) ConvertTimeStamp($value + CTimeZone::getOffset());

				if ($bTzWasDisabled)
					CTimeZone::disable();
			break;
		}

		return ($value);
	}


	private static function convertDateStringToTimestamp($operation, $value)
	{
		switch ($operation)
		{
			case self::OP_DATE_TODAY:
			case self::OP_DATE_YESTERDAY:
			case self::OP_DATE_TOMORROW:
			case self::OP_DATE_CUR_WEEK:
			case self::OP_DATE_PREV_WEEK:
			case self::OP_DATE_NEXT_WEEK:
			case self::OP_DATE_CUR_MONTH:
			case self::OP_DATE_PREV_MONTH:
			case self::OP_DATE_NEXT_MONTH:
				$value = '';
			break;

			case self::OP_DATE_LAST_DAYS:
			case self::OP_DATE_NEXT_DAYS:
				$value = (int) $value;
			break;

			default:
			case self::OP_EQUAL:
			case self::OP_NOT_EQUAL:
			case self::OP_STRICTLY_LESS:
			case self::OP_STRICTLY_GREATER:
			case self::OP_LESS_OR_EQUAL:
			case self::OP_GREATER_OR_EQUAL:
				$bTzWasDisabled = ! CTimeZone::enabled();

				if ($bTzWasDisabled)
					CTimeZone::enable();

				// get correct UnixTimestamp
				$value = (int) MakeTimeStamp($value) - CTimeZone::getOffset();

				if ($bTzWasDisabled)
					CTimeZone::disable();
			break;
		}

		return ($value);
	}


	private static function removeArrayKeyRecursively($ar, $dropKey)
	{
		if (is_array($ar))
		{
			if (array_key_exists($dropKey, $ar))
				unset($ar[$dropKey]);

			foreach(array_keys($ar) as $k)
				$ar[$k] = self::removeArrayKeyRecursively($ar[$k], $dropKey);
		}

		return ($ar);
	}
}