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

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

class 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),
			'OnClosePopup' => Array ('self' => true),
			'OnSaveSetting' => Array ('self' => true),
			'OnDropTempTablesByWID' => Array ('self' => true),
		);

		$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', '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;
		}

		$perm_helper = $this->Application->recallObject('PermissionsHelper');
		/* @var $perm_helper kPermissionsHelper */

		$csv_events = Array ('OnCSVImportBegin', 'OnCSVImportStep', 'OnExportCSV', 'OnGetCSV');

		if ( in_array($event->Name, $csv_events) ) {
			$csv_helper = $this->Application->recallObject('CSVHelper');
			/* @var $csv_helper kCSVHelper */

			$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);

		$skin_helper = $this->Application->recallObject('SkinHelper');
		/* @var $skin_helper 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)
	{
		$compiler = $this->Application->recallObject('NParserCompiler');
		/* @var $compiler 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 (),
			)
		);

		$grid_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' ) {
				$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;
		}

		$themes_helper = $this->Application->recallObject('ThemesHelper');
		/* @var $themes_helper kThemesHelper */

		$themes_helper->refreshThemes();

		$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 = $this->Application->recallObject('ColumnPickerHelper');
		/* @var $picker_helper kColumnPickerHelper */

		$picker_helper->SetGridName($this->Application->GetLinkedVar('grid_name'));

		$picked = trim($this->Application->GetVar('picked_str'), '|');
		$hidden = trim($this->Application->GetVar('hidden_str'), '|');

		$main_prefix = $this->Application->GetVar('main_prefix');

		$picker_helper->SaveColumns($main_prefix, $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)
	{
		$csv_helper = $this->Application->recallObject('CSVHelper');
		/* @var $csv_helper kCSVHelper */

		$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)
	{
		$csv_helper = $this->Application->recallObject('CSVHelper');
		/* @var $csv_helper kCSVHelper */

		$csv_helper->GetCSV();
	}

	/**
	 * Start CSV import
	 *
	 * @param kEvent $event
	 * @return void
	 * @access protected
	 */
	protected function OnCSVImportBegin(kEvent $event)
	{
		$object = $event->getObject(Array ('skip_autoload' => true));
		/* @var $object kDBItem */

		$field_values = $this->getSubmittedFields($event);
		$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));

		$event->redirect = false;
		$result = 'required';

		if ( $object->GetDBField('ImportFile') ) {
			$csv_helper = $this->Application->recallObject('CSVHelper');
			/* @var $csv_helper kCSVHelper */

			$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)
	{
		$import_helper = $this->Application->recallObject('CSVHelper');
		/* @var $import_helper kCSVHelper */

		$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)
	{
		$backup_helper = $this->Application->recallObject('BackupHelper');
		/* @var $backup_helper 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)
	{
		$backup_helper = $this->Application->recallObject('BackupHelper');
		/* @var $backup_helper 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)
	{
		$backup_helper = $this->Application->recallObject('BackupHelper');
		/* @var $backup_helper BackupHelper */

		$backup_helper->initRestore();
		$event->redirect = 'tools/restore3';
	}

	/**
	 * Performs next restore step
	 *
	 * @param kEvent $event
	 * @return void
	 * @access protected
	 */
	protected function OnRestoreProgress(kEvent $event)
	{
		$backup_helper = $this->Application->recallObject('BackupHelper');
		/* @var $backup_helper 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)
	{
		$backup_helper = $this->Application->recallObject('BackupHelper');
		/* @var $backup_helper 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->SetConfigValue('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';
			}
		}

		$json_helper = $this->Application->recallObject('JSONHelper');
		/* @var $json_helper 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';
		}

		$json_helper = $this->Application->recallObject('JSONHelper');
		/* @var $json_helper 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)
	{
		$deployment_helper = $this->Application->recallObject('DeploymentHelper');
		/* @var $deployment_helper DeploymentHelper */

		$deployment_helper->deployAll();

		if ( $deployment_helper->isCommandLine ) {
			// command line invocation -> don't render template
			$event->status = kEvent::erSTOP;
		}
		else {
			// browser invocation -> don't perform redirect
			$event->redirect = false;
		}

		$event->SetRedirectParam('action_completed', 1);
	}

	/**
	 * Synchronizes database revisions from "project_upgrades.sql" file
	 *
	 * @param kEvent $event
	 * @return void
	 * @access protected
	 */
	protected function OnSynchronizeDBRevisions(kEvent $event)
	{
		$deployment_helper = $this->Application->recallObject('DeploymentHelper');
		/* @var $deployment_helper DeploymentHelper */

		if ( $deployment_helper->deployAll(true) ) {
			$this->Application->SetVar('action_completed', 1);
		}

		if ( $deployment_helper->isCommandLine ) {
			// command line invocation -> don't render template
			$event->status = kEvent::erSTOP;
		}
		else {
			// browser invocation -> don't perform redirect
			$event->redirect = false;
		}
	}

	/**
	 * [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'];
		}
	}
}


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) . ', ';
				array_pop($this->parentPath);
			}

			$prepend = $expand ? "\n" . str_repeat("\t", $level) : '';
			$ret = rtrim($ret, ', ') . $prepend . ')';
		}
		else {
			if ( is_null($var) ) {
				$ret = 'NULL';
			}
			elseif ( is_string($var) ) {
				$ret = "'" . $var . "'";
			}
			else {
				$ret = $var;
			}
		}

		return $ret;
	}
}