Index: branches/5.2.x/core/units/admin/admin_events_handler.php
===================================================================
--- branches/5.2.x/core/units/admin/admin_events_handler.php	(revision 16656)
+++ branches/5.2.x/core/units/admin/admin_events_handler.php	(revision 16657)
@@ -1,1290 +1,1305 @@
 <?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!');
 
 class AdminEventsHandler extends kDBEventHandler {
 
 	/**
 	 * Allows to override standard permission mapping
 	 *
 	 * @return void
 	 * @access protected
 	 * @see kEventHandler::$permMapping
 	 */
 	protected function mapPermissions()
 	{
 		parent::mapPermissions();
 
 		$permissions = Array (
 			'OnSaveColumns' => Array ('self' => true),
 			'OnGetPopupSize' => Array ('self' => true),
 			'OnClosePopup' => Array ('self' => true),
 			'OnSaveSetting' => Array ('self' => true),
 			'OnDropTempTablesByWID' => Array ('self' => true),
 			'OnProcessSelected' => Array ('self' => true), // allow CSV import file upload
 		);
 
 		$this->permMapping = array_merge($this->permMapping, $permissions);
 	}
 
 	/**
 	 * Checks user permission to execute given $event
 	 *
 	 * @param kEvent $event
 	 * @return bool
 	 * @access public
 	 */
 	public function CheckPermission(kEvent $event)
 	{
 		$perm_value = null;
 
 		$system_events = array(
 			'OnResetModRwCache', 'OnResetSections', 'OnResetConfigsCache', 'OnResetParsedData', 'OnResetMemcache',
 			'OnDeleteCompiledTemplates', 'OnCompileTemplates', 'OnGenerateTableStructure', 'OnSynchronizeDBRevisions',
 			'OnDeploy', 'OnRebuildThemes', 'OnDumpAssets', 'OnCheckPrefixConfig', 'OnMemoryCacheGet',
 			'OnMemoryCacheSet',
 		);
 
 		if ( in_array($event->Name, $system_events) ) {
 			// events from "Tools -> System Tools" section are controlled via that section "edit" permission
 			$perm_value = /*$this->Application->isDebugMode() ||*/ $this->Application->CheckPermission($event->getSection() . '.edit');
 		}
 
 		$tools_events = Array (
 			'OnBackup' => 'in-portal:backup.view',
 			'OnBackupProgress' => 'in-portal:backup.view',
 			'OnDeleteBackup' => 'in-portal:backup.view',
 			'OnBackupCancel' => 'in-portal:backup.view',
 
 			'OnRestore' => 'in-portal:restore.view',
 			'OnRestoreProgress' => 'in-portal:restore.view',
 			'OnRestoreCancel' => 'in-portal:backup.view',
 
 			'OnSqlQuery' => 'in-portal:sql_query.view',
 		);
 
 		if ( array_key_exists($event->Name, $tools_events) ) {
 			$perm_value = $this->Application->CheckPermission($tools_events[$event->Name]);
 		}
 
 		if ( $event->Name == 'OnSaveMenuFrameWidth' ) {
 			$perm_value = $this->Application->isAdminUser;
 		}
 
 		/** @var kPermissionsHelper $perm_helper */
 		$perm_helper = $this->Application->recallObject('PermissionsHelper');
 
 		$csv_events = Array ('OnCSVImportBegin', 'OnCSVImportStep', 'OnExportCSV', 'OnGetCSV');
 
 		if ( in_array($event->Name, $csv_events) ) {
 			/** @var kCSVHelper $csv_helper */
 			$csv_helper = $this->Application->recallObject('CSVHelper');
 
 			$prefix = $csv_helper->getPrefix(stripos($event->Name, 'import') !== false);
 
 			$perm_mapping = Array (
 				'OnCSVImportBegin' => 'OnProcessSelected',
 				'OnCSVImportStep' => 'OnProcessSelected',
 				'OnExportCSV' => 'OnLoad',
 				'OnGetCSV' => 'OnLoad',
 			);
 
 			$tmp_event = new kEvent($prefix . ':' . $perm_mapping[$event->Name] );
 			$perm_value = $perm_helper->CheckEventPermission($tmp_event, $this->permMapping);
 		}
 
 		if ( isset($perm_value) ) {
 			return $perm_helper->finalizePermissionCheck($event, $perm_value);
 		}
 
 		return parent::CheckPermission($event);
 	}
 
 	/**
 	 * Reset mod-rewrite url cache
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnResetModRwCache(kEvent $event)
 	{
 		if ( $this->Application->GetVar('ajax') == 'yes' ) {
 			$event->status = kEvent::erSTOP;
 		}
 
 		$this->Conn->Query('DELETE FROM ' . TABLE_PREFIX . 'CachedUrls');
 
 		$event->SetRedirectParam('action_completed', 1);
 	}
 
 	/**
 	 * Resets tree section cache and refreshes admin section tree
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnResetSections(kEvent $event)
 	{
 		if ($this->Application->GetVar('ajax') == 'yes') {
 			$event->status = kEvent::erSTOP;
 		}
 
 		if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
 			$this->Application->rebuildCache('master:sections_parsed', kCache::REBUILD_LATER, CacheSettings::$sectionsParsedRebuildTime);
 		}
 		else {
 			$this->Application->rebuildDBCache('sections_parsed', kCache::REBUILD_LATER, CacheSettings::$sectionsParsedRebuildTime);
 		}
 
 		$event->SetRedirectParam('refresh_tree', 1);
 		$event->SetRedirectParam('action_completed', 1);
 	}
 
 	/**
 	 * Resets unit config cache
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnResetConfigsCache(kEvent $event)
 	{
 		if ( $this->Application->GetVar('ajax') == 'yes' ) {
 			$event->status = kEvent::erSTOP;
 		}
 
 		if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) {
 			$this->Application->rebuildCache('master:config_files', kCache::REBUILD_LATER, CacheSettings::$unitCacheRebuildTime);
 		}
 		else {
 			$this->Application->rebuildDBCache('config_files', kCache::REBUILD_LATER, CacheSettings::$unitCacheRebuildTime);
 		}
 
 		$this->OnResetParsedData($event);
 
 		/** @var SkinHelper $skin_helper */
 		$skin_helper = $this->Application->recallObject('SkinHelper');
 
 		$skin_helper->deleteCompiled();
 	}
 
 	/**
 	 * Resets parsed data from unit configs
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnResetParsedData(kEvent $event)
 	{
 		if ( $this->Application->GetVar('ajax') == 'yes' ) {
 			$event->status = kEvent::erSTOP;
 		}
 
 		$this->Application->DeleteUnitCache();
 
 		if ( $this->Application->GetVar('validate_configs') ) {
 			$event->SetRedirectParam('validate_configs', 1);
 		}
 
 		$event->SetRedirectParam('action_completed', 1);
 	}
 
 	/**
 	 * Resets memory cache
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnResetMemcache(kEvent $event)
 	{
 		if ($this->Application->GetVar('ajax') == 'yes') {
 			$event->status = kEvent::erSTOP;
 		}
 
 		$this->Application->resetCache();
 		$event->SetRedirectParam('action_completed', 1);
 	}
 
 	/**
 	 * Compiles all templates (with a progress bar)
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnCompileTemplates(kEvent $event)
 	{
 		/** @var NParserCompiler $compiler */
 		$compiler = $this->Application->recallObject('NParserCompiler');
 
 		$compiler->CompileTemplatesStep();
 		$event->status = kEvent::erSTOP;
 	}
 
 	/**
 	 * Deletes all compiled templates
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnDeleteCompiledTemplates(kEvent $event)
 	{
 		if ( $this->Application->GetVar('ajax') == 'yes' ) {
 			$event->status = kEvent::erSTOP;
 		}
 
 		$base_path = WRITEABLE . DIRECTORY_SEPARATOR . 'cache';
 
 		// delete debugger reports
 		$debugger_reports = glob(RESTRICTED . '/debug_@*@.txt');
 
 		if ( $debugger_reports ) {
 			foreach ($debugger_reports as $debugger_report) {
 				unlink($debugger_report);
 			}
 		}
 
 		$this->_deleteCompiledTemplates($base_path);
 		$event->SetRedirectParam('action_completed', 1);
 	}
 
 	/**
 	 * Deletes compiled templates in a given folder
 	 *
 	 * @param string $folder
 	 * @param bool $unlink_folder
 	 * @return void
 	 * @access protected
 	 */
 	protected function _deleteCompiledTemplates($folder, $unlink_folder = false)
 	{
 		$sub_folders = glob($folder . '/*', GLOB_ONLYDIR);
 
 		if ( is_array($sub_folders) ) {
 			foreach ($sub_folders as $sub_folder) {
 				$this->_deleteCompiledTemplates($sub_folder, true);
 			}
 		}
 
 		$files = glob($folder . '/*.php');
 
 		if ( is_array($files) ) {
 			foreach ($files as $file) {
 				unlink($file);
 			}
 		}
 
 		if ( $unlink_folder ) {
 			rmdir($folder);
 		}
 	}
 
 	/**
 	 * Generates structure for specified table
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnGenerateTableStructure(kEvent $event)
 	{
 		$types_hash = Array (
 			'string'	=>	'varchar|text|mediumtext|longtext|date|datetime|time|timestamp|char|year|enum|set',
 			'int'		=>	'smallint|mediumint|int|bigint|tinyint',
 			'float'		=>	'float|double|decimal',
 		);
 
 		$table_name = $this->Application->GetVar('table_name');
 		if ( !$table_name ) {
 			echo 'error: no table name specified';
 			return;
 		}
 
 		if ( TABLE_PREFIX && !preg_match('/^' . preg_quote(TABLE_PREFIX, '/') . '(.*)/', $table_name) && (strtolower($table_name) != $table_name) ) {
 			// table name without prefix, then add it (don't affect K3 tables named in lowercase)
 			$table_name = TABLE_PREFIX . $table_name;
 		}
 
 		if ( !$this->Conn->TableFound($table_name) ) {
 			// table with prefix doesn't exist, assume that just config prefix passed -> resolve table name from it
 			$prefix = preg_replace('/^' . preg_quote(TABLE_PREFIX, '/') . '/', '', $table_name);
 			if ( $this->Application->prefixRegistred($prefix) ) {
 				// when prefix is found -> use it's table (don't affect K3 tables named in lowecase)
 				$table_name = $this->Application->getUnitOption($prefix, 'TableName');
 			}
 		}
 
 		$table_info = $this->Conn->Query('DESCRIBE '.$table_name);
 
 		// 1. prepare config keys
 		$grids = Array (
 			'Default' => Array (
 				'Icons' => Array ('default' => 'icon16_item.png'),
 				'Fields' => Array (),
 			)
 		);
 
 		$grids_fields = Array();
 
 		$id_field = '';
 		$fields = Array ();
 		$float_types = Array ('float', 'double', 'numeric');
 		foreach ($table_info as $field_info) {
 			if ( preg_match('/l[\d]+_.*/', $field_info['Field']) ) {
 				// don't put multilingual fields in config
 				continue;
 			}
 
 			$field_options = Array ();
 
 			if ( $field_info['Key'] == 'PRI' ) {
 				if ( $field_info['Field'] == 'Id' ) {
 					$grid_col_options = Array ('filter_block' => 'grid_range_filter', 'width' => 80);
 				}
 				else {
 					$grid_col_options = Array ('title' => 'column:la_fld_Id', 'filter_block' => 'grid_range_filter', 'width' => 80);
 				}
 			}
 			else {
 				$grid_col_options = Array ('filter_block' => 'grid_like_filter');
 			}
 
 			// 1. get php field type by mysql field type
 			foreach ($types_hash as $php_type => $db_types) {
 				if ( preg_match('/' . $db_types . '/', $field_info['Type']) ) {
 					$field_options['type'] = $php_type;
 					break;
 				}
 			}
 
 			// 2. get field default value
 			$default_value = $field_info['Default'];
 			$not_null = $field_info['Null'] != 'YES';
 
 			if ( is_numeric($default_value) ) {
 				$default_value = preg_match('/[\.,]/', $default_value) ? (float)$default_value : (int)$default_value;
 			}
 
 			if ( is_null($default_value) && $not_null ) {
 				$default_value = $field_options['type'] == 'string' ? '' : 0;
 			}
 
 			if ( in_array($php_type, $float_types) ) {
 				// this is float number
 				if ( preg_match('/' . $db_types . '\([\d]+,([\d]+)\)/i', $field_info['Type'], $regs) ) {
 					// size is described in structure -> add formatter
 					$field_options['formatter'] = 'kFormatter';
 					$field_options['format'] = '%01.' . $regs[1] . 'f';
 
 					if ( $not_null ) {
 						// null fields, will most likely have NULL as default value
 						$default_value = 0;
 					}
 				}
 				elseif ( $not_null ) {
 					// no size information, just convert to float
 					// null fields, will most likely have NULL as default value
 					$default_value = (float)$default_value;
 				}
 			}
 
 			if ( preg_match('/varchar\(([\d]+)\)/i', $field_info['Type'], $regs) ) {
 				$field_options['max_len'] = (int)$regs[1];
 			}
 
 			if ( preg_match('/tinyint\([\d]+\)/i', $field_info['Type']) ) {
 				$field_options['formatter'] = 'kOptionsFormatter';
 				$field_options['options'] = Array (1 => 'la_Yes', 0 => 'la_No');
 				$field_options['use_phrases'] = 1;
 				$grid_col_options['filter_block'] = 'grid_options_filter';
 			}
 
 			if ( $not_null ) {
 				$field_options['not_null'] = 1;
 			}
 
 			if ( $field_info['Key'] == 'PRI' ) {
 				$default_value = 0;
 				$id_field = $field_info['Field'];
 			}
 
 			if ( $php_type == 'int' && !$not_null ) {
 				// numeric null field
 				if ( preg_match('/(On|Date)$/', $field_info['Field']) || $field_info['Field'] == 'Modified' ) {
 					$field_options['formatter'] = 'kDateFormatter';
 					$grid_col_options['filter_block'] = 'grid_date_range_filter';
 					$grid_col_options['width'] = 120;
 				}
 				else {
 					$grid_col_options['filter_block'] = 'grid_range_filter';
 					$grid_col_options['width'] = 80;
 				}
 			}
 
 			if ( $php_type == 'int' && ($not_null || is_numeric($default_value)) ) {
 				// is integer field AND not null
 				$field_options['default'] = (int)$default_value;
 			}
 			else {
 				$field_options['default'] = $default_value;
 			}
 
 			$fields[$field_info['Field']] = $field_options;
 			$grids_fields[$field_info['Field']] = $grid_col_options;
 		}
 
 		$grids['Default']['Fields'] = $grids_fields;
 
 		$ret = Array (
 			'IDField' => $id_field,
 			'Fields' => $fields,
 			'Grids' => $grids,
 		);
 
 		$decorator = new UnitConfigDecorator();
 		$ret = $decorator->decorate($ret);
 
 		$this->Application->InitParser();
 		ob_start();
 		echo $this->Application->ParseBlock(Array('name' => 'incs/header', 'body_properties' => 'style="background-color: #E7E7E7; margin: 8px;"'));
 	?>
 		<script type="text/javascript">
 			set_window_title('Table "<?php echo $table_name; ?>" Structure');
 		</script>
 
 		<a href="javascript:window_close();">Close Window</a><br /><br />
 		<?php echo $GLOBALS['debugger']->highlightString($ret); ?>
 		<br /><br /><a href="javascript:window_close();">Close Window</a><br />
 	<?php
 		echo $this->Application->ParseBlock(Array('name' => 'incs/footer'));
 		echo ob_get_clean();
 		$event->status = kEvent::erSTOP;
 	}
 
 	/**
 	 * Refreshes ThemeFiles & Themes tables by actual content on HDD
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnRebuildThemes(kEvent $event)
 	{
 		if ( $this->Application->GetVar('ajax') == 'yes' ) {
 			$event->status = kEvent::erSTOP;
 		}
 
 		/** @var kThemesHelper $themes_helper */
 		$themes_helper = $this->Application->recallObject('ThemesHelper');
 
 		$themes_helper->refreshThemes();
 
 		$event->SetRedirectParam('action_completed', 1);
 	}
 
 	/**
 	 * Dumps assets
 	 *
 	 * @param kEvent $event Event.
 	 *
 	 * @return void
 	 */
 	protected function OnDumpAssets(kEvent $event)
 	{
 		if ( $this->Application->GetVar('ajax') == 'yes' ) {
 			$event->status = kEvent::erSTOP;
 		}
 
 		/** @var MinifyHelper $minify_helper */
 		$minify_helper = $this->Application->recallObject('MinifyHelper');
 
 		$minify_helper->delete();
 		$minify_helper->dump();
 
 		$event->SetRedirectParam('action_completed', 1);
 	}
 
 	/**
 	 * Saves grid column widths after their resize by user
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnSaveColumns(kEvent $event)
 	{
 		$picker_helper = new kColumnPickerHelper(
 			$this->Application->GetVar('main_prefix'),
 			$this->Application->GetLinkedVar('grid_name')
 		);
 
 		$picked = trim($this->Application->GetVar('picked_str'), '|');
 		$hidden = trim($this->Application->GetVar('hidden_str'), '|');
 
 		$picker_helper->saveColumns($picked, $hidden);
 		$this->finalizePopup($event);
 	}
 
 	/**
 	 * Saves various admin settings via ajax
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnSaveSetting(kEvent $event)
 	{
 		if ( $this->Application->GetVar('ajax') != 'yes' ) {
 			return;
 		}
 
 		$var_name = $this->Application->GetVar('var_name');
 		$var_value = $this->Application->GetVar('var_value');
 
 		$this->Application->StorePersistentVar($var_name, $var_value);
 
 		$event->status = kEvent::erSTOP;
 	}
 
 	/**
 	 * Just closes popup & deletes last_template & opener_stack if popup, that is closing
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnClosePopup(kEvent $event)
 	{
 		$event->SetRedirectParam('opener', 'u');
 	}
 
 	/**
 	 * Occurs right after initialization of the kernel, used mainly as hook-to event
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnStartup(kEvent $event)
 	{
 		if ( $this->Application->isAdmin ) {
 			return;
 		}
 
 		$base_url = preg_quote($this->Application->BaseURL(), '/');
 		$referrer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
 
 		if ( $referrer && !preg_match('/^' . $base_url . '/', $referrer) ) {
 			$this->Application->Session->SetCookie('original_referrer', $referrer);
 			$this->Application->SetVar('original_referrer', $referrer);
 		}
 	}
 
 	/**
 	 * Occurs right before echoing the output, in Done method of application, used mainly as hook-to event
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBeforeShutdown(kEvent $event)
 	{
 
 	}
 
 	/**
 	 * Is called after tree was build (when not from cache)
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnAfterBuildTree(kEvent $event)
 	{
 
 	}
 
 	/**
 	 * Called by AJAX to perform CSV export
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnExportCSV(kEvent $event)
 	{
 		/** @var kCSVHelper $csv_helper */
 		$csv_helper = $this->Application->recallObject('CSVHelper');
 
 		$csv_helper->PrefixSpecial = $csv_helper->getPrefix(false);
 		$csv_helper->grid = $this->Application->GetVar('grid');
 		$csv_helper->ExportStep();
 		$event->status = kEvent::erSTOP;
 	}
 
 	/**
 	 * Returning created by AJAX CSV file
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnGetCSV(kEvent $event)
 	{
 		/** @var kCSVHelper $csv_helper */
 		$csv_helper = $this->Application->recallObject('CSVHelper');
 
 		$csv_helper->GetCSV();
 	}
 
 	/**
 	 * Start CSV import
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnCSVImportBegin(kEvent $event)
 	{
 		/** @var kDBItem $object */
 		$object = $event->getObject(Array ('skip_autoload' => true));
 
 		$object->setID(0);
 		$field_values = $this->getSubmittedFields($event);
 		$object->SetFieldsFromHash($field_values);
 		$event->setEventParam('form_data', $field_values);
 
 		$event->redirect = false;
 		$result = 'required';
 
 		if ( $object->GetDBField('ImportFile') ) {
 			/** @var kCSVHelper $csv_helper */
 			$csv_helper = $this->Application->recallObject('CSVHelper');
 
 			$csv_helper->PrefixSpecial = $csv_helper->getPrefix(true);
 			$csv_helper->grid = $this->Application->GetVar('grid');
 			$result = $csv_helper->ImportStart($object->GetField('ImportFile', 'file_paths'));
 
 			if ( $result === true ) {
 				$event->redirect = $this->Application->GetVar('next_template');
 				$event->SetRedirectParam('PrefixSpecial', $this->Application->GetVar('PrefixSpecial'));
 				$event->SetRedirectParam('grid', $this->Application->GetVar('grid'));
 			}
 		}
 
 		if ( $event->redirect === false ) {
 			$object->SetError('ImportFile', $result);
 			$event->status = kEvent::erFAIL;
 		}
 	}
 
 	/**
 	 * Performs one CSV import step
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnCSVImportStep(kEvent $event)
 	{
 		/** @var kCSVHelper $import_helper */
 		$import_helper = $this->Application->recallObject('CSVHelper');
 
 		$import_helper->ImportStep();
 		$event->status = kEvent::erSTOP;
 	}
 
 	/**
 	 * Shows unit config filename, where requested prefix is defined
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnCheckPrefixConfig(kEvent $event)
 	{
 		$prefix = $this->Application->GetVar('config_prefix');
 		$config_file = $this->Application->UnitConfigReader->prefixFiles[$prefix];
 
 		$this->Application->InitParser();
 
 		ob_start();
 		echo $this->Application->ParseBlock(Array('name' => 'incs/header', 'body_properties' => 'style="background-color: #E7E7E7; margin: 8px;"'));
 		?>
 		<script type="text/javascript">
 			set_window_title('Unit Config of "<?php echo $prefix; ?>" prefix');
 		</script>
 
 		<a href="javascript:window_close();">Close Window</a><br /><br />
 		<strong>Prefix:</strong> <?php echo $prefix; ?><br />
 		<strong>Unit Config:</strong> <?php echo $GLOBALS['debugger']->highlightString($config_file); ?><br />
 		<br /><a href="javascript:window_close();">Close Window</a><br />
 
 		<?php
 		echo $this->Application->ParseBlock(Array ('name' => 'incs/footer'));
 		echo ob_get_clean();
 
 		$event->status = kEvent::erSTOP;
 	}
 
 	/**
 	 * Deletes temp tables, when user closes window using "x" button in top right corner
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnDropTempTablesByWID(kEvent $event)
 	{
 		$sid = $this->Application->GetSID();
 		$wid = $this->Application->GetVar('m_wid');
 		$tables = $this->Conn->GetCol('SHOW TABLES');
 		$mask_edit_table = '/' . TABLE_PREFIX . 'ses_' . $sid . '_' . $wid . '_edit_(.*)$/';
 
 		foreach ($tables as $table) {
 			if ( preg_match($mask_edit_table, $table, $rets) ) {
 				$this->Conn->Query('DROP TABLE IF EXISTS ' . $table);
 			}
 		}
 
 		echo 'OK';
 		$event->status = kEvent::erSTOP;
 	}
 
 
 	/**
 	 * Backup all data
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBackup(kEvent $event)
 	{
 		/** @var BackupHelper $backup_helper */
 		$backup_helper = $this->Application->recallObject('BackupHelper');
 
 		if ( !$backup_helper->initBackup() ) {
 			$event->status = kEvent::erFAIL;
 		}
 
 		$event->redirect = 'tools/backup2';
 	}
 
 	/**
 	 * Perform next backup step
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBackupProgress(kEvent $event)
 	{
 		/** @var BackupHelper $backup_helper */
 		$backup_helper = $this->Application->recallObject('BackupHelper');
 
 		$done_percent = $backup_helper->performBackup();
 
 		if ( $done_percent == 100 ) {
 			$event->redirect = 'tools/backup3';
 			return;
 		}
 
 		$event->status = kEvent::erSTOP;
 		echo $done_percent;
 	}
 
 	/**
 	 * Stops Backup & redirect to Backup template
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBackupCancel(kEvent $event)
 	{
 		$event->redirect = 'tools/backup1';
 	}
 
 	/**
 	 * Starts restore process
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnRestore(kEvent $event)
 	{
 		/** @var BackupHelper $backup_helper */
 		$backup_helper = $this->Application->recallObject('BackupHelper');
 
 		$backup_helper->initRestore();
 		$event->redirect = 'tools/restore3';
 	}
 
 	/**
 	 * Performs next restore step
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnRestoreProgress(kEvent $event)
 	{
 		/** @var BackupHelper $backup_helper */
 		$backup_helper = $this->Application->recallObject('BackupHelper');
 
 		$done_percent = $backup_helper->performRestore();
 
 		if ( $done_percent == BackupHelper::SQL_ERROR_DURING_RESTORE ) {
 			$event->redirect = 'tools/restore4';
 		}
 		elseif ( $done_percent == BackupHelper::FAILED_READING_BACKUP_FILE ) {
 			$this->Application->StoreVar('adm.restore_error', 'File read error');
 			$event->redirect = 'tools/restore4';
 		}
 		elseif ( $done_percent == 100 ) {
 			$backup_helper->replaceRestoredFiles();
 			$this->Application->StoreVar('adm.restore_success', 1);
 			$event->redirect = 'tools/restore4';
 		}
 		else {
 			$event->status = kEvent::erSTOP;
 			echo $done_percent;
 		}
 	}
 
 	/**
 	 * Stops Restore & redirect to Restore template
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnRestoreCancel(kEvent $event)
 	{
 		$event->redirect = 'tools/restore1';
 	}
 
 	/**
 	 * Deletes one backup file
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnDeleteBackup(kEvent $event)
 	{
 		/** @var BackupHelper $backup_helper */
 		$backup_helper = $this->Application->recallObject('BackupHelper');
 
 		$backup_helper->delete();
 	}
 
 	/**
 	 * Starts restore process
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnSqlQuery(kEvent $event)
 	{
 		$sql = $this->Application->GetVar('sql');
 
 		if ( $sql ) {
 			$start = microtime(true);
 			$result = $this->Conn->Query($sql);
 			$this->Application->SetVar('sql_time', round(microtime(true) - $start, 7));
 
 			if ( $result && is_array($result) ) {
 				$this->Application->SetVar('sql_has_rows', 1);
 				$this->Application->SetVar('sql_rows', serialize($result));
 			}
 
 			$check_sql = trim(strtolower($sql));
 
 			if ( preg_match('/^(insert|update|replace|delete)/', $check_sql) ) {
 				$this->Application->SetVar('sql_has_affected', 1);
 				$this->Application->SetVar('sql_affected', $this->Conn->getAffectedRows());
 			}
 		}
 
 		$this->Application->SetVar('query_status', 1);
 		$event->status = kEvent::erFAIL;
 	}
 
 	/**
 	 * Occurs after unit config cache was successfully rebuilt
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnAfterCacheRebuild(kEvent $event)
 	{
 
 	}
 
 	/**
 	 * Removes "Community -> Groups" section when it is not allowed
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnAfterConfigRead(kEvent $event)
 	{
 		parent::OnAfterConfigRead($event);
 
 		$section_adjustments = $this->Application->getUnitOption($event->Prefix, 'SectionAdjustments', Array());
 
 		if ( !$this->Application->ConfigValue('AdvancedUserManagement') ) {
 			$section_adjustments['in-portal:user_groups'] = 'remove';
 		}
 
 		$section_adjustments['in-portal:root'] = Array (
 			'label' => $this->Application->ConfigValue('Site_Name')
 		);
 
 		$this->Application->setUnitOption($event->Prefix, 'SectionAdjustments', $section_adjustments);
 	}
 
 	/**
 	 * Saves menu (tree) frame width
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnSaveMenuFrameWidth(kEvent $event)
 	{
 		$event->status = kEvent::erSTOP;
 
 		if ( !$this->Application->ConfigValue('ResizableFrames') ) {
 			return;
 		}
 
 		$this->Application->StorePersistentVar('MenuFrameWidth', (int)$this->Application->GetVar('width'));
 	}
 
 	/**
 	 * Retrieves data from memory cache
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnMemoryCacheGet(kEvent $event)
 	{
 		$event->status = kEvent::erSTOP;
 
 		$ret = Array ('message' => '', 'code' => 0); // 0 - ok, > 0 - error
 		$key = $this->Application->GetVar('key');
 
 		if ( !$key ) {
 			$ret['code'] = 1;
 			$ret['message'] = 'Key name missing';
 		}
 		else {
 			$value = $this->Application->getCache($key);
 
 			$ret['value'] =& $value;
 			$ret['size'] = is_string($value) ? kUtil::formatSize(strlen($value)) : '?';
 			$ret['type'] = gettype($value);
 
 			if ( kUtil::IsSerialized($value) ) {
 				$value = unserialize($value);
 			}
 
 			if ( is_array($value) ) {
 				$ret['value'] = print_r($value, true);
 			}
 
 			if ( $ret['value'] === false ) {
 				$ret['code'] = 2;
 				$ret['message'] = 'Key "' . $key . '" doesn\'t exist';
 			}
 		}
 
 		/** @var JSONHelper $json_helper */
 		$json_helper = $this->Application->recallObject('JSONHelper');
 
 		echo $json_helper->encode($ret);
 	}
 
 	/**
 	 * Retrieves data from memory cache
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnMemoryCacheSet(kEvent $event)
 	{
 		$event->status = kEvent::erSTOP;
 
 		$ret = Array ('message' => '', 'code' => 0); // 0 - ok, > 0 - error
 		$key = $this->Application->GetVar('key');
 
 		if ( !$key ) {
 			$ret['code'] = 1;
 			$ret['message'] = 'Key name missing';
 		}
 		else {
 			$value = $this->Application->GetVar('value');
 			$res = $this->Application->setCache($key, $value);
 
 			$ret['result'] = $res ? 'OK' : 'FAILED';
 		}
 
 		/** @var JSONHelper $json_helper */
 		$json_helper = $this->Application->recallObject('JSONHelper');
 
 		echo $json_helper->encode($ret);
 	}
 
 	/**
 	 * Deploy changes
 	 *
 	 * Usage: "php tools/run_event.php adm:OnDeploy b674006f3edb1d9cd4d838c150b0567d"
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnDeploy(kEvent $event)
 	{
 		$this->_deploymentAction($event);
 	}
 
 	/**
 	 * Synchronizes database revisions from "project_upgrades.sql" file
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnSynchronizeDBRevisions(kEvent $event)
 	{
 		$this->_deploymentAction($event, true);
 	}
 
 	/**
 	 * Common code to invoke deployment helper
 	 *
 	 * @param kEvent $event
 	 * @param bool $dry_run
 	 * @return void
 	 * @access protected
 	 */
 	protected function _deploymentAction(kEvent $event, $dry_run = false)
 	{
 		/** @var DeploymentHelper $deployment_helper */
 		$deployment_helper = $this->Application->recallObject('DeploymentHelper');
 
 		$deployment_helper->setEvent($event);
 
 		if ( $deployment_helper->deployAll($dry_run) ) {
 			$event->SetRedirectParam('action_completed', 1);
 
 			if ( !$deployment_helper->isCommandLine ) {
 				// browser invocation -> don't perform redirect
 				$event->redirect = false;
 
 				// no redirect, but deployment succeeded - set redirect params directly
 				foreach ($event->getRedirectParams() as $param_name => $param_value) {
 					$this->Application->SetVar($param_name, $param_value);
 				}
 			}
 		}
 		else {
 			$event->status = kEvent::erFAIL;
 		}
 	}
 
 	/**
 	 * [SCHEDULED TASK]
 	 * 1. Delete all Debug files from system/.restricted folder	(format debug_@977827436@.txt)
 	 * 2. Run MySQL OPTIMIZE SQL one by one on all In-Portal tables (found by prefix).
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnOptimizePerformance(kEvent $event)
 	{
 		$start_time = adodb_mktime();
 
 		$sql = 'SELECT SessionKey
 				FROM ' . TABLE_PREFIX . 'UserSessions
 				WHERE LastAccessed > ' . $start_time;
 		$active_sessions = array_flip($this->Conn->GetCol($sql));
 
 		$files = scandir(RESTRICTED);
 		$file_path = RESTRICTED . '/';
 
 		foreach ($files AS $file_name) {
 			if ( !preg_match('#^debug_@([0-9]{9})@.txt$#', $file_name, $matches) ) {
 				// not debug file
 				continue;
 			}
 
 			$sid = $matches[1];
 
 			if ( isset($active_sessions[$sid]) || (filemtime($file_path . $file_name) > $start_time) ) {
 				// debug file belongs to an active session
 				// debug file is recently created (after sessions snapshot)
 				continue;
 			}
 
 			unlink($file_path . $file_name);
 		}
 
 		$system_tables = $this->Conn->GetCol('SHOW TABLES LIKE "' . TABLE_PREFIX . '%"');
 
 		foreach ($system_tables AS $table_name) {
 			$this->Conn->Query('OPTIMIZE TABLE ' . $table_name);
 		}
 	}
 
 	/**
 	 * Returns popup size (by template), if not cached, then parse template to get value
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnGetPopupSize(kEvent $event)
 	{
 		$event->status = kEvent::erSTOP;
 
 		if ( $this->Application->GetVar('ajax') != 'yes' ) {
 			return;
 		}
 
 		$t = $this->Application->GetVar('template_name');
 
 		$sql = 'SELECT *
 				FROM ' . TABLE_PREFIX . 'PopupSizes
 				WHERE TemplateName = ' . $this->Conn->qstr($t);
 		$popup_info = $this->Conn->GetRow($sql);
 
 		$this->Application->setContentType('text/plain');
 
 		if ( !$popup_info ) {
 			// dies when SetPopupSize tag found & in ajax request
 			$this->Application->InitParser();
 			$this->Application->ParseBlock(Array ('name' => $t));
 
 			// tag SetPopupSize not found in template -> use default size
 			echo '750x400';
 		}
 		else {
 			echo $popup_info['PopupWidth'] . 'x' . $popup_info['PopupHeight'];
 		}
 	}
 
 	/**
 	 * Writes HTTP request to System Log
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access public
 	 */
 	public function OnLogHttpRequest(kEvent $event)
 	{
 		if ( defined('DBG_REQUEST_LOG') && DBG_REQUEST_LOG && $this->Application->LoggedIn() ) {
 			$log = $this->Application->log('HTTP_REQUEST')->addRequestData();
 
 			if ( !$log->write() ) {
 				trigger_error('Unable to log Http Request due disabled "System Log"', E_USER_WARNING);
 			}
 		}
 	}
