Index: branches/5.1.x/core/kernel/db/db_event_handler.php
===================================================================
--- branches/5.1.x/core/kernel/db/db_event_handler.php	(revision 13404)
+++ branches/5.1.x/core/kernel/db/db_event_handler.php	(revision 13405)
@@ -1,2819 +1,2819 @@
 <?php
 /**
 * @version	$Id$
 * @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!');
 
 	define('EH_CUSTOM_PROCESSING_BEFORE',1);
 	define('EH_CUSTOM_PROCESSING_AFTER',2);
 
 	/**
 	 * Note:
 	 *   1. When adressing variables from submit containing
 	 *	 	Prefix_Special as part of their name use
 	 *	 	$event->getPrefixSpecial(true) instead of
 	 *	 	$event->Prefix_Special as usual. This is due PHP
 	 *	 	is converting "." symbols in variable names during
 	 *	 	submit info "_". $event->getPrefixSpecial optional
 	 *	 	1st parameter returns correct corrent Prefix_Special
 	 *	 	for variables beeing submitted such way (e.g. variable
 	 *	 	name that will be converted by PHP: "users.read_only_id"
 	 *	 	will be submitted as "users_read_only_id".
 	 *
 	 *	 2.	When using $this->Application-LinkVar on variables submitted
 	 *		from form which contain $Prefix_Special then note 1st item. Example:
 	 *		LinkVar($event->getPrefixSpecial(true).'_varname',$event->Prefix_Special.'_varname')
 	 *
 	 */
 
 
 	/**
 	 * EventHandler that is used to process
 	 * any database related events
 	 *
 	 */
 	class kDBEventHandler extends kEventHandler {
 
 		/**
 		* Description
 		*
 		* @var kDBConnection
 		* @access public
 		*/
 		var $Conn;
 
 		/**
 		 * Adds ability to address db connection
 		 *
 		 * @return kDBEventHandler
 		 * @access public
 		 */
 		function kDBEventHandler()
 		{
 			parent::kBase();
 			$this->Conn =& $this->Application->GetADODBConnection();
 		}
 
 		/**
 		 * Checks permissions of user
 		 *
 		 * @param kEvent $event
 		 */
 		function CheckPermission(&$event)
 		{
 			$section = $event->getSection();
 
 			if (!$this->Application->isAdmin) {
 				$allow_events = Array('OnSearch', 'OnSearchReset', 'OnNew');
 				if (in_array($event->Name, $allow_events)) {
 					// allow search on front
 					return true;
 				}
 			}
 			elseif (($event->Name == 'OnPreSaveAndChangeLanguage') && !$this->UseTempTables($event)) {
 				// allow changing language in grids, when not in editing mode
 				return $this->Application->CheckPermission($section . '.view', 1);
 			}
 
 			if (!preg_match('/^CATEGORY:(.*)/', $section)) {
 				// only if not category item events
 				if ((substr($event->Name, 0, 9) == 'OnPreSave') || ($event->Name == 'OnSave')) {
 					if ($this->isNewItemCreate($event)) {
 						return $this->Application->CheckPermission($section.'.add', 1);
 					}
 					else {
 						return $this->Application->CheckPermission($section.'.add', 1) || $this->Application->CheckPermission($section.'.edit', 1);
 					}
 				}
 			}
 
 			if ($event->Name == 'OnPreCreate') {
 				// save category_id before item create (for item category selector not to destroy permission checking category)
 				$this->Application->LinkVar('m_cat_id');
 			}
 
 			if ($event->Name == 'OnSaveWidths') {
 				return $this->Application->isAdminUser;
 			}
 
 			return parent::CheckPermission($event);
 		}
 
 		/**
 		 * Allows to override standart permission mapping
 		 *
 		 */
 		function mapPermissions()
 		{
 			parent::mapPermissions();
 			$permissions = Array(
 									'OnLoad'				=>	Array('self' => 'view', 'subitem' => 'view'),
 									'OnItemBuild'			=>	Array('self' => 'view', 'subitem' => 'view'),
 									'OnSuggestValues'		=>	Array('self' => 'view', 'subitem' => 'view'),
 
 									'OnBuild'				=>	Array('self' => true),
 
 									'OnNew'					=>	Array('self' => 'add', 'subitem' => 'add|edit'),
 									'OnCreate'				=>	Array('self' => 'add', 'subitem' => 'add|edit'),
 									'OnUpdate'				=>	Array('self' => 'edit', 'subitem' => 'add|edit'),
 									'OnSetPrimary'			=>	Array('self' => 'add|edit', 'subitem' => 'add|edit'),
 									'OnDelete'				=>	Array('self' => 'delete', 'subitem' => 'add|edit'),
 									'OnDeleteAll'			=>	Array('self' => 'delete', 'subitem' => 'add|edit'),
 									'OnMassDelete'			=>	Array('self' => 'delete', 'subitem' => 'add|edit'),
 									'OnMassClone'			=>	Array('self' => 'add', 'subitem' => 'add|edit'),
 
 									'OnCut'	=> array('self'=>'edit', 'subitem' => 'edit'),
 									'OnCopy'	=> array('self'=>'edit', 'subitem' => 'edit'),
 									'OnPaste'	=> array('self'=>'edit', 'subitem' => 'edit'),
 
 									'OnSelectItems'			=>	Array('self' => 'add|edit', 'subitem' => 'add|edit'),
 									'OnProcessSelected'		=>	Array('self' => 'add|edit', 'subitem' => 'add|edit'),
 									'OnStoreSelected'		=>	Array('self' => 'add|edit', 'subitem' => 'add|edit'),
 									'OnSelectUser'			=>	Array('self' => 'add|edit', 'subitem' => 'add|edit'),
 
 									'OnMassApprove'			=>	Array('self' => 'advanced:approve|edit', 'subitem' => 'advanced:approve|add|edit'),
 									'OnMassDecline'			=>	Array('self' => 'advanced:decline|edit', 'subitem' => 'advanced:decline|add|edit'),
 									'OnMassMoveUp'			=>	Array('self' => 'advanced:move_up|edit', 'subitem' => 'advanced:move_up|add|edit'),
 									'OnMassMoveDown'		=>	Array('self' => 'advanced:move_down|edit', 'subitem' => 'advanced:move_down|add|edit'),
 
 									'OnPreCreate'			=>	Array('self' => 'add|add.pending', 'subitem' => 'edit|edit.pending'),
 									'OnEdit'				=>	Array('self' => 'edit|edit.pending', 'subitem' => 'edit|edit.pending'),
 
 									'OnExport'					=>	Array('self' => 'view|advanced:export'),
 									'OnExportBegin'				=>	Array('self' => 'view|advanced:export'),
 									'OnExportProgress'			=>	Array('self' => 'view|advanced:export'),
 
 
 									'OnSetAutoRefreshInterval' => Array ('self' => true, 'subitem' => true),
 									'OnAutoRefreshToggle' => Array ('self' => true, 'subitem' => true),
 
 									// theese event do not harm, but just in case check them too :)
 									'OnCancelEdit'			=>	Array('self' => true, 'subitem' => true),
 									'OnCancel'				=>	Array('self' => true, 'subitem' => true),
 									'OnReset'				=>	Array('self' => true, 'subitem' => true),
 
 									'OnSetSorting'			=>	Array('self' => true, 'subitem' => true),
 									'OnSetSortingDirect'	=>	Array('self' => true, 'subitem' => true),
 									'OnResetSorting'	=>	Array('self' => true, 'subitem' => true),
 
 									'OnSetFilter'			=>	Array('self' => true, 'subitem' => true),
 									'OnApplyFilters'		=>	Array('self' => true, 'subitem' => true),
 									'OnRemoveFilters'		=>	Array('self' => true, 'subitem' => true),
 									'OnSetFilterPattern'		=>	Array('self' => true, 'subitem' => true),
 
 									'OnSetPerPage'			=>	Array('self' => true, 'subitem' => true),
 									'OnSetPage'				=>	Array('self' => true, 'subitem' => true),
 
 									'OnSearch'				=>	Array('self' => true, 'subitem' => true),
 									'OnSearchReset'			=>	Array('self' => true, 'subitem' => true),
 
 									'OnGoBack'				=>	Array('self' => true, 'subitem' => true),
 
 									// it checks permission itself since flash uploader does not send cookies
 									'OnUploadFile' => Array ('self' => true, 'subitem' => true),
 									'OnDeleteFile' => Array ('self' => true, 'subitem' => true),
 
 									'OnViewFile' => Array ('self' => true, 'subitem' => true),
 									'OnSaveWidths' => Array ('self' => true, 'subitem' => true),
 
 									'OnValidateMInputFields' => Array ('self' => 'view'),
 							);
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		function mapEvents()
 		{
 			$events_map = Array(
 								'OnRemoveFilters'	=>	'FilterAction',
 								'OnApplyFilters'	=>	'FilterAction',
 								'OnMassApprove'=>'iterateItems',
 								'OnMassDecline'=>'iterateItems',
 								'OnMassMoveUp'=>'iterateItems',
 								'OnMassMoveDown'=>'iterateItems',
 								);
 
 			$this->eventMethods = array_merge($this->eventMethods, $events_map);
 		}
 
 		/**
 		 * Returns ID of current item to be edited
 		 * by checking ID passed in get/post as prefix_id
 		 * or by looking at first from selected ids, stored.
 		 * Returned id is also stored in Session in case
 		 * it was explicitly passed as get/post
 		 *
 		 * @param kEvent $event
 		 * @return int
 		 */
 		function getPassedID(&$event)
 		{
 			if ($event->getEventParam('raise_warnings') === false) {
 				$event->setEventParam('raise_warnings', 1);
 			}
 
 			if (preg_match('/^auto-(.*)/', $event->Special, $regs) && $this->Application->prefixRegistred($regs[1])) {
 				// <inp2:lang.auto-phrase_Field name="DateFormat"/> - returns field DateFormat value from language (LanguageId is extracted from current phrase object)
 				$main_object =& $this->Application->recallObject($regs[1]);
 				/* @var $main_object kDBItem */
 
 				$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
 				return $main_object->GetDBField($id_field);
 			}
 
 			// 1. get id from post (used in admin)
 			$ret = $this->Application->GetVar($event->getPrefixSpecial(true).'_id');
 			if (($ret !== false) && ($ret != '')) {
 				return $ret;
 			}
 
 			// 2. get id from env (used in front)
 			$ret = $this->Application->GetVar($event->getPrefixSpecial().'_id');
 			if (($ret !== false) && ($ret != '')) {
 				return $ret;
 			}
 
 			// recall selected ids array and use the first one
 			$ids = $this->Application->GetVar($event->getPrefixSpecial().'_selected_ids');
 			if ($ids != '') {
 				$ids = explode(',',$ids);
 				if ($ids) {
 					$ret = array_shift($ids);
 				}
 			}
 			else { // if selected ids are not yet stored
 				$this->StoreSelectedIDs($event);
 				return $this->Application->GetVar($event->getPrefixSpecial().'_id'); // StoreSelectedIDs sets this variable
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * Prepares and stores selected_ids string
 		 * in Session and Application Variables
 		 * by getting all checked ids from grid plus
 		 * id passed in get/post as prefix_id
 		 *
 		 * @param kEvent $event
 		 * @param Array $direct_ids
 		 *
 		 * @return Array ids stored
 		 */
 		function StoreSelectedIDs(&$event, $direct_ids = null)
 		{
 			$wid = $this->Application->GetTopmostWid($event->Prefix);
 			$session_name = rtrim($event->getPrefixSpecial().'_selected_ids_'.$wid, '_');
 
 			$ids = $event->getEventParam('ids');
 			if (isset($direct_ids) || ($ids !== false)) {
 				// save ids directly if they given + reset array indexes
 				$resulting_ids = $direct_ids ? array_values($direct_ids) : ($ids ? array_values($ids) : false);
 				if ($resulting_ids) {
 					$this->Application->SetVar($event->getPrefixSpecial() . '_selected_ids', implode(',', $resulting_ids));
 					$this->Application->LinkVar($event->getPrefixSpecial() . '_selected_ids', $session_name, '', true);
 					$this->Application->SetVar($event->getPrefixSpecial() . '_id', $resulting_ids[0]);
 
 					return $resulting_ids;
 				}
 
 				return Array ();
 			}
 
 			$ret = Array();
 
 			// May be we don't need this part: ?
 			$passed = $this->Application->GetVar($event->getPrefixSpecial(true).'_id');
 			if($passed !== false && $passed != '')
 			{
 				array_push($ret, $passed);
 			}
 
 			$ids = Array();
 
 			// get selected ids from post & save them to session
 			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 			if($items_info)
 			{
 				$id_field = $this->Application->getUnitOption($event->Prefix,'IDField');
 				foreach($items_info as $id => $field_values)
 				{
 					if( getArrayValue($field_values,$id_field) ) array_push($ids,$id);
 				}
 				//$ids=array_keys($items_info);
 			}
 
 			$ret = array_unique(array_merge($ret, $ids));
 
 			$this->Application->SetVar($event->getPrefixSpecial().'_selected_ids', implode(',',$ret));
 			$this->Application->LinkVar($event->getPrefixSpecial().'_selected_ids', $session_name, '', !$ret); // optional when IDs are missing
 
 			// This is critical - otherwise getPassedID will return last ID stored in session! (not exactly true)
 			// this smells... needs to be refactored
 			$first_id = getArrayValue($ret,0);
 			if (($first_id === false) && ($event->getEventParam('raise_warnings') == 1)) {
 				if ($this->Application->isDebugMode()) {
 					$this->Application->Debugger->appendTrace();
 				}
 				trigger_error('Requested ID for prefix <b>'.$event->getPrefixSpecial().'</b> <span class="debug_error">not passed</span>',E_USER_NOTICE);
 			}
 
 			$this->Application->SetVar($event->getPrefixSpecial() . '_id', $first_id);
 			return $ret;
 		}
 
 		/**
 		 * Returns stored selected ids as an array
 		 *
 		 * @param kEvent $event
 		 * @param bool $from_session return ids from session (written, when editing was started)
 		 * @return array
 		 */
 		function getSelectedIDs(&$event, $from_session = false)
 		{
 			if ($from_session) {
 				$wid = $this->Application->GetTopmostWid($event->Prefix);
 				$var_name = rtrim($event->getPrefixSpecial().'_selected_ids_'.$wid, '_');
 				$ret = $this->Application->RecallVar($var_name);
 			}
 			else {
 				$ret = $this->Application->GetVar($event->getPrefixSpecial().'_selected_ids');
 			}
 
 			return explode(',', $ret);
 		}
 
 		/**
 		 * Stores IDs, selected in grid in session
 		 *
 		 * @param kEvent $event
 		 */
 		function OnStoreSelected(&$event)
 		{
 			$this->StoreSelectedIDs($event);
 
 			$id = $this->Application->GetVar($event->getPrefixSpecial() . '_id');
 
 			if ($id !== false) {
 				$event->SetRedirectParam($event->getPrefixSpecial() . '_id', $id);
 				$event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial());
 			}
 		}
 
 		/**
 		 * Returs associative array of submitted fields for current item
 		 * Could be used while creating/editing single item -
 		 * meaning on any edit form, except grid edit
 		 *
 		 * @param kEvent $event
 		 */
 		function getSubmittedFields(&$event)
 		{
 			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 			$field_values = $items_info ? array_shift($items_info) : Array();
 			return $field_values;
 		}
 
 		/**
 		 * Removes any information about current/selected ids
 		 * from Application variables and Session
 		 *
 		 * @param kEvent $event
 		 */
 		function clearSelectedIDs(&$event)
 		{
 			$prefix_special = $event->getPrefixSpecial();
 
 			$ids = implode(',', $this->getSelectedIDs($event, true));
 			$event->setEventParam('ids', $ids);
 
 			$wid = $this->Application->GetTopmostWid($event->Prefix);
 			$session_name = rtrim($prefix_special.'_selected_ids_'.$wid, '_');
 
 			$this->Application->RemoveVar($session_name);
 			$this->Application->SetVar($prefix_special.'_selected_ids', '');
 
 			$this->Application->SetVar($prefix_special.'_id', ''); // $event->getPrefixSpecial(true).'_id' too may be
 		}
 
 		/*function SetSaveEvent(&$event)
 		{
 			$this->Application->SetVar($event->Prefix_Special.'_SaveEvent','OnUpdate');
 			$this->Application->LinkVar($event->Prefix_Special.'_SaveEvent');
 		}*/
 
 		/**
 		 * Common builder part for Item & List
 		 *
 		 * @param kDBBase $object
 		 * @param kEvent $event
 		 * @access private
 		 */
 		function dbBuild(&$object, &$event)
 		{
 			// for permission checking inside item/list build events
 			$event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true));
 
 			$object->Configure( $event->getEventParam('populate_ml_fields') || $this->Application->getUnitOption($event->Prefix, 'PopulateMlFields') );
 			$this->PrepareObject($object, $event);
 
 			// force live table if specified or is original item
 			$live_table = $event->getEventParam('live_table') || $event->Special == 'original';
 
 			if( $this->UseTempTables($event) && !$live_table )
 			{
 				$object->SwitchToTemp();
 			}
 
 			// This strange constuction creates hidden field for storing event name in form submit
 			// It pass SaveEvent to next screen, otherwise after unsuccsefull create it will try to update rather than create
 			$current_event = $this->Application->GetVar($event->Prefix_Special.'_event');
 //			$this->Application->setEvent($event->Prefix_Special, $current_event);
 			$this->Application->setEvent($event->Prefix_Special, '');
 
 			$save_event = $this->UseTempTables($event) && $this->Application->GetTopmostPrefix($event->Prefix) == $event->Prefix ? 'OnSave' : 'OnUpdate';
 			$this->Application->SetVar($event->Prefix_Special.'_SaveEvent',$save_event);
 		}
 
 		/**
 		 * Checks, that currently loaded item is allowed for viewing (non permission-based)
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 */
 		function checkItemStatus(&$event)
 		{
 			$status_fields = $this->Application->getUnitOption($event->Prefix,'StatusField');
 			if (!$status_fields) {
 				return true;
 			}
 
 			$status_field = array_shift($status_fields);
 			if ($status_field == 'Status' || $status_field == 'Enabled') {
 				$object =& $event->getObject();
 				if (!$object->isLoaded()) {
 					return true;
 				}
 
 				return $object->GetDBField($status_field) == STATUS_ACTIVE;
 			}
 			return true;
 		}
 
 		/**
 		 * Shows not found template content
 		 *
 		 * @param kEvent $event
 		 *
 		 */
 		function _errorNotFound(&$event)
 		{
 			if ($event->getEventParam('raise_warnings') === 0) {
 				// when it's possible, that autoload fails do nothing
 				return ;
 			}
 
 			if ($this->Application->isDebugMode()) {
 				$this->Application->Debugger->appendTrace();
 			}
 			trigger_error('ItemLoad Permission Failed for prefix [' . $event->getPrefixSpecial() . '] in <strong>checkItemStatus</strong>, leading to "404 Not Found"', E_USER_WARNING);
 
 			header('HTTP/1.0 404 Not Found');
 
 			while (ob_get_level()) {
 				ob_end_clean();
 			}
 
 			// object is used inside template parsing, so break out any parsing and return error document
 			$error_template = $this->Application->ConfigValue('ErrorTemplate');
 
 			$themes_helper =& $this->Application->recallObject('ThemesHelper');
 			/* @var $themes_helper kThemesHelper */
 
 			$this->Application->SetVar('t', $error_template);
 			$this->Application->SetVar('m_cat_id', $themes_helper->getPageByTemplate($error_template));
 
 			// in case if missing item is recalled first from event (not from template)
 			$this->Application->InitParser();
 			$this->Application->HTML = $this->Application->ParseBlock( Array ('name' => $error_template) );
 			$this->Application->Done();
 			exit;
 		}
 
 		/**
 		 * Builds item (loads if needed)
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		function OnItemBuild(&$event)
 		{
 			$object =& $event->getObject();
 			$this->dbBuild($object,$event);
 
 			$sql = $this->ItemPrepareQuery($event);
 			$sql = $this->Application->ReplaceLanguageTags($sql);
 			$object->setSelectSQL($sql);
 
 			// 2. loads if allowed
 			$auto_load = $this->Application->getUnitOption($event->Prefix,'AutoLoad');
 			$skip_autload = $event->getEventParam('skip_autoload');
 
 			if ($auto_load && !$skip_autload) {
 				$perm_status = true;
 				$user_id = $this->Application->RecallVar('user_id');
 				$event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true));
 				$status_checked = false;
 				if ($user_id == -1 || $this->CheckPermission($event)) {
 					// don't autoload item, when user doesn't have view permission
 					$this->LoadItem($event);
 
 					$status_checked = true;
 					$editing_mode = defined('EDITING_MODE') ? EDITING_MODE : false;
 
 					if ($user_id != -1 && !$this->Application->isAdmin && !($editing_mode || $this->checkItemStatus($event))) {
 						// non-root user AND on front-end AND (not editing mode || incorrect status)
 						$perm_status = false;
 					}
 				}
 				else {
 					$perm_status = false;
 				}
 
 				if (!$perm_status) {
 					// when no permission to view item -> redirect to no pemrission template
 					if ($this->Application->isDebugMode()) {
 						$this->Application->Debugger->appendTrace();
 					}
 
 					trigger_error('ItemLoad Permission Failed for prefix ['.$event->getPrefixSpecial().'] in <strong>'.($status_checked ? 'checkItemStatus' : 'CheckPermission').'</strong>', E_USER_WARNING);
 					$template = $this->Application->isAdmin ? 'no_permission' : $this->Application->ConfigValue('NoPermissionTemplate');
 
 					if (MOD_REWRITE) {
 						$redirect_params = Array (
 							'm_cat_id' => 0,
 							'next_template' => urlencode('external:' . $_SERVER['REQUEST_URI']),
 						);
 					}
 					else {
 						$redirect_params = Array (
 							'next_template' => $this->Application->GetVar('t'),
 						);
 					}
 
 					$this->Application->Redirect($template, $redirect_params);
 				}
 			}
 
 			$actions =& $this->Application->recallObject('kActions');
 			$actions->Set($event->Prefix_Special.'_GoTab', '');
 
 			$actions->Set($event->Prefix_Special.'_GoId', '');
 		}
 
 		/**
 		 * Build subtables array from configs
 		 *
 		 * @param kEvent $event
 		 */
 		function OnTempHandlerBuild(&$event)
 		{
 			$object =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
 			/* @var $object kTempTablesHandler */
 
 			$object->BuildTables( $event->Prefix, $this->getSelectedIDs($event) );
 		}
 
 		/**
 		 * Checks, that object used in event should use temp tables
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 */
 		function UseTempTables(&$event)
 		{
 			$top_prefix = $this->Application->GetTopmostPrefix($event->Prefix); // passed parent, not always actual
 			$special = ($top_prefix == $event->Prefix) ? $event->Special : $this->getMainSpecial($event);
 
 			return $this->Application->IsTempMode($event->Prefix, $special);
 		}
 
 		/**
 		 * Returns table prefix from event (temp or live)
 		 *
 		 * @param kEvent $event
 		 * @return string
 		 * @todo Needed? Should be refactored (by Alex)
 		 */
 		function TablePrefix(&$event)
 		{
 			return $this->UseTempTables($event) ? $this->Application->GetTempTablePrefix('prefix:'.$event->Prefix).TABLE_PREFIX : TABLE_PREFIX;
 		}
 
 		/**
 		 * Load item if id is available
 		 *
 		 * @param kEvent $event
 		 */
 		function LoadItem(&$event)
 		{
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$id = $this->getPassedID($event);
 
 			if ($object->isLoaded() && !is_array($id) && ($object->GetID() == $id)) {
 				// object is already loaded by same id
 				return ;
 			}
 
 			if ($object->Load($id)) {
 				$actions =& $this->Application->recallObject('kActions');
 				$actions->Set($event->Prefix_Special.'_id', $object->GetID() );
 			}
 			else {
 				$object->setID($id);
 			}
 		}
 
 		/**
 		 * Builds list
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		function OnListBuild(&$event)
 		{
 			$object =& $event->getObject();
 			/* @var $object kDBList */
 
 			$this->dbBuild($object,$event);
 
 			if (!$object->mainList && $event->getEventParam('main_list')) {
 				// once list is set to main, then even "requery" parameter can't remove that
 				/*$passed = $this->Application->GetVar('passed');
 				$this->Application->SetVar('passed', $passed . ',' . $event->Prefix);*/
 
 				$object->mainList = true;
 			}
 
 			$sql = $this->ListPrepareQuery($event);
 			$sql = $this->Application->ReplaceLanguageTags($sql);
 			$object->setSelectSQL($sql);
 
 			$object->Counted = false; // when requery="1" should re-count records too!
 			$object->ClearOrderFields(); // prevents duplicate order fields, when using requery="1"
 
 			$object->linkToParent( $this->getMainSpecial($event) );
 
 			$this->AddFilters($event);
 			$this->SetCustomQuery($event);	// new!, use this for dynamic queries based on specials for ex.
 			$this->SetPagination($event);
 			$this->SetSorting($event);
 
 //			$object->CalculateTotals();	 // Now called in getTotals to avoid extra query
 
 			$actions =& $this->Application->recallObject('kActions');
 			$actions->Set('remove_specials['.$event->Prefix_Special.']', '0');
 			$actions->Set($event->Prefix_Special.'_GoTab', '');
 		}
 
 
 		/**
 		 * Get's special of main item for linking with subitem
 		 *
 		 * @param kEvent $event
 		 * @return string
 		 */
 		function getMainSpecial(&$event)
 		{
 			$main_special = $event->getEventParam('main_special');
 
 			if ($main_special === false) {
 				// main item's special not passed
 
 				if (substr($event->Special, -5) == '-item') {
 					// temp handler added "-item" to given special -> process that here
 					return substr($event->Special, 0, -5);
 				}
 
 				// by default subitem's special is used for main item searching
 				return $event->Special;
 			}
 
 			return $main_special;
 		}
 
 		/**
 		 * Apply any custom changes to list's sql query
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 * @see OnListBuild
 		 */
 		function SetCustomQuery(&$event)
 		{
 
 		}
 
 		/**
 		 * Set's new perpage for grid
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSetPerPage(&$event)
 		{
 			$per_page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_PerPage');
 			$event->SetRedirectParam($event->getPrefixSpecial() . '_PerPage', $per_page);
 			$event->SetRedirectParam('pass', 'm,' . $event->getPrefixSpecial());
 
 			if (!$this->Application->isAdminUser) {
 				$list_helper =& $this->Application->recallObject('ListHelper');
 				/* @var $list_helper ListHelper */
 
 				$this->_passListParams($event, 'per_page');
 
