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

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

	class ConfigurationEventHandler extends kDBEventHandler  {

		/**
		 * Allows to override standard permission mapping
		 *
		 * @return void
		 * @access protected
		 * @see kEventHandler::$permMapping
		 */
		protected function mapPermissions()
		{
			parent::mapPermissions();

			$permissions = Array (
				'OnGenerateMaintenancePage' => Array ('self' => 'add|edit'),
			);

			$this->permMapping = array_merge($this->permMapping, $permissions);
		}

		/**
		 * Changes permission section to one from REQUEST, not from config
		 *
		 * @param kEvent $event
		 * @return bool
		 * @access public
		 */
		public function CheckPermission(kEvent $event)
		{
			$event->setEventParam('PermSection', $this->Application->GetVar('section'));
			return parent::CheckPermission($event);
		}

		/**
		 * Apply any custom changes to list's sql query
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 * @see kDBEventHandler::OnListBuild()
		 */
		protected function SetCustomQuery(kEvent $event)
		{
			parent::SetCustomQuery($event);

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

			$module = $this->Application->GetVar('module');
			$section = $this->Application->GetVar('section');

			$object->addFilter('module_filter', '%1$s.ModuleOwner = ' . $this->Conn->qstr($module));
			$object->addFilter('section_filter', '%1$s.Section = ' . $this->Conn->qstr($section));

			$can_change = $this->Application->ConfigValue('AllowAdminConsoleInterfaceChange');

			if ( !$can_change && !$this->Application->isDebugMode() ) {
				$object->addFilter('interface_change_filter', '%1$s.VariableName NOT IN ("AdminConsoleInterface", "AllowAdminConsoleInterfaceChange")');
			}

			if ( defined('IS_INSTALL') && IS_INSTALL ) {
				$object->addFilter('install_filter', '%1$s.Install = 1');
			}

			$object->addFilter('visible_filter', '%1$s.Heading <> ""');
		}

		/**
		 * Presets new system setting fields
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnPreCreate(kEvent $event)
		{
			parent::OnPreCreate($event);

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

			$object->SetDBField('Section', $this->Application->GetVar('section'));
			$object->SetDBField('ModuleOwner', $this->Application->GetVar('module'));
		}

		/**
		 * Sets custom validation
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnAfterItemLoad(kEvent $event)
		{
			static $default_field_options = null;

			parent::OnAfterItemLoad($event);

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

			// ability to validate each configuration variable separately
			if ( !isset($default_field_options) ) {
				$default_field_options = $object->GetFieldOptions('VariableValue');
			}

			$new_field_options = $default_field_options;
			$validation = $object->GetDBField('Validation');

			if ( $validation ) {
				$new_field_options = array_merge($new_field_options, unserialize($validation));
			}

			$object->SetFieldOptions('VariableValue', $new_field_options);
		}

		/**
		 * Performs custom validation
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnBeforeItemValidate(kEvent $event)
		{
			parent::OnBeforeItemValidate($event);

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

			// if password field is empty, then don't update
			if ( $object->GetDBField('ElementType') == 'password' ) {
				if ( trim($object->GetDBField('VariableValue')) != '' ) {
					/** @var kPasswordFormatter $password_formatter */
					$password_formatter = $this->Application->recallObject('kPasswordFormatter');

					$object->SetDBField('VariableValue', $password_formatter->hashPassword($object->GetDBField('VariableValue')));
				}
			}

			$this->_processCountryState($event);

			$variable_name = $object->GetDBField('VariableName');
			$new_value = $object->GetDBField('VariableValue');

			if ( $variable_name == 'AdminConsoleInterface' ) {
				$can_change = $this->Application->ConfigValue('AllowAdminConsoleInterfaceChange');

				if ( ($new_value != $object->GetOriginalField('VariableValue')) && !$can_change ) {
					$object->SetError('VariableValue', 'not_allowed', 'la_error_OperationNotAllowed');
				}
			}
			elseif ( $variable_name == 'HardMaintenanceTemplate' ) {
				$compile = $event->MasterEvent->getEventParam('compile_maintenance_template');
				$compile = $compile || $new_value != $object->GetOriginalField('VariableValue');

				if ( $compile && !$this->_generateMaintenancePage($new_value) ) {
					$object->SetError('VariableValue', 'template_file_missing', 'la_error_TemplateFileMissing');
				}
			}
			elseif ( $variable_name == 'DefaultEmailRecipients' ) {
				$email_event_data = $this->Application->GetVar('email-template_' . $event->Prefix);
				$object->SetDBField('VariableValue', $email_event_data[0]['Recipients']);
			}

			/** @var kSectionsHelper $sections_helper */
			$sections_helper = $this->Application->recallObject('SectionsHelper');

			$section = $object->GetDBField('Section');

			if ( $section && !$sections_helper->getSectionData($section) ) {
				$object->SetError('Section', 'unknown_section');
			}
		}

		/**
		 * Checks, that state belongs to selected country
		 *
		 * @param kEvent $event
		 * @access protected
		 */
		protected function _processCountryState(kEvent $event)
		{
			/** @var kDBItem $object */
			$object = $event->getObject();

			$country_iso = $this->_getCountryByState($event);
			$state_name = $object->GetDBField('VariableValue');

			if ( !$country_iso || !$state_name ) {
				return;
			}

			/** @var kCountryStatesHelper $cs_helper */
			$cs_helper = $this->Application->recallObject('CountryStatesHelper');

			$state_iso = $cs_helper->getStateIso($state_name, $country_iso);

			if ( $state_iso !== false ) {
				$object->SetDBField('VariableValue', $state_iso);
			}
			else {
				// selected state doesn't belong to selected country
				$object->SetError('VariableValue', 'invalid_state', 'la_InvalidState');
			}
		}

		/**
		 * Returns country iso code, that matches current state variable name
		 *
		 * @param kEvent $event
		 * @return bool
		 * @access protected
		 */
		protected function _getCountryByState(kEvent $event)
		{
			/** @var kDBItem $object */
			$object = $event->getObject();

			$variable_name = $object->GetDBField('VariableName');

			$state_country_hash = Array (
				'Comm_State' => 'Comm_Country',
				'Comm_Shipping_State' => 'Comm_Shipping_Country'
			);

			if ( !array_key_exists($variable_name, $state_country_hash) ) {
				return false;
			}

			$field_values = $this->Application->GetVar($event->getPrefixSpecial(true));

			$sql = 'SELECT VariableId
					FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
					WHERE VariableName = ' . $this->Conn->qstr($state_country_hash[$variable_name]);
			$country_variable_id = $this->Conn->GetOne($sql);

			return $field_values[$country_variable_id]['VariableValue'];
		}

		/**
		 * Does custom password setting processong
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnBeforeItemUpdate(kEvent $event)
		{
			parent::OnBeforeItemUpdate($event);

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

			// if password field is empty, then don't update
			if ( $object->GetDBField('ElementType') == 'password' && trim($object->GetDBField('VariableValue')) == '' ) {
				$object->SetFieldOption('VariableValue', 'skip_empty', 1);
			}
		}

		/**
		 * Occurs after updating item
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnAfterItemUpdate(kEvent $event)
		{
			static $skin_deleted = false;

			parent::OnAfterItemUpdate($event);

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

			if ( $object->GetDBField('ElementType') == 'password' && trim($object->GetDBField('VariableValue')) == '' ) {
				$object->SetFieldOption('VariableValue', 'skip_empty', 0);
			}

			// allows to check if variable's value was changed now
			$variable_name = $object->GetDBField('VariableName');
			$changed = $this->Application->GetVar($event->getPrefixSpecial() . '_changed', Array ());

			if ( $object->GetDBField('VariableValue') != $object->GetOriginalField('VariableValue') ) {
				$changed[] = $variable_name;
				$this->Application->SetVar($event->getPrefixSpecial() . '_changed', $changed);

				// update value in cache, so other code (during this script run) would use new value
				$this->Application->SetConfigValue($variable_name, $object->GetDBField('VariableValue'), true);

				$sorting_prefix = $this->getSortingPrefix($variable_name);

				if ( $sorting_prefix ) {
					$sql = 'DELETE FROM ' . TABLE_PREFIX . 'UserPersistentSessionData
							WHERE VariableName LIKE "' . $sorting_prefix . '%Sortings.%"';
					$this->Conn->Query($sql);
				}
			}

			if ( $variable_name == 'Require_AdminSSL' || $variable_name == 'AdminSSL_URL' ) {
				// when administrative console is moved to SSL mode, then delete skin
				if ( in_array($variable_name, $changed) && !$skin_deleted ) {
					/** @var SkinHelper $skin_helper */
					$skin_helper = $this->Application->recallObject('SkinHelper');

					$skin_file = $skin_helper->getSkinPath();
					if ( file_exists($skin_file) ) {
						unlink($skin_file);
					}

					$skin_deleted = true;
				}
			}
		}

		/**
		 * Returns prefix, related to given sorting system setting
		 *
		 * @param string $system_setting System setting.
		 *
		 * @return boolean|string
		 */
		protected function getSortingPrefix($system_setting)
		{
			foreach ( $this->Application->ModuleInfo as $module_info ) {
				if ( $module_info['Name'] == 'In-Portal' ) {
					continue;
				}

				$prefix = ($module_info['Var'] == 'adm') ? 'c' : $module_info['Var'];
				$config_mapping = $this->Application->getUnitOption($prefix, 'ConfigMapping');

				if ( (isset($config_mapping['DefaultSorting1Field'])
					&& $system_setting == $config_mapping['DefaultSorting1Field'])
					|| (isset($config_mapping['DefaultSorting2Field'])
					&& $system_setting == $config_mapping['DefaultSorting2Field'])
				) {
					return $prefix;
				}
			}

			return false;
		}

		/**
		 * Updates kDBItem
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnUpdate(kEvent $event)
		{
			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
				$event->status = kEvent::erFAIL;
				return;
			}

			$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));

			// 1. save user selected module root category
			$new_category_id = getArrayValue($items_info, 'ModuleRootCategory', 'VariableValue');
			if ( $new_category_id !== false ) {
				unset($items_info['ModuleRootCategory']);
			}

			/** @var kDBItem $object */
			$object = $event->getObject( Array('skip_autoload' => true) );

			if ( $items_info ) {
				$has_error = false;

				foreach ($items_info as $id => $field_values) {
					$object->Clear(); // clear validation errors from previous variable
					$object->Load($id);
					$object->SetFieldsFromHash($field_values);
					$event->setEventParam('form_data', $field_values);

					if ( !$object->Update($id) ) {
						// don't stop when error found !
						$has_error = true;
					}
				}

				$event->status = $has_error ? kEvent::erFAIL : kEvent::erSUCCESS;
			}

			if ( $event->status == kEvent::erSUCCESS ) {
				$event->SetRedirectParam('action_completed', 1);

				if ( $new_category_id !== false ) {
					// root category was submitted
					$module = $this->Application->GetVar('module');
					$root_category_id = $this->Application->findModule('Name', $module, 'RootCat');

					if ( $root_category_id != $new_category_id ) {
						// root category differs from one in db
						$fields_hash = Array ('RootCat' => $new_category_id);
						$this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'Modules', 'Name = ' . $this->Conn->qstr($module));
					}
				}

				// reset cache
				$changed = $this->Application->GetVar($event->getPrefixSpecial() . '_changed', Array ());
				$require_refresh = Array ('AdvancedUserManagement', 'Site_Name', 'AdminConsoleInterface', 'UsePopups', 'Catalog_PreselectModuleTab');

				$refresh_sections = array_intersect($require_refresh, $changed);
				$require_full_refresh = Array ('Site_Name', 'AdminConsoleInterface');

				if ( array_intersect($require_full_refresh, $changed) ) {
					$event->SetRedirectParam('refresh_all', 1);
				}
				elseif ( $refresh_sections ) {
					$event->SetRedirectParam('refresh_tree', 1);
				}

				if ( $refresh_sections ) {
					// reset sections too, because of AdvancedUserManagement
					$this->Application->DeleteSectionCache();
				}

				$this->Application->DeleteUnitCache($changed);
			}
			else{
				$errors = $this->Application->GetVar('errors_' . $event->getPrefixSpecial());

				if ( $errors ) {
					// because we have list out there, and this is item
					$this->Application->SetVar('first_error', key($errors));
					$this->Application->removeObject($event->getPrefixSpecial());
				}
			}

			// keeps module and section in REQUEST to ensure, that last admin template will work
			$event->SetRedirectParam('module', $this->Application->GetVar('module'));
			$event->SetRedirectParam('section', $this->Application->GetVar('section'));
		}

		/**
		 * Process items from selector (selected_ids var, key - prefix, value - comma separated ids)
		 *
		 * @param kEvent $event
		 */
		function OnProcessSelected($event)
		{
			$selected_ids = $this->Application->GetVar('selected_ids');
			$this->Application->StoreVar('ModuleRootCategory', $selected_ids['c']);

			$event->SetRedirectParam('opener', 'u');
		}

		/**
		 * Generates maintenance page
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnGenerateMaintenancePage(kEvent $event)
		{
			$event->setEventParam('compile_maintenance_template', 1);

			$event->CallSubEvent('OnUpdate');
		}

		/**
		 * Generates HTML version of hard maintenance template
		 *
		 * @param string $template
		 * @return bool
		 * @access protected
		 */
		protected function _generateMaintenancePage($template = null)
		{
			if ( !isset($template) ) {
				$template = $this->Application->ConfigValue('HardMaintenanceTemplate');
			}

			/** @var kCurlHelper $curl_helper */
			$curl_helper = $this->Application->recallObject('CurlHelper');

			$html = $curl_helper->Send($this->Application->BaseURL() . '?t=' . $template);

			if ( $curl_helper->isGoodResponseCode() ) {
				file_put_contents(WRITEABLE . DIRECTORY_SEPARATOR . 'maintenance.html', $html);

				return true;
			}

			return false;
		}

		/**
		 * Returns auto-complete values for ajax-dropdown
		 *
		 * @param kEvent $event Event.
		 * @param string $term Term.
		 *
		 * @return Array
		 * @access protected
		 */
		protected function getAutoCompleteSuggestions(kEvent $event, $term)
		{
			/** @var kDBItem $object */
			$object = $event->getObject();

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

			if ( !$field || !$term || !$object->isField($field) ) {
				return array();
			}

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

			if ( !$limit ) {
				$limit = 20;
			}

			$sql = 'SELECT DISTINCT ' . $field . ', ModuleOwner
					FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
					WHERE ' . $field . ' LIKE ' . $this->Conn->qstr('%' . $term . '%') . '
					ORDER BY ' . $field . ' ASC';
			$data = $this->Conn->Query($sql);

			$suggestions = array();

			foreach ($data as $raw_suggestion) {
				$suggestion = $raw_suggestion[$field];

				if ( !isset($suggestions[$suggestion]) ) {
					$suggestions[$suggestion] = array();
				}

				$suggestions[$suggestion][] = $raw_suggestion['ModuleOwner'];
			}

			array_splice($suggestions, $limit);

			$ret = array();
			$of_label = $this->Application->Phrase('la_From', false);

			foreach ($suggestions as $suggestion_value => $suggestion_modules) {
				$suggestion_module = in_array('In-Portal', $suggestion_modules) ? 'In-Portal' : implode(', ', $suggestion_modules);
				$suggestion_title = $suggestion_value . ' <em style="color: grey;">' . $of_label . ' ' . $suggestion_module . '</em>';

				$ret[$suggestion_value] = $suggestion_title;
			}

			return $ret;
		}

		/**
		 * Prefills module dropdown
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnAfterConfigRead(kEvent $event)
		{
			parent::OnAfterConfigRead($event);

			$options = Array ();

			foreach ($this->Application->ModuleInfo as $module_name => $module_info) {
				if ( $module_name == 'Core' ) {
					continue;
				}

				$options[$module_name] = $module_name;

				if ( $module_name == 'In-Portal' ) {
					$options['In-Portal:Users'] = 'In-Portal:Users';
				}
			}

			$fields = $this->Application->getUnitOption($event->Prefix, 'Fields');
			$fields['ModuleOwner']['options'] = $options;
			$this->Application->setUnitOption($event->Prefix, 'Fields', $fields);
		}
	}