+
+	/**
+	 * Purges expired database cache entries.
+	 *
+	 * @param kEvent $event Event.
+	 *
+	 * @return void
+	 */
+	protected function OnPurgeExpiredDatabaseCacheScheduledTask(kEvent $event)
+	{
+		$sql = 'DELETE FROM ' . TABLE_PREFIX . 'SystemCache
+				WHERE LifeTime > 0 AND Cached + LifeTime < ' . time();
+		$this->Conn->Query($sql);
+	}
+
 }
 
 
 class UnitConfigDecorator {
 
 	var $parentPath = Array ();
 
 	/**
 	 * Decorates given array
 	 *
 	 * @param Array $var
 	 * @param int $level
 	 * @return string
 	 */
 	public function decorate($var, $level = 0)
 	{
 		$ret = '';
 
 		$deep_level = count($this->parentPath);
 
 		if ( $deep_level && ($this->parentPath[0] == 'Fields') ) {
 			$expand = $level < 2;
 		}
 		elseif ( $deep_level && ($this->parentPath[0] == 'Grids') ) {
 			if ( $deep_level == 3 && $this->parentPath[2] == 'Icons' ) {
 				$expand = false;
 			}
 			else {
 				$expand = $level < 4;
 			}
 		}
 		else {
 			$expand = $level == 0;
 		}
 
 		if ( is_array($var) ) {
 			$ret .= 'array(';
 			$prepend = $expand ? "\n" . str_repeat("\t", $level + 1) : '';
 
 			foreach ($var as $key => $value) {
 				array_push($this->parentPath, $key);
 				$ret .= $prepend . (is_string($key) ? "'" . $key . "'" : $key) . ' => ' . $this->decorate($value, $level + 1);
 				$ret .= ',' . ($expand ? '' : ' ');
 				array_pop($this->parentPath);
 			}
 
 			$prepend = $expand ? "\n" . str_repeat("\t", $level) : '';
 
 			if ( !$expand ) {
 				$ret = rtrim($ret, ', ');
 			}
 
 			$ret .=  $prepend . ')';
 		}
 		else {
 			if ( is_null($var) ) {
 				$ret = 'null';
 			}
 			elseif ( is_string($var) ) {
 				$ret = "'" . $var . "'";
 			}
 			else {
 				$ret = $var;
 			}
 		}
 
 		return $ret;
 	}
 }