-				if ($per_page != $list_helper->getDefaultPerPage($event->Prefix)) {
+				/*if ($per_page != $list_helper->getDefaultPerPage($event->Prefix)) {
 					$event->SetRedirectParam('per_page', $per_page);
-				}
+				}*/
 			}
 		}
 
 		/**
 		 * Occurs when page is changed (only for hooking)
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSetPage(&$event)
 		{
 			$page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Page');
 			$event->SetRedirectParam($event->getPrefixSpecial() . '_Page', $page);
 			$event->SetRedirectParam('pass', 'm,' . $event->getPrefixSpecial());
 
 			if (!$this->Application->isAdminUser) {
-				if ($page > 1) {
+				/*if ($page > 1) {
 					$event->SetRedirectParam('page', $page);
-				}
+				}*/
 
 				$this->_passListParams($event, 'page');
 			}
 		}
 
 		/**
 		 * Passes through main list pagination and sorting
 		 *
 		 * @param kEvent $event
 		 * @param string $skip_var
 		 */
 		function _passListParams(&$event, $skip_var)
 		{
 			$param_names = array_diff(Array ('page', 'per_page', 'sort_by'), Array ($skip_var));
 
 			$list_helper =& $this->Application->recallObject('ListHelper');
 			/* @var $list_helper ListHelper */
 
 			foreach ($param_names as $param_name) {
 				$value = $this->Application->GetVar($param_name);
 
 				switch ($param_name) {
 					case 'page':
 						if ($value > 1) {
 							$event->SetRedirectParam('page', $value);
 						}
 						break;
 
 					case 'per_page':
 						if ($value > 0) {
 							if ($value != $list_helper->getDefaultPerPage($event->Prefix)) {
 								$event->SetRedirectParam('per_page', $value);
 							}
 						}
 						break;
 
 					case 'sort_by':
 						$event->setPseudoClass('_List');
 						$object =& $event->getObject( Array ('main_list' => 1) );
 						/* @var $object kDBList */
 
 						if ($list_helper->hasUserSorting($object)) {
 							$event->SetRedirectParam('sort_by', $value);
 						}
 						break;
 				}
 			}
 		}
 
 		/**
 		 * Set's correct page for list
 		 * based on data provided with event
 		 *
 		 * @param kEvent $event
 		 * @access private
 		 * @see OnListBuild
 		 */
 		function SetPagination(&$event)
 		{
 			$object =& $event->getObject();
 			/* @var $object kDBList */
 
 			// get PerPage (forced -> session -> config -> 10)
 			$object->SetPerPage( $this->getPerPage($event) );
 
 			// main lists on Front-End have special get parameter for page
 			$page = $object->mainList ? $this->Application->GetVar('page') : false;
 
 			if (!$page) {
 				// page is given in "env" variable for given prefix
 				$page = $this->Application->GetVar($event->getPrefixSpecial() . '_Page');
 			}
 
 			if (!$page && $event->Special) {
 				// when not part of env, then variables like "prefix.special_Page" are
 				// replaced (by PHP) with "prefix_special_Page", so check for that too
 				$page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Page');
 			}
 
 			if (!$object->mainList) {
 				// main lists doesn't use session for page storing
 				$this->Application->StoreVarDefault($event->getPrefixSpecial() . '_Page', 1, true); // true for optional
 
 				if ($page) {
 					// page found in request -> store in session
 					$this->Application->StoreVar($event->getPrefixSpecial() . '_Page', $page, true); //true for optional
 				}
 				else {
 					// page not found in request -> get from session
 					$page = $this->Application->RecallVar($event->getPrefixSpecial() . '_Page');
 				}
 
 				if ( !$event->getEventParam('skip_counting') ) {
 					// when stored page is larger, then maximal list page number
 					// (such case is also processed in kDBList::Query method)
 					$pages = $object->GetTotalPages();
 
 					if ($page > $pages) {
 						$page = 1;
 						$this->Application->StoreVar($event->getPrefixSpecial() . '_Page', 1, true);
 					}
 				}
 			}
 
 			$object->SetPage($page);
 		}
 
 		/**
 		 * Returns current per-page setting for list
 		 *
 		 * @param kEvent $event
 		 * @return int
 		 */
 		function getPerPage(&$event)
 		{
 			$object =& $event->getObject();
 			/* @var $object kDBList */
 
 			$per_page = $event->getEventParam('per_page');
 
 			if ($per_page) {
 				// per-page is passed as tag parameter to PrintList, InitList, etc.
 				$config_mapping = $this->Application->getUnitOption($event->Prefix, 'ConfigMapping');
 
 				// 2. per-page setting is stored in configuration variable
 				if ($config_mapping) {
 					// such pseudo per-pages are only defined in templates directly
 					switch ($per_page) {
 						case 'short_list':
 							$per_page = $this->Application->ConfigValue($config_mapping['ShortListPerPage']);
 							break;
 
 						case 'default':
 							$per_page = $this->Application->ConfigValue($config_mapping['PerPage']);
 							break;
 					}
 				}
 
 				return $per_page;
 			}
 
 			if (!$per_page && $object->mainList) {
 				// main lists on Front-End have special get parameter for per-page
 				$per_page = $this->Application->GetVar('per_page');
 			}
 
 			if (!$per_page) {
 				// per-page is given in "env" variable for given prefix
 				$per_page = $this->Application->GetVar($event->getPrefixSpecial() . '_PerPage');
 			}
 
 			if (!$per_page && $event->Special) {
 				// when not part of env, then variables like "prefix.special_PerPage" are
 				// replaced (by PHP) with "prefix_special_PerPage", so check for that too
 				$per_page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_PerPage');
 			}
 
 			if (!$object->mainList) {
 				// per-page given in env and not in main list
 				$view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view');
 
 				if ($per_page) {
 					// per-page found in request -> store in session and persistent session
 					$this->Application->StoreVar($event->getPrefixSpecial() . '_PerPage', $per_page, true); //true for optional
 					$this->Application->StorePersistentVar($event->getPrefixSpecial() . '_PerPage.' . $view_name, $per_page);
 				}
 				else {
 					// per-page not found in request -> get from pesistent session (or session)
 					$storage_prefix = $event->getEventParam('same_special') ? $event->Prefix : $event->getPrefixSpecial();
 					$per_page = $this->Application->RecallPersistentVar($storage_prefix . '_PerPage.' . $view_name, ALLOW_DEFAULT_SETTINGS);
 
 					if (!$per_page) {
 						// per-page is stored to current session
 						$per_page = $this->Application->RecallVar($storage_prefix . '_PerPage');
 					}
 				}
 			}
 
 			if (!$per_page) {
 				// per page wan't found in request/session/persistent session
 				$list_helper =& $this->Application->recallObject('ListHelper');
 				/* @var $list_helper ListHelper */
 
 				// allow to override default per-page value from tag
 				$default_per_page = $event->getEventParam('default_per_page');
 
 				if (!is_numeric($default_per_page)) {
 					$default_per_page = 10;
 				}
 
 				$per_page = $list_helper->getDefaultPerPage($event->Prefix, $default_per_page);
 			}
 
 			return $per_page;
 		}
 
 		/**
 		 * Set's correct sorting for list
 		 * based on data provided with event
 		 *
 		 * @param kEvent $event
 		 * @access private
 		 * @see OnListBuild
 		 */
 		function SetSorting(&$event)
 		{
 			$event->setPseudoClass('_List');
 
 			$object =& $event->getObject();
 			/* @var $object kDBList */
 
 			if ($object->mainList) {
 				$sort_by = $this->Application->GetVar('sort_by');
 				$cur_sort1 = $cur_sort1_dir = $cur_sort2 = $cur_sort2_dir = false;
 
 				if ($sort_by) {
 					list ($cur_sort1, $cur_sort1_dir) = explode(',', $sort_by);
 				}
 			}
 			else {
 				$storage_prefix = $event->getEventParam('same_special') ? $event->Prefix : $event->Prefix_Special;
 				$cur_sort1 = $this->Application->RecallVar($storage_prefix . '_Sort1');
 				$cur_sort1_dir = $this->Application->RecallVar($storage_prefix . '_Sort1_Dir');
 				$cur_sort2 = $this->Application->RecallVar($storage_prefix . '_Sort2');
 				$cur_sort2_dir = $this->Application->RecallVar($storage_prefix . '_Sort2_Dir');
 			}
 
 			$tag_sort_by = $event->getEventParam('sort_by');
 
 			if ($tag_sort_by) {
 				if ($tag_sort_by == 'random') {
 					$object->AddOrderField('RAND()', '');
 				}
 				else {
 					// multiple sortings could be specified at once
 					$tag_sort_by = explode('|', $tag_sort_by);
 
 					foreach ($tag_sort_by as $sorting_element) {
 						list ($by, $dir) = explode(',', $sorting_element);
 						$object->AddOrderField($by, $dir);
 					}
 				}
 			}
 
 			$list_sortings = $this->Application->getUnitOption($event->Prefix, 'ListSortings');
 			$sorting_prefix = getArrayValue($list_sortings, $event->Special) ? $event->Special : '';
 			$sorting_configs = $this->Application->getUnitOption($event->Prefix, 'ConfigMapping');
 
 			if ($sorting_configs && array_key_exists('DefaultSorting1Field', $sorting_configs)) {
 				// sorting defined in configuration variables overrides one from unit config
 				$list_sortings[$sorting_prefix]['Sorting'] = Array (
 					$this->Application->ConfigValue($sorting_configs['DefaultSorting1Field']) => $this->Application->ConfigValue($sorting_configs['DefaultSorting1Dir']),
 					$this->Application->ConfigValue($sorting_configs['DefaultSorting2Field']) => $this->Application->ConfigValue($sorting_configs['DefaultSorting2Dir']),
 				);
 			}
 
 			// use default if not specified in session
 			if (!$cur_sort1 || !$cur_sort1_dir) {
 				$sorting = getArrayValue($list_sortings, $sorting_prefix, 'Sorting');
 
 				if ($sorting) {
 					reset($sorting);
 					$cur_sort1 = key($sorting);
 					$cur_sort1_dir = current($sorting);
 
 					if (next($sorting)) {
 						$cur_sort2 = key($sorting);
 						$cur_sort2_dir = current($sorting);
 					}
 				}
 			}
 
 			// always add forced sorting before any user sortings
 			$forced_sorting = getArrayValue($list_sortings, $sorting_prefix, 'ForcedSorting');
 
 			if ($forced_sorting) {
 				foreach ($forced_sorting as $field => $dir) {
 					$object->AddOrderField($field, $dir);
 				}
 			}
 
 			// add user sortings
 			if ($cur_sort1 != '' && $cur_sort1_dir != '') {
 				$object->AddOrderField($cur_sort1, $cur_sort1_dir);
 			}
 
 			if ($cur_sort2 != '' && $cur_sort2_dir != '') {
 				$object->AddOrderField($cur_sort2, $cur_sort2_dir);
 			}
 		}
 
 		/**
 		 * Add filters found in session
 		 *
 		 * @param kEvent $event
 		 */
 		function AddFilters(&$event)
 		{
 			$object =& $event->getObject();
 
 			$edit_mark = rtrim($this->Application->GetSID().'_'.$this->Application->GetTopmostWid($event->Prefix), '_');
 
 			// add search filter
 			$filter_data = $this->Application->RecallVar($event->getPrefixSpecial().'_search_filter');
 			if ($filter_data) {
 				$filter_data = unserialize($filter_data);
 				foreach ($filter_data as $filter_field => $filter_params) {
 					$filter_type = ($filter_params['type'] == 'having') ? HAVING_FILTER : WHERE_FILTER;
 					$filter_value = str_replace(EDIT_MARK, $edit_mark, $filter_params['value']);
 					$object->addFilter($filter_field, $filter_value, $filter_type, FLT_SEARCH);
 				}
 			}
 
 			// add custom filter
 			$view_name = $this->Application->RecallVar($event->getPrefixSpecial().'_current_view');
 			$custom_filters = $this->Application->RecallPersistentVar($event->getPrefixSpecial().'_custom_filter.'.$view_name);
 			if ($custom_filters) {
 				$grid_name = $event->getEventParam('grid');
 				$custom_filters = unserialize($custom_filters);
 				if (isset($custom_filters[$grid_name])) {
 					foreach ($custom_filters[$grid_name] as $field_name => $field_options) {
 						list ($filter_type, $field_options) = each($field_options);
 						if (isset($field_options['value']) && $field_options['value']) {
 							$filter_type = ($field_options['sql_filter_type'] == 'having') ? HAVING_FILTER : WHERE_FILTER;
 							$filter_value = str_replace(EDIT_MARK, $edit_mark, $field_options['value']);
 							$object->addFilter($field_name, $filter_value, $filter_type, FLT_CUSTOM);
 						}
 					}
 				}
 			}
 
 			$view_filter = $this->Application->RecallVar($event->getPrefixSpecial().'_view_filter');
 			if($view_filter)
 			{
 				$view_filter = unserialize($view_filter);
 				$temp_filter =& $this->Application->makeClass('kMultipleFilter');
 				$filter_menu = $this->Application->getUnitOption($event->Prefix,'FilterMenu');
 
 				$group_key = 0; $group_count = count($filter_menu['Groups']);
 				while($group_key < $group_count)
 				{
 					$group_info = $filter_menu['Groups'][$group_key];
 
 					$temp_filter->setType( constant('FLT_TYPE_'.$group_info['mode']) );
 					$temp_filter->clearFilters();
 					foreach ($group_info['filters'] as $flt_id)
 					{
 						$sql_key = getArrayValue($view_filter,$flt_id) ? 'on_sql' : 'off_sql';
 						if ($filter_menu['Filters'][$flt_id][$sql_key] != '')
 						{
 							$temp_filter->addFilter('view_filter_'.$flt_id, $filter_menu['Filters'][$flt_id][$sql_key]);
 						}
 					}
 					$object->addFilter('view_group_'.$group_key, $temp_filter, $group_info['type'] , FLT_VIEW);
 					$group_key++;
 				}
 			}
 		}
 
 		/**
 		 * Set's new sorting for list
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		function OnSetSorting(&$event)
 		{
 			$cur_sort1		=	$this->Application->RecallVar($event->Prefix_Special.'_Sort1');
 			$cur_sort1_dir	=	$this->Application->RecallVar($event->Prefix_Special.'_Sort1_Dir');
 
 			$use_double_sorting = $this->Application->ConfigValue('UseDoubleSorting');
 
 			if ($use_double_sorting) {
 				$cur_sort2		=	$this->Application->RecallVar($event->Prefix_Special.'_Sort2');
 				$cur_sort2_dir	=	$this->Application->RecallVar($event->Prefix_Special.'_Sort2_Dir');
 			}
 
 			$passed_sort1 = $this->Application->GetVar($event->getPrefixSpecial(true).'_Sort1');
 			if ($cur_sort1 == $passed_sort1) {
 				$cur_sort1_dir = $cur_sort1_dir == 'asc' ? 'desc' : 'asc';
 			}
 			else {
 				if ($use_double_sorting) {
 					$cur_sort2 = $cur_sort1;
 					$cur_sort2_dir = $cur_sort1_dir;
 				}
 				$cur_sort1 = $passed_sort1;
 				$cur_sort1_dir = 'asc';
 			}
 
 			$this->Application->StoreVar($event->Prefix_Special.'_Sort1', $cur_sort1);
 			$this->Application->StoreVar($event->Prefix_Special.'_Sort1_Dir', $cur_sort1_dir);
 
 			if ($use_double_sorting) {
 				$this->Application->StoreVar($event->Prefix_Special.'_Sort2', $cur_sort2);
 				$this->Application->StoreVar($event->Prefix_Special.'_Sort2_Dir', $cur_sort2_dir);
 			}
 		}
 
 		/**
 		 * Set sorting directly to session (used for category item sorting (front-end), grid sorting (admin, view menu)
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSetSortingDirect(&$event)
 		{
 			// used on Front-End in category item lists
 			$prefix_special = $event->getPrefixSpecial();
 			$combined = $this->Application->GetVar($event->getPrefixSpecial(true) . '_CombinedSorting');
 
 			if ($combined) {
 				list ($field, $dir) = explode('|', $combined);
 
 				if ($this->Application->isAdmin || !$this->Application->GetVar('main_list')) {
 					$this->Application->StoreVar($prefix_special . '_Sort1', $field);
 					$this->Application->StoreVar($prefix_special . '_Sort1_Dir', $dir);
 				}
 				else {
 					$event->setPseudoClass('_List');
 					$this->Application->SetVar('sort_by', $field . ',' . $dir);
 
 					$object =& $event->getObject( Array ('main_list' => 1) );
 					/* @var $object kDBList */
 
 					$list_helper =& $this->Application->recallObject('ListHelper');
 					/* @var $list_helper ListHelper */
 
 					$this->_passListParams($event, 'sort_by');
 
 					if ($list_helper->hasUserSorting($object)) {
 						$event->SetRedirectParam('sort_by', $field . ',' . strtolower($dir));
 					}
 
 					$event->SetRedirectParam('pass', 'm');
 				}
 
 				return ;
 			}
 
 			// used in "View Menu -> Sort" menu in administrative console
 			$field_pos = $this->Application->GetVar($event->getPrefixSpecial(true) . '_SortPos');
 			$this->Application->LinkVar($event->getPrefixSpecial(true) . '_Sort' . $field_pos, $prefix_special . '_Sort' . $field_pos);
 			$this->Application->LinkVar($event->getPrefixSpecial(true) . '_Sort' . $field_pos . '_Dir', $prefix_special . '_Sort' . $field_pos . '_Dir');
 		}
 
 		/**
 		 * Reset grid sorting to default (from config)
 		 *
 		 * @param kEvent $event
 		 */
 		function OnResetSorting(&$event)
 		{
 			$this->Application->RemoveVar($event->Prefix_Special.'_Sort1');
 			$this->Application->RemoveVar($event->Prefix_Special.'_Sort1_Dir');
 			$this->Application->RemoveVar($event->Prefix_Special.'_Sort2');
 			$this->Application->RemoveVar($event->Prefix_Special.'_Sort2_Dir');
 		}
 
 		/**
 		 * Sets grid refresh interval
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSetAutoRefreshInterval(&$event)
 		{
 			$refresh_interval = $this->Application->GetVar('refresh_interval');
 
 			$view_name = $this->Application->RecallVar($event->getPrefixSpecial().'_current_view');
 			$this->Application->StorePersistentVar($event->getPrefixSpecial().'_refresh_interval.'.$view_name, $refresh_interval);
 		}
 
 		/**
 		 * Changes auto-refresh state for grid
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAutoRefreshToggle(&$event)
 		{
 			$refresh_intervals = $this->Application->ConfigValue('AutoRefreshIntervals');
 			if (!$refresh_intervals) {
 				return ;
 			}
 
 			$view_name = $this->Application->RecallVar($event->getPrefixSpecial().'_current_view');
 			$auto_refresh = $this->Application->RecallPersistentVar($event->getPrefixSpecial().'_auto_refresh.'.$view_name);
 
 			if ($auto_refresh === false) {
 				$refresh_intervals = explode(',', $refresh_intervals);
 				$this->Application->StorePersistentVar($event->getPrefixSpecial().'_refresh_interval.'.$view_name, $refresh_intervals[0]);
 			}
 
 			$this->Application->StorePersistentVar($event->getPrefixSpecial().'_auto_refresh.'.$view_name, $auto_refresh ? 0 : 1);
 		}
 
 		/**
 		 * Creates needed sql query to load item,
 		 * if no query is defined in config for
 		 * special requested, then use default
 		 * query
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		function ItemPrepareQuery(&$event)
 		{
 			$sqls = $this->Application->getUnitOption($event->Prefix, 'ItemSQLs', Array ());
 			$special = array_key_exists($event->Special, $sqls) ? $event->Special : '';
 
 			if (!array_key_exists($special, $sqls)) {
 				// preferred special not found in ItemSQLs -> use analog from ListSQLs
 				return $this->ListPrepareQuery($event);
 			}
 
 			return $sqls[$special];
 		}
 
 		/**
 		 * Creates needed sql query to load list,
 		 * if no query is defined in config for
 		 * special requested, then use default
 		 * query
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		function ListPrepareQuery(&$event)
 		{
 			$sqls = $this->Application->getUnitOption($event->Prefix, 'ListSQLs', Array ());
 			return $sqls[ array_key_exists($event->Special, $sqls) ? $event->Special : '' ];
 		}
 
 		/**
 		 * Apply custom processing to item
 		 *
 		 * @param kEvent $event
 		 */
 		function customProcessing(&$event, $type)
 		{
 
 		}
 
 		/* Edit Events mostly used in Admin */
 
 		/**
 		 * Creates new kDBItem
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		function OnCreate(&$event)
 		{
 			$object =& $event->getObject( Array('skip_autoload' => true) );
 			/* @var $object kDBItem */
 
 			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 			if ($items_info) {
 				list($id,$field_values) = each($items_info);
 				$object->SetFieldsFromHash($field_values);
 			}
 
 			$this->customProcessing($event,'before');
 
 			//look at kDBItem' Create for ForceCreateId description, it's rarely used and is NOT set by default
 			if( $object->Create($event->getEventParam('ForceCreateId')) )
 			{
 				if( $object->IsTempTable() ) $object->setTempID();
 				$this->customProcessing($event,'after');
 				$event->status=erSUCCESS;
 				$event->redirect_params = Array('opener'=>'u');
 			}
 			else
 			{
 				$event->status = erFAIL;
 				$event->redirect = false;
 				$this->Application->SetVar($event->Prefix_Special.'_SaveEvent','OnCreate');
 				$object->setID($id);
 			}
 		}
 
 		/**
 		 * Updates kDBItem
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		function OnUpdate(&$event)
 		{
 			$object =& $event->getObject( Array('skip_autoload' => true) );
 
 			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 			if ($items_info) {
 				foreach ($items_info as $id => $field_values) {
 					$object->Load($id);
 	 				$object->SetFieldsFromHash($field_values);
 	 				$this->customProcessing($event, 'before');
 
 					if ( $object->Update($id) ) {
 						$this->customProcessing($event, 'after');
 						$event->status = erSUCCESS;
 					}
 					else {
 						$event->status = erFAIL;
 						$event->redirect = false;
 						break;
 					}
 				}
 			}
 
 			$event->SetRedirectParam('opener', 'u');
 		}
 
 		/**
 		 * Delete's kDBItem object
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		function OnDelete(&$event)
 		{
 			if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
 				$event->status = erFAIL;
 				return;
 			}
 
 			$temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
 			/* @var $temp kTempTablesHandler */
 
 			$temp->DeleteItems($event->Prefix, $event->Special, Array($this->getPassedID($event)));
 		}
 
 		/**
 		 * Deletes all records from table
 		 *
 		 * @param kEvent $event
 		 */
 		function OnDeleteAll(&$event)
 		{
 			$sql = 'SELECT ' . $this->Application->getUnitOption($event->Prefix, 'IDField') . '
 					FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName');
 			$ids = $this->Conn->GetCol($sql);
 
 			if ($ids) {
 				$temp_handler =& $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler');
 				/* @var $temp_handler kTempTablesHandler */
 
 				$temp_handler->DeleteItems($event->Prefix, $event->Special, $ids);
 			}
 		}
 
 		/**
 		 * Prepares new kDBItem object
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		function OnNew(&$event)
 		{
 			$object =& $event->getObject( Array('skip_autoload' => true) );
 			/* @var $object kDBItem */
 
 			$object->Clear(0);
 			$this->Application->SetVar($event->Prefix_Special.'_SaveEvent', 'OnCreate');
 
 			if ($event->getEventParam('top_prefix') != $event->Prefix) {
 				// this is subitem prefix, so use main item special
 				$table_info = $object->getLinkedInfo( $this->getMainSpecial($event) );
 			}
 			else {
 				$table_info = $object->getLinkedInfo();
 			}
 
 			$object->SetDBField($table_info['ForeignKey'], $table_info['ParentId']);
 
 			$event->redirect = false;
 		}
 
 		/**
 		 * Cancel's kDBItem Editing/Creation
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		function OnCancel(&$event)
 		{
 			$object =& $event->getObject(Array('skip_autoload' => true));
 
 			$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
 			if ($items_info) {
 				$delete_ids = Array();
 				$temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
 				foreach ($items_info as $id => $field_values) {
 					$object->Load($id);
 					// record created for using with selector (e.g. Reviews->Select User), and not validated => Delete it
 					if ($object->isLoaded() && !$object->Validate() && ($id <= 0) ) {
 						$delete_ids[] = $id;
 					}
 				}
 
 				if ($delete_ids) {
 					$temp->DeleteItems($event->Prefix, $event->Special, $delete_ids);
 				}
 			}
 
 			$event->redirect_params = Array('opener'=>'u');
 		}
 
 
 		/**
 		 * Deletes all selected items.
 		 * Automatically recurse into sub-items using temp handler, and deletes sub-items
 		 * by calling its Delete method if sub-item has AutoDelete set to true in its config file
 		 *
 		 * @param kEvent $event
 		 */
 		function OnMassDelete(&$event)
 		{
 			if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
 				$event->status = erFAIL;
 				return;
 			}
 
 			$event->status=erSUCCESS;
 
 			$temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
 
 			$ids = $this->StoreSelectedIDs($event);
 
 			$event->setEventParam('ids', $ids);
 			$this->customProcessing($event, 'before');
 			$ids = $event->getEventParam('ids');
 
 			if($ids)
 			{
 				$temp->DeleteItems($event->Prefix, $event->Special, $ids);
 			}
 			$this->clearSelectedIDs($event);
 		}
 
 		/**
 		 * Sets window id (of first opened edit window) to temp mark in uls
 		 *
 		 * @param kEvent $event
 		 */
 		function setTempWindowID(&$event)
 		{
 			$prefixes = Array ($event->Prefix, $event->getPrefixSpecial(true));
 
 			foreach ($prefixes as $prefix) {
 				$mode = $this->Application->GetVar($prefix . '_mode');
 
 				if ($mode == 't') {
 					$wid = $this->Application->GetVar('m_wid');
 					$this->Application->SetVar(str_replace('_', '.', $prefix) . '_mode', 't' . $wid);
 					break;
 				}
 			}
 		}
 
 		/**
 		 * Prepare temp tables and populate it
 		 * with items selected in the grid
 		 *
 		 * @param kEvent $event
 		 */
 		function OnEdit(&$event)
 		{
 			$this->setTempWindowID($event);
 			$ids = $this->StoreSelectedIDs($event);
 
 			$var_name = $event->getPrefixSpecial().'_file_pending_actions'.$this->Application->GetVar('m_wid');
 			$this->Application->RemoveVar($var_name);
 
 			$changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
 			$this->Application->RemoveVar($changes_var_name);
 
 			$temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
 			/* @var $temp kTempTablesHandler */
 
 			$temp->PrepareEdit();
 
 			$event->SetRedirectParam('m_lang', $this->Application->GetDefaultLanguageId());
 			$event->SetRedirectParam($event->getPrefixSpecial() . '_id', array_shift($ids));
 			$event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial());
 		}
 
 		/**
 		 * Saves content of temp table into live and
 		 * redirects to event' default redirect (normally grid template)
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSave(&$event)
 		{
 			$event->CallSubEvent('OnPreSave');
 			if ($event->status == erSUCCESS) {
 				$skip_master = false;
 				$temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
 
 				$changes_var_name = $this->Prefix.'_changes_'.$this->Application->GetTopmostWid($this->Prefix);
 
 				if (!$this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
 					$live_ids = $temp->SaveEdit($event->getEventParam('master_ids') ? $event->getEventParam('master_ids') : Array());
 					if ($live_ids === false) {
 						// coping from table failed, because we have another coping process to same table, that wasn't finished
 						$event->status = erFAIL;
 						return ;
 					}
 
 					// Deleteing files scheduled for delete
 					$var_name = $event->getPrefixSpecial().'_file_pending_actions'.$this->Application->GetVar('m_wid');
 					$schedule = $this->Application->RecallVar($var_name);
 					$schedule = $schedule ? unserialize($schedule) : array();
 					foreach ($schedule as $data) {
 						if ($data['action'] == 'delete') {
 							unlink($data['file']);
 						}
 					}
 
 					if ($live_ids) {
 						// ensure, that newly created item ids are avalable as if they were selected from grid
 						// NOTE: only works if main item has subitems !!!
 						$this->StoreSelectedIDs($event, $live_ids);
 					}
 
 					$object =& $event->getObject();
 					/* @var $object kDBItem */
 
 					$this->SaveLoggedChanges($changes_var_name, $object->ShouldLogChanges());
 				}
 				else {
 					$event->status = erFAIL;
 				}
 
 				$this->clearSelectedIDs($event);
 
 				$event->redirect_params = Array('opener' => 'u');
 				$this->Application->RemoveVar($event->getPrefixSpecial().'_modified');
 
 				// all temp tables are deleted here => all after hooks should think, that it's live mode now
 				$this->Application->SetVar($event->Prefix.'_mode', '');
 			}
 		}
 
 		function SaveLoggedChanges($changes_var_name, $save = true)
 		{
 			// 1. get changes, that were made
 			$changes = $this->Application->RecallVar($changes_var_name);
 			$changes = $changes ? unserialize($changes) : Array ();
 			$this->Application->RemoveVar($changes_var_name);
 
 			if (!$changes) {
 				// no changes, skip processing
 				return ;
 			}
 
 			// TODO: 2. optimize change log records (replace multiple changes to same record with one change record)
 
 			$to_increment = Array ();
 
 			// 3. collect serials to reset based on foreign keys
 			foreach ($changes as $index => $rec) {
 				if (array_key_exists('DependentFields', $rec)) {
 
 					foreach ($rec['DependentFields'] as $field_name => $field_value) {
 						// will be "ci|ItemResourceId:345"
 						$to_increment[] = $rec['Prefix'] . '|' . $field_name . ':' . $field_value;
 
 						// also reset sub-item prefix general serial
 						$to_increment[] = $rec['Prefix'];
 					}
 
 					unset($changes[$index]['DependentFields']);
 				}
 
 				unset($changes[$index]['ParentId'], $changes[$index]['ParentPrefix']);
 			}
 
 			// 4. collect serials to reset based on changed ids
 			foreach ($changes as $change) {
 				$to_increment[] = $change['MasterPrefix'] . '|' . $change['MasterId'];
 
 				if ($change['MasterPrefix'] != $change['Prefix']) {
 					// also reset sub-item prefix general serial
 					$to_increment[] = $change['Prefix'];
 
 					// will be "ci|ItemResourceId"
 					$to_increment[] = $change['Prefix'] . '|' . $change['ItemId'];
 				}
 			}
 
 			// 5. reset serials collected before
 			$to_increment = array_unique($to_increment);
 			$this->Application->incrementCacheSerial($this->Prefix);
 
 			foreach ($to_increment as $to_increment_mixed) {
 				if (strpos($to_increment_mixed, '|') !== false) {
 					list ($to_increment_prefix, $to_increment_id) = explode('|', $to_increment_mixed, 2);
 					$this->Application->incrementCacheSerial($to_increment_prefix, $to_increment_id);
 				}
 				else {
 					$this->Application->incrementCacheSerial($to_increment_mixed);
 				}
 			}
 
 			// save changes to database
 			$sesion_log_id = $this->Application->RecallVar('_SessionLogId_');
 
 			if (!$save || !$sesion_log_id) {
 				// saving changes to database disabled OR related session log missing
 				return ;
 			}
 
 			$add_fields = Array (
 				'PortalUserId' => $this->Application->RecallVar('user_id'),
 				'SessionLogId' => $sesion_log_id,
 			);
 
 			$change_log_table = $this->Application->getUnitOption('change-log', 'TableName');
 
 			foreach ($changes as $rec) {
 				$this->Conn->doInsert(array_merge($rec, $add_fields), $change_log_table);
 			}
 
 			$this->Application->incrementCacheSerial('change-log');
 
 			$sql = 'UPDATE ' . $this->Application->getUnitOption('session-log', 'TableName') . '
 					SET AffectedItems = AffectedItems + ' . count($changes) . '
 					WHERE SessionLogId = ' . $sesion_log_id;
 			$this->Conn->Query($sql);
 
 			$this->Application->incrementCacheSerial('session-log');
 		}
 
 		/**
 		 * Cancels edit
 		 * Removes all temp tables and clears selected ids
 		 *
 		 * @param kEvent $event
 		 */
 		function OnCancelEdit(&$event)
 		{
 			$temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
 			$temp->CancelEdit();
 
 			$this->clearSelectedIDs($event);
 			$event->redirect_params = Array('opener'=>'u');
 			$this->Application->RemoveVar($event->getPrefixSpecial().'_modified');
 
 			$changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
 			$this->Application->RemoveVar($changes_var_name);
 		}
 
 
 		/**
 		 * Allows to determine if we are creating new item or editing already created item
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 */
 		function isNewItemCreate(&$event)
 		{
 			$object =& $event->getObject( Array ('raise_warnings' => 0) );
 			return !$object->IsLoaded();
 
 //			$item_id = $this->getPassedID($event);
 //			return ($item_id == '') ? true : false;
 		}
 
 		/**
 		 * Saves edited item into temp table
 		 * If there is no id, new item is created in temp table
 		 *
 		 * @param kEvent $event
 		 */
 		function OnPreSave(&$event)
 		{
 			//$event->redirect = false;
 			// if there is no id - it means we need to create an item
 			if (is_object($event->MasterEvent)) {
 				$event->MasterEvent->setEventParam('IsNew',false);
 			}
 
 			if ($this->isNewItemCreate($event)) {
 				$event->CallSubEvent('OnPreSaveCreated');
 				if (is_object($event->MasterEvent)) {
 					$event->MasterEvent->setEventParam('IsNew',true);
 				}
 				return;
 			}
 
 			$object =& $event->getObject( Array('skip_autoload' => true) );
 
 			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 			if ($items_info) {
 				foreach ($items_info as $id => $field_values) {
 					$object->SetDefaultValues();
 					$object->Load($id);
 	 				$object->SetFieldsFromHash($field_values);
 	 				$this->customProcessing($event, 'before');
 					if( $object->Update($id) )
 					{
 						$this->customProcessing($event, 'after');
 						$event->status=erSUCCESS;
 					}
 					else {
 						$event->status = erFAIL;
 						$event->redirect = false;
 						break;
 					}
 				}
 			}
 		}
 
 		/**
 		 * [HOOK] Saves subitem
 		 *
 		 * @param kEvent $event
 		 */
 		function OnPreSaveSubItem(&$event)
 		{
 			$not_created = $this->isNewItemCreate($event);
 
 			$event->CallSubEvent($not_created ? 'OnCreate' : 'OnUpdate');
 			if ($event->status == erSUCCESS) {
 				$object =& $event->getObject();
 				/* @var $object kDBItem */
 
 				$this->Application->SetVar($event->getPrefixSpecial() . '_id', $object->GetID());
 			}
 			else {
 				$event->MasterEvent->status = $event->status;
 			}
 
 			$event->SetRedirectParam('opener', 's');
 		}
 
 		/**
 		 * Saves edited item in temp table and loads
 		 * item with passed id in current template
 		 * Used in Prev/Next buttons
 		 *
 		 * @param kEvent $event
 		 */
 		function OnPreSaveAndGo(&$event)
 		{
 			$event->CallSubEvent('OnPreSave');
 
 			if ($event->status == erSUCCESS) {
 				$id = $this->Application->GetVar($event->getPrefixSpecial(true) . '_GoId');
 				$event->SetRedirectParam($event->getPrefixSpecial() . '_id', $id);
 			}
 		}
 
 		/**
 		 * Saves edited item in temp table and goes
 		 * to passed tabs, by redirecting to it with OnPreSave event
 		 *
 		 * @param kEvent $event
 		 */
 		function OnPreSaveAndGoToTab(&$event)
 		{
 			$event->CallSubEvent('OnPreSave');
 			if ($event->status==erSUCCESS) {
 				$event->redirect=$this->Application->GetVar($event->getPrefixSpecial(true).'_GoTab');
 			}
 		}
 
 		/**
 		 * Saves editable list and goes to passed tab,
 		 * by redirecting to it with empty event
 		 *
 		 * @param kEvent $event
 		 */
 		function OnUpdateAndGoToTab(&$event)
 		{
 			$event->setPseudoClass('_List');
 			$event->CallSubEvent('OnUpdate');
 			if ($event->status==erSUCCESS) {
 				$event->redirect=$this->Application->GetVar($event->getPrefixSpecial(true).'_GoTab');
 			}
 		}
 
 		/**
 		 * Prepare temp tables for creating new item
 		 * but does not create it. Actual create is
 		 * done in OnPreSaveCreated
 		 *
 		 * @param kEvent $event
 		 */
 		function OnPreCreate(&$event)
 		{
 			$this->setTempWindowID($event);
 			$this->clearSelectedIDs($event);
 			$this->Application->SetVar('m_lang', $this->Application->GetDefaultLanguageId());
 
 			$object =& $event->getObject( Array('skip_autoload' => true) );
 
 			$temp =& $this->Application->recallObject($event->Prefix.'_TempHandler', 'kTempTablesHandler');
 			$temp->PrepareEdit();
 
 			$object->setID(0);
 			$this->Application->SetVar($event->getPrefixSpecial().'_id', 0);
 			$this->Application->SetVar($event->getPrefixSpecial().'_PreCreate', 1);
 
 			$changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
 			$this->Application->RemoveVar($changes_var_name);
 
 			$event->redirect = false;
 		}
 
 		/**
 		 * Creates a new item in temp table and
 		 * stores item id in App vars and Session on succsess
 		 *
 		 * @param kEvent $event
 		 */
 		function OnPreSaveCreated(&$event)
 		{
 			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 			if($items_info) $field_values = array_shift($items_info);
 
 			$object =& $event->getObject( Array('skip_autoload' => true) );
 			$object->SetFieldsFromHash($field_values);
 
 			$this->customProcessing($event, 'before');
 
 			if( $object->Create() )
 			{
 				$this->customProcessing($event, 'after');
 				$event->redirect_params[$event->getPrefixSpecial(true).'_id'] = $object->GetId();
 				$event->status=erSUCCESS;
 			}
 			else
 			{
 				$event->status=erFAIL;
 				$event->redirect=false;
 				$object->setID(0);
 			}
 
 		}
 
 		function OnReset(&$event)
 		{
 			//do nothing - should reset :)
 			if ($this->isNewItemCreate($event)) {
 				// just reset id to 0 in case it was create
 				$object =& $event->getObject( Array('skip_autoload' => true) );
 				$object->setID(0);
 				$this->Application->SetVar($event->getPrefixSpecial().'_id',0);
 			}
 		}
 
 		/**
 		 * Apply same processing to each item beeing selected in grid
 		 *
 		 * @param kEvent $event
 		 * @access private
 		 */
 		function iterateItems(&$event)
 		{
 			if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
 				$event->status = erFAIL;
 				return;
 			}
 
 			$object =& $event->getObject( Array('skip_autoload' => true) );
 			$ids = $this->StoreSelectedIDs($event);
 
 			if ($ids) {
 				$status_field = array_shift( $this->Application->getUnitOption($event->Prefix,'StatusField') );
 				$order_field = $this->Application->getUnitOption($event->Prefix,'OrderField');
 
 				if (!$order_field) {
 					$order_field = 'Priority';
 				}
 
 				foreach ($ids as $id) {
 					$object->Load($id);
 
 					switch ($event->Name) {
 						case 'OnMassApprove':
 							$object->SetDBField($status_field, 1);
 							break;
 
 						case 'OnMassDecline':
 							$object->SetDBField($status_field, 0);
 							break;
 
 						case 'OnMassMoveUp':
 							$object->SetDBField($order_field, $object->GetDBField($order_field) + 1);
 							break;
 
 						case 'OnMassMoveDown':
 							$object->SetDBField($order_field, $object->GetDBField($order_field) - 1);
 							break;
 					}
 
 					if ($object->Update()) {
 						$event->status = erSUCCESS;
 					}
 					else {
 						$event->status = erFAIL;
 						$event->redirect = false;
 						break;
 					}
 				}
 			}
 
 			$this->clearSelectedIDs($event);
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 */
 		function OnMassClone(&$event)
 		{
 			if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
 				$event->status = erFAIL;
 				return;
 			}
 
 			$event->status = erSUCCESS;
 
 			$temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
 
 			$ids = $this->StoreSelectedIDs($event);
 
 			if ($ids) {
 				$temp->CloneItems($event->Prefix, $event->Special, $ids);
 			}
 
 			$this->clearSelectedIDs($event);
 		}
 
 		function check_array($records, $field, $value)
 		{
 			foreach ($records as $record) {
 				if ($record[$field] == $value) {
 					return true;
 				}
 			}
 			return false;
 		}
 
 		function OnPreSavePopup(&$event)
 		{
 			$object =& $event->getObject();
 			$this->RemoveRequiredFields($object);
 			$event->CallSubEvent('OnPreSave');
 
 			$this->finalizePopup($event);
 		}
 
 
 /* End of Edit events */
 
 		// III. Events that allow to put some code before and after Update,Load,Create and Delete methods of item
 
 		/**
 		 * Occurse before loading item, 'id' parameter
 		 * allows to get id of item beeing loaded
 		 *
 		 * @param kEvent $event
 		 * @access public
 		 */
 		function OnBeforeItemLoad(&$event)
 		{
 
 		}
 
 		/**
 		 * Occurse after loading item, 'id' parameter
 		 * allows to get id of item that was loaded
 		 *
 		 * @param kEvent $event
 		 * @access public
 		 */
 		function OnAfterItemLoad(&$event)
 		{
 
 		}
 
 		/**
 		 * Occurse before creating item
 		 *
 		 * @param kEvent $event
 		 * @access public
 		 */
 		function OnBeforeItemCreate(&$event)
 		{
 
 		}
 
 		/**
 		 * Occurse after creating item
 		 *
 		 * @param kEvent $event
 		 * @access public
 		 */
 		function OnAfterItemCreate(&$event)
 		{
 
 		}
 
 		/**
 		 * Occurse before updating item
 		 *
 		 * @param kEvent $event
 		 * @access public
 		 */
 		function OnBeforeItemUpdate(&$event)
 		{
 
 		}
 
 		/**
 		 * Occurse after updating item
 		 *
 		 * @param kEvent $event
 		 * @access public
 		 */
 		function OnAfterItemUpdate(&$event)
 		{
 
 		}
 
 		/**
 		 * Occurse before deleting item, id of item beeing
 		 * deleted is stored as 'id' event param
 		 *
 		 * @param kEvent $event
 		 * @access public
 		 */
 		function OnBeforeItemDelete(&$event)
 		{
 
 		}
 
 		/**
 		 * Occurse after deleting item, id of deleted item
 		 * is stored as 'id' param of event
 		 *
 		 * @param kEvent $event
 		 * @access public
 		 */
 		function OnAfterItemDelete(&$event)
 		{
 
 		}
 
 		/**
 		 * Occurs before validation attempt
 		 *
 		 * @param kEvent $event
 		 */
 		function OnBeforeItemValidate(&$event)
 		{
 
 		}
 
 		/**
 		 * Occurs after successful item validation
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAfterItemValidate(&$event)
 		{
 
 		}
 
 		/**
 		 * Occures after an item has been copied to temp
 		 * Id of copied item is passed as event' 'id' param
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAfterCopyToTemp(&$event)
 		{
 
 		}
 
 		/**
 		 * Occures before an item is deleted from live table when copying from temp
 		 * (temp handler deleted all items from live and then copy over all items from temp)
 		 * Id of item being deleted is passed as event' 'id' param
 		 *
 		 * @param kEvent $event
 		 */
 		function OnBeforeDeleteFromLive(&$event)
 		{
 
 		}
 
 		/**
 		 * Occures before an item is copied to live table (after all foreign keys have been updated)
 		 * Id of item being copied is passed as event' 'id' param
 		 *
 		 * @param kEvent $event
 		 */
 		function OnBeforeCopyToLive(&$event)
 		{
 
 		}
 
 		/**
 		 * !!! NOT FULLY IMPLEMENTED - SEE TEMP HANDLER COMMENTS (search by event name)!!!
 		 * Occures after an item has been copied to live table
 		 * Id of copied item is passed as event' 'id' param
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAfterCopyToLive(&$event)
 		{
 
 		}
 
 		/**
 		 * Occures before an item is cloneded
 		 * Id of ORIGINAL item is passed as event' 'id' param
 		 * Do not call object' Update method in this event, just set needed fields!
 		 *
 		 * @param kEvent $event
 		 */
 		function OnBeforeClone(&$event)
 		{
 
 		}
 
 		/**
 		 * Occures after an item has been cloned
 		 * Id of newly created item is passed as event' 'id' param
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAfterClone(&$event)
 		{
 
 		}
 
 		/**
 		 * Occures after list is queried
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAfterListQuery(&$event)
 		{
 
 		}
 
 		/**
 		 * Ensures that popup will be closed automatically
 		 * and parent window will be refreshed with template
 		 * passed
 		 *
 		 * @param kEvent $event
 		 * @access public
 		 */
 		function finalizePopup(&$event)
 		{
 			$event->SetRedirectParam('opener', 'u');
 		}
 
 		/**
 		 * Create search filters based on search query
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		function OnSearch(&$event)
 		{
 			$event->setPseudoClass('_List');
 
 			$search_helper =& $this->Application->recallObject('SearchHelper');
 			/* @var $search_helper kSearchHelper */
 
 			$search_helper->performSearch($event);
 		}
 
 		/**
 		 * Clear search keywords
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		function OnSearchReset(&$event)
 		{
 			$search_helper =& $this->Application->recallObject('SearchHelper');
 			/* @var $search_helper kSearchHelper */
 
 			$search_helper->resetSearch($event);
 		}
 
 		/**
 		 * Set's new filter value (filter_id meaning from config)
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSetFilter(&$event)
 		{
 			$filter_id = $this->Application->GetVar('filter_id');
 			$filter_value = $this->Application->GetVar('filter_value');
 
 			$view_filter = $this->Application->RecallVar($event->getPrefixSpecial().'_view_filter');
 			$view_filter = $view_filter ? unserialize($view_filter) : Array();
 
 			$view_filter[$filter_id] = $filter_value;
 
 			$this->Application->StoreVar( $event->getPrefixSpecial().'_view_filter', serialize($view_filter) );
 		}
 
 		function OnSetFilterPattern(&$event)
 		{
 			$filters = $this->Application->GetVar($event->getPrefixSpecial(true).'_filters');
 			if (!$filters) return ;
 
 			$view_filter = $this->Application->RecallVar($event->getPrefixSpecial().'_view_filter');
 			$view_filter = $view_filter ? unserialize($view_filter) : Array();
 
 			$filters = explode(',', $filters);
 			foreach ($filters as $a_filter) {
 				list($id, $value) = explode('=', $a_filter);
 				$view_filter[$id] = $value;
 			}
 			$this->Application->StoreVar( $event->getPrefixSpecial().'_view_filter', serialize($view_filter) );
 			$event->redirect = false;
 		}
 
 		/**
 		 * Add/Remove all filters applied to list from "View" menu
 		 *
 		 * @param kEvent $event
 		 */
 		function FilterAction(&$event)
 		{
 			$view_filter = Array();
 			$filter_menu = $this->Application->getUnitOption($event->Prefix,'FilterMenu');
 			switch ($event->Name)
 			{
 				case 'OnRemoveFilters':
 					$filter_value = 1;
 					break;
 
 				case 'OnApplyFilters':
 					$filter_value = 0;
 					break;
 			}
 
 			foreach($filter_menu['Filters'] as $filter_key => $filter_params)
 			{
 				if(!$filter_params) continue;
 				$view_filter[$filter_key] = $filter_value;
 			}
 			$this->Application->StoreVar( $event->getPrefixSpecial().'_view_filter', serialize($view_filter) );
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 */
 		function OnPreSaveAndOpenTranslator(&$event)
 		{
 			$this->Application->SetVar('allow_translation', true);
 			$object =& $event->getObject();
 			$this->RemoveRequiredFields($object);
 			$event->CallSubEvent('OnPreSave');
 
 			if ($event->status == erSUCCESS) {
 
 				$resource_id = $this->Application->GetVar('translator_resource_id');
 				if ($resource_id) {
 					$t_prefixes = explode(',', $this->Application->GetVar('translator_prefixes'));
 
 					$cdata =& $this->Application->recallObject($t_prefixes[1], null, Array('skip_autoload' => true));
 					$cdata->Load($resource_id, 'ResourceId');
 					if (!$cdata->isLoaded()) {
 						$cdata->SetDBField('ResourceId', $resource_id);
 						$cdata->Create();
 					}
 					$this->Application->SetVar($cdata->getPrefixSpecial().'_id', $cdata->GetID());
 				}
 
 				$event->redirect = $this->Application->GetVar('translator_t');
 				$event->redirect_params = Array('pass'=>'all,trans,'.$this->Application->GetVar('translator_prefixes'),
 							$event->getPrefixSpecial(true).'_id' => $object->GetID(),
 							'trans_event'		=>	'OnLoad',
 							'trans_prefix'		=>	$this->Application->GetVar('translator_prefixes'),
 							'trans_field' 		=>	$this->Application->GetVar('translator_field'),
 							'trans_multi_line'	=>	$this->Application->GetVar('translator_multi_line'),
 							);
 
 				// 1. SAVE LAST TEMPLATE TO SESSION (really needed here, because of tweaky redirect)
 				$last_template = $this->Application->RecallVar('last_template');
 				preg_match('/index4\.php\|'.$this->Application->GetSID().'-(.*):/U', $last_template, $rets);
 				$this->Application->StoreVar('return_template', $this->Application->GetVar('t'));
 			}
 		}
 
 		function RemoveRequiredFields(&$object)
 		{
 			// making all field non-required to achieve successful presave
 			foreach($object->Fields as $field => $options)
 			{
 				if(isset($options['required']))
 				{
 					unset($object->Fields[$field]['required']);
 				}
 			}
 		}
 
 		/**
 		 * Saves selected user in needed field
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSelectUser(&$event)
 		{
 			$items_info = $this->Application->GetVar('u');
 			if ($items_info) {
 				$user_id = array_shift( array_keys($items_info) );
 
 				$object =& $event->getObject();
 				$this->RemoveRequiredFields($object);
 
 				$is_new = !$object->isLoaded();
 				$is_main = substr($this->Application->GetVar($event->Prefix.'_mode'), 0, 1) == 't';
 
 				if ($is_new) {
 					$new_event = $is_main ? 'OnPreCreate' : 'OnNew';
 					$event->CallSubEvent($new_event);
 					$event->redirect = true;
 				}
 
 				$object->SetDBField($this->Application->RecallVar('dst_field'), $user_id);
 
 				if ($is_new) {
 					$object->Create();
 					if (!$is_main && $object->IsTempTable()) {
 						$object->setTempID();
 					}
 				}
 				else {
 					$object->Update();
 				}
 			}
 
 			$event->SetRedirectParam($event->getPrefixSpecial().'_id', $object->GetID());
 			$this->finalizePopup($event);
 		}
 
 
 /** EXPORT RELATED **/
 
 		/**
 		 * Shows export dialog
 		 *
 		 * @param kEvent $event
 		 */
 		function OnExport(&$event)
 		{
 			$selected_ids = $this->StoreSelectedIDs($event);
 
 			if (implode(',', $selected_ids) == '') {
 				// K4 fix when no ids found bad selected ids array is formed
 				$selected_ids = false;
 			}
 
 			$this->Application->StoreVar($event->Prefix.'_export_ids', $selected_ids ? implode(',', $selected_ids) : '' );
 
 			$this->Application->LinkVar('export_finish_t');
 			$this->Application->LinkVar('export_progress_t');
 			$this->Application->StoreVar('export_oroginal_special', $event->Special);
 
 			$export_helper =& $this->Application->recallObject('CatItemExportHelper');
 
 			/*list ($index_file, $env) = explode('|', $this->Application->RecallVar('last_template'));
 			$finish_url = $this->Application->BaseURL('/admin').$index_file.'?'.ENV_VAR_NAME.'='.$env;
 			$this->Application->StoreVar('export_finish_url', $finish_url);*/
 
 			$redirect_params = Array (
 				$this->Prefix . '.export_event' => 'OnNew',
 				'pass' => 'all,' . $this->Prefix . '.export'
 			);
 
 			$event->setRedirectParams($redirect_params);
 		}
 
 		/**
 		 * Apply some special processing to
 		 * object beeing recalled before using
 		 * it in other events that call prepareObject
 		 *
 		 * @param Object $object
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		function prepareObject(&$object, &$event)
 		{
 			if ($event->Special == 'export' || $event->Special == 'import')
 			{
 				$export_helper =& $this->Application->recallObject('CatItemExportHelper');
 				/* @var $export_helper kCatDBItemExportHelper */
 
 				$export_helper->prepareExportColumns($event);
 			}
 		}
 
 		/**
 		 * Returns specific to each item type columns only
 		 *
 		 * @param kEvent $event
 		 * @return Array
 		 */
 		function getCustomExportColumns(&$event)
 		{
 			return Array();
 		}
 
 		/**
 		 * Export form validation & processing
 		 *
 		 * @param kEvent $event
 		 */
 		function OnExportBegin(&$event)
 		{
 			$export_helper =& $this->Application->recallObject('CatItemExportHelper');
 			/* @var $export_helper kCatDBItemExportHelper */
 			$export_helper->OnExportBegin($event);
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 */
 		function OnExportCancel(&$event)
 		{
 			$this->OnGoBack($event);
 		}
 
 		/**
 		 * Allows configuring export options
 		 *
 		 * @param kEvent $event
 		 */
 		function OnBeforeExportBegin(&$event)
 		{
 
 		}
 
 		function OnDeleteExportPreset(&$event)
 		{
 			$object =& $event->GetObject();
 
 			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 			if($items_info)
 			{
 				list($id,$field_values) = each($items_info);
 				$preset_key = $field_values['ExportPresets'];
 
 				$export_settings = $this->Application->RecallPersistentVar('export_settings');
 				if (!$export_settings) return ;
 				$export_settings = unserialize($export_settings);
 				if (!isset($export_settings[$event->Prefix])) return ;
 
 				$to_delete = '';
 				$export_presets = array(''=>'');
 				foreach ($export_settings[$event->Prefix] as $key => $val) {
 					if (implode('|', $val['ExportColumns']) == $preset_key) {
 						$to_delete = $key;
 						break;
 					}
 				}
 				if ($to_delete) {
 					unset($export_settings[$event->Prefix][$to_delete]);
 					$this->Application->StorePersistentVar('export_settings', serialize($export_settings));
 				}
 			}
 		}
 
 		/**
 		 * Saves changes & changes language
 		 *
 		 * @param kEvent $event
 		 */
 		function OnPreSaveAndChangeLanguage(&$event)
 		{
 			if ($this->UseTempTables($event)) {
 				$event->CallSubEvent('OnPreSave');
 			}
 
 			if ($event->status == erSUCCESS) {
 				$this->Application->SetVar('m_lang', $this->Application->GetVar('language'));
 
 				$data = $this->Application->GetVar('st_id');
 
 				if ($data) {
 					$event->SetRedirectParam('st_id', $data);
 				}
 			}
 		}
 
 		/**
 		 * Used to save files uploaded via swfuploader
 		 *
 		 * @param kEvent $event
 		 */
 		function OnUploadFile(&$event)
 		{
 			$event->status = erSTOP;
 			define('DBG_SKIP_REPORTING', 1);
 			echo "Flash requires that we output something or it won't fire the uploadSuccess event";
 
 			if (!$this->Application->HttpQuery->Post) {
 				// Variables {field, id, flashsid} are always submitted through POST!
 				// When file size is larger, then "upload_max_filesize" (in php.ini),
 				// then theese variables also are not submitted -> handle such case.
 				header('HTTP/1.0 413 File size exceeds allowed limit');
 				return ;
 			}
 
 			if (!$this->_checkFlashUploaderPermission($event)) {
 				// 403 Forbidden
 				header('HTTP/1.0 403 You don\'t have permissions to upload');
 				return ;
 			}
 
 			$value = $this->Application->GetVar('Filedata');
 
 			if (!$value || ($value['error'] != UPLOAD_ERR_OK)) {
 				// 413 Request Entity Too Large (file uploads disabled OR uploaded file was
 				// to large for web server to accept, see "upload_max_filesize" in php.ini)
 				header('HTTP/1.0 413 File size exceeds allowed limit');
 				return ;
 			}
 
 			$tmp_path = WRITEABLE . '/tmp/';
 			$fname = $value['name'];
 			$id = $this->Application->GetVar('id');
 			if ($id) {
 				$fname = $id.'_'.$fname;
 			}
 
 			$fields = $this->Application->getUnitOption($event->Prefix, 'Fields');
 			$upload_dir = $fields[ $this->Application->GetVar('field') ]['upload_dir'];
 
 			if (!is_writable($tmp_path) || !is_writable(FULL_PATH . $upload_dir)) {
 				// 500 Internal Server Error
 				// check both temp and live upload directory
 				header('HTTP/1.0 500 Write permissions not set on the server');
 				return ;
 			}
 
 			move_uploaded_file($value['tmp_name'], $tmp_path.$fname);
 		}
 
 		/**
 		 * Checks, that flash uploader is allowed to perform upload
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 */
 		function _checkFlashUploaderPermission(&$event)
 		{
 			// Flash uploader does NOT send correct cookies, so we need to make our own check
 			$cookie_name = 'adm_' . $this->Application->ConfigValue('SessionCookieName');
 			$this->Application->HttpQuery->Cookie['cookies_on'] = 1;
 			$this->Application->HttpQuery->Cookie[$cookie_name] = $this->Application->GetVar('flashsid');
 
 			// this prevents session from auto-expiring when KeepSessionOnBrowserClose & FireFox is used
 			$this->Application->HttpQuery->Cookie[$cookie_name . '_live'] = $this->Application->GetVar('flashsid');
 
 			$admin_ses =& $this->Application->recallObject('Session.admin');
 			/* @var $admin_ses Session */
 
 			if ($admin_ses->RecallVar('user_id') == -1) {
 				return true;
 			}
 
 			$backup_user_id = $this->Application->RecallVar('user_id'); // 1. backup user
 			$this->Application->StoreVar('user_id', $admin_ses->RecallVar('user_id')); // 2. fake user_id
 
 			$check_event = new kEvent($event->getPrefixSpecial() . ':OnProcessSelected'); // 3. event, that have "add|edit" rule
 			$check_event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true));
 
 			$allowed_to_upload = $this->CheckPermission($check_event); // 4. check permission
 
 			$this->Application->StoreVar('user_id', $backup_user_id); // 5. restore user id
 
 			return $allowed_to_upload;
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 */
 		function OnDeleteFile(&$event)
 		{
 			$event->status = erSTOP;
 
 			if (strpos($this->Application->GetVar('file'), '../') !== false) {
 				return ;
 			}
 
 			$object =& $event->getObject( Array ('skip_autoload' => true) );
 			$options = $object->GetFieldOptions( $this->Application->GetVar('field') );
 
 			$var_name = $event->getPrefixSpecial() . '_file_pending_actions' . $this->Application->GetVar('m_wid');
 			$schedule = $this->Application->RecallVar($var_name);
 			$schedule = $schedule ? unserialize($schedule) : Array ();
 			$schedule[] = Array ('action' => 'delete', 'file' => $path = FULL_PATH . $options['upload_dir'] . $this->Application->GetVar('file'));
 			$this->Application->StoreVar($var_name, serialize($schedule));
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 */
 		function OnViewFile(&$event)
 		{
 			$file = $this->Application->GetVar('file');
 			if ((strpos($file, '../') !== false) || (trim($file) !== $file)) {
 				// when relative paths or special chars are found template names from url, then it's hacking attempt
 				return ;
 			}
 
 			if ($this->Application->GetVar('tmp')) {
 				$path = WRITEABLE . '/tmp/' . $this->Application->GetVar('id') . '_' . $this->Application->GetVar('file');
 			}
 			else {
 				$object =& $event->getObject(array('skip_autoload'=>true));
 				$options = $object->GetFieldOptions($this->Application->GetVar('field'));
 
 				$path = FULL_PATH.$options['upload_dir'].$file;
 			}
 
 			$path = str_replace('/', DIRECTORY_SEPARATOR, $path);
 
 			if (!file_exists($path)) {
 				exit;
 			}
 
 			$type = mime_content_type($path);
 
 			header('Content-Length: '.filesize($path));
 			header('Content-Type: '.$type);
 
 			safeDefine('DBG_SKIP_REPORTING',1);
 
 			readfile($path);
 			exit();
 		}
 
 		/**
 		 * Validates MInput control fields
 		 *
 		 * @param kEvent $event
 		 */
 		function OnValidateMInputFields(&$event)
 		{
 			$minput_helper =& $this->Application->recallObject('MInputHelper');
 			/* @var $minput_helper MInputHelper */
 
 			$minput_helper->OnValidateMInputFields($event);
 		}
 
 		/**
 		 * Returns auto-complete values for ajax-dropdown
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSuggestValues(&$event)
 		{
 			if (!$this->Application->isAdminUser) {
 				// very careful here, because this event allows to
 				// view every object field -> limit only to logged-in admins
 				return ;
 			}
 
 			$event->status = erSTOP;
 
 			$field = $this->Application->GetVar('field');
 			$cur_value = $this->Application->GetVar('cur_value');
 
 			$object =& $event->getObject();
 
 			if (!$field || !$cur_value || !array_key_exists($field, $object->Fields)) {
 				return ;
 			}
 
 			$limit = $this->Application->GetVar('limit');
 			if (!$limit) {
 				$limit = 20;
 			}
 
 			$sql = 'SELECT DISTINCT '.$field.'
 					FROM '.$object->TableName.'
 					WHERE '.$field.' LIKE '.$this->Conn->qstr($cur_value.'%').'
 					ORDER BY '.$field.'
 					LIMIT 0,' . $limit;
 			$data = $this->Conn->GetCol($sql);
 
 			$this->Application->XMLHeader();
 
 			echo '<suggestions>';
 
 			foreach ($data as $item) {
 				echo '<item>' . htmlspecialchars($item) . '</item>';
 			}
 
 			echo '</suggestions>';
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSaveWidths(&$event)
 		{
 			$event->status = erSTOP;
 
 			$lang =& $this->Application->recallObject('lang.current');
 //			header('Content-type: text/xml; charset='.$lang->GetDBField('Charset'));
 
 			$picker_helper =& $this->Application->RecallObject('ColumnPickerHelper');
 			/* @var $picker_helper kColumnPickerHelper */
 			$picker_helper->PreparePicker($event->getPrefixSpecial(), $this->Application->GetVar('grid_name'));
 
 			$picker_helper->SaveWidths($event->getPrefixSpecial(), $this->Application->GetVar('widths'));
 
 			echo 'OK';
 		}
 
 		/**
 		 * Called from CSV import script after item fields
 		 * are set and validated, but before actual item create/update.
 		 * If event status is erSUCCESS, line will be imported,
 		 * else it will not be imported but added to skipped lines
 		 * and displayed in the end of import.
 		 * Event status is preset from import script.
 		 *
 		 * @param kEvent $event
 		 */
 		function OnBeforeCSVLineImport(&$event)
 		{
 			// abstract, for hooking
 		}
 
 	}
\ No newline at end of file