Index: branches/5.2.x/core/units/admin/admin_config.php
===================================================================
--- branches/5.2.x/core/units/admin/admin_config.php	(revision 16656)
+++ branches/5.2.x/core/units/admin/admin_config.php	(revision 16657)
@@ -1,102 +1,103 @@
 <?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!');
 
 $config = Array (
 	'Prefix' => 'adm',
 	'ItemClass' => Array ('class' => 'kDBItem', 'file' => '', 'build_event' => 'OnItemBuild'),
 	'EventHandlerClass' => Array ('class' => 'AdminEventsHandler', 'file' => 'admin_events_handler.php', 'build_event' => 'OnBuild'),
 	'TagProcessorClass' => Array ('class' => 'AdminTagProcessor', 'file' => 'admin_tag_processor.php', 'build_event' => 'OnBuild'),
 
 	'RegisterClasses' => Array (
 		Array ('pseudo' => 'UnitConfigDecorator', 'class' => 'UnitConfigDecorator', 'file' => 'admin_events_handler.php', 'build_event' => ''),
 	),
 
 	'QueryString' => Array (
 		1 => 'event',
 	),
 
 	'ScheduledTasks' => Array (
 		'optimize_performance' => Array ('EventName' => 'OnOptimizePerformance', 'RunSchedule' => '0 0 * * *'),
+		'purge_expired_database_cache' => Array ('EventName' => 'OnPurgeExpiredDatabaseCacheScheduledTask', 'RunSchedule' => '0 0,12 * * *'),
 	),
 
 	'TitlePresets' => Array (
 		'tree_root' => Array ('format' => '!la_section_overview!'),
 		'tree_reports' => Array ('format' => '!la_section_overview!'),
 		'tree_system' => Array ('format' => '!la_section_overview!'),
 		'tree_tools' => Array ('format' => '!la_section_overview!'),
 		'system_tools' => Array ('format' => '!la_title_SystemTools!'),
 
 		'backup' => Array ('format' => '!la_performing_backup! - !la_Step! <span id="step_number"></span>'),
 		'import' => Array ('format' => '!la_performing_import! - !la_Step! <span id="step_number"></span>'),
 		'restore' => Array ('format' => '!la_performing_restore! - !la_Step! <span id="step_number"></span>'),
 		'server_info' => Array ('format' => '!la_tab_ServerInfo!'),
 		'sql_query' => Array ('format' => '!la_tab_QueryDB!'),
 
 		'no_permissions' => Array ('format' => '!la_title_NoPermissions!'),
 
 		'column_picker' => Array ('format' => '!la_title_ColumnPicker!'),
 		'csv_export' => Array ('format' => '!la_title_CSVExport!'),
 		'csv_import' => Array ('format' => '!la_title_CSVImport!'),
 	),
 
 	'PermSection' => Array ('main' => 'in-portal:service'),
 
 	'Sections' => Array (
 		'in-portal:root' => Array (
 			'parent' => null,
 			'icon' => 'site',
 			'label' => 'SITE_NAME',
 			'url' => Array ('t' => 'index', 'pass' => 'm'),
 			'permissions' => Array ('view'),
 			'priority' => 0,
 			'container' => true,
 			'type' => stTREE,
 			'icon_module' => 'core',
 		),
 
 		'in-portal:service' => Array (
 			'parent' => 'in-portal:tools',
 			'icon' => 'service',
 			'label' => 'la_tab_Service',
 			'url' => Array ('t' => 'tools/system_tools', 'pass' => 'm'),
 			'permissions' => Array ('view', 'edit'),
 			'priority' => 6,
 			'type' => stTREE,
 		),
 	),
 
 	'ListSQLs' => Array (
 		'' => '', // to prevent warning
 	),
 
 	'Fields' => Array (), // we need empty array because kernel doesn't use virtual fields else
 	'VirtualFields' => Array (
 		'ImportFile' => Array (
 			'type' => 'string',
 			'formatter' => 'kUploadFormatter', 'max_size' => MAX_UPLOAD_SIZE, // in Bytes !
 			'error_msgs' => Array (
 				'cant_open_file' => '!la_error_CantOpenFile!',
 				'no_matching_columns' => '!la_error_NoMatchingColumns!',
 			),
 			'file_types' => '*.csv', 'files_description' => '!la_hint_CSVFiles!',
 			'upload_dir' => '/system/import/', // relative to project's home
 			'multiple' => false, 'direct_links' => false,
 			'default' => null,
 		),
 
 		'Content' => Array ('type' => 'string', 'default' => ''),
 	),
 